using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Robust.Shared.Maths { /// /// Represents a float vector with two components (x, y). /// [StructLayout(LayoutKind.Sequential)] [Serializable] public struct Vector2 : IEquatable, IApproxEquatable { /// /// The X component of the vector. /// public float X; /// /// The Y component of the vector. /// public float Y; /// /// A zero length vector. /// public static readonly Vector2 Zero = new(0, 0); /// /// A vector with all components set to 1. /// public static readonly Vector2 One = new(1, 1); /// /// A unit vector pointing in the +X direction. /// public static readonly Vector2 UnitX = new(1, 0); /// /// A unit vector pointing in the +Y direction. /// public static readonly Vector2 UnitY = new(0, 1); public static readonly Vector2 Infinity = new(float.PositiveInfinity, float.PositiveInfinity); /// /// A vector with NaN X and Y. /// public static readonly Vector2 NaN = new(float.NaN, float.NaN); /// /// Construct a vector from its coordinates. /// /// X coordinate /// Y coordinate [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2(float x, float y) { X = x; Y = y; } /// /// Gets the length (magnitude) of the vector. /// public readonly float Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => MathF.Sqrt(LengthSquared); } /// /// Gets the squared length of the vector. /// public readonly float LengthSquared { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => X * X + Y * Y; } /// /// Returns a new, normalized, vector. /// /// public readonly Vector2 Normalized { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { var length = Length; return new Vector2(X / length, Y / length); } } /// /// Returns a new, rotated 90 degrees clockwise (in world Y-up orientation), vector. /// /// public readonly Vector2 Rotated90DegreesClockwiseWorld { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return new Vector2(Y, -X); } } /// /// Returns a new, rotated 90 degrees anticlockwise (in world Y-up orientation), vector. /// /// public readonly Vector2 Rotated90DegreesAnticlockwiseWorld { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return new Vector2(-Y, X); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector2 Rounded() { return new(MathF.Round(X), MathF.Round(Y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector2i Floored() { return new((int) MathF.Floor(X), (int) MathF.Floor(Y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector2i Ceiled() { return new((int) MathF.Ceiling(X), (int) MathF.Ceiling(Y)); } /// /// Subtracts a vector from another, returning a new vector. /// /// Vector to subtract from. /// Vector to subtract with. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator -(Vector2 a, Vector2 b) { return new(a.X - b.X, a.Y - b.Y); } /// /// Subtracts a scalar with each component of a vector, returning a new vecotr.. /// /// Vector to subtract from. /// Scalar to subtract with. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator -(Vector2 a, float b) { return new(a.X - b, a.Y - b); } /// /// Negates a vector. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator -(Vector2 vec) { return new(-vec.X, -vec.Y); } /// /// Adds two vectors together, returning a new vector with the components of each added together. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator +(Vector2 a, Vector2 b) { return new(a.X + b.X, a.Y + b.Y); } /// /// Adds a scalar to each component of a vector, returning a new vector. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator +(Vector2 a, float b) { return new(a.X + b, a.Y + b); } /// /// Multiply a vector by a scale by multiplying the individual components. /// /// The vector to multiply. /// The scale to multiply with. /// A new vector. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator *(Vector2 vec, float scale) { return new(vec.X * scale, vec.Y * scale); } /// /// Multiplies a vector's components corresponding to a vector scale. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator *(Vector2 vec, Vector2 scale) { return new(vec.X * scale.X, vec.Y * scale.Y); } /// /// Divide a vector by a scale by dividing the individual components. /// /// The vector to divide. /// The scale to divide by. /// A new vector. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator /(Vector2 vec, float scale) { return new(vec.X / scale, vec.Y / scale); } /// /// Divides a vector's components corresponding to a vector scale. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 operator /(Vector2 vec, Vector2 scale) { return new(vec.X / scale.X, vec.Y / scale.Y); } /// /// Return a vector made up of the smallest components of the provided vectors. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ComponentMin(Vector2 a, Vector2 b) { return new( MathF.Min(a.X, b.X), MathF.Min(a.Y, b.Y) ); } /// /// Return a vector made up of the largest components of the provided vectors. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ComponentMax(Vector2 a, Vector2 b) { return new( MathF.Max(a.X, b.X), MathF.Max(a.Y, b.Y) ); } /// /// Returns the vector with the smallest magnitude. If both have equal magnitude, is selected. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 MagnitudeMin(Vector2 a, Vector2 b) { return a.LengthSquared < b.LengthSquared ? a : b; } /// /// Returns the vector with the largest magnitude. If both have equal magnitude, is selected. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 MagnitudeMax(Vector2 a, Vector2 b) { return a.LengthSquared >= b.LengthSquared ? a : b; } /// /// Clamps the components of a vector to minimum and maximum vectors. /// /// The vector to clamp. /// The lower bound vector. /// The upper bound vector. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Clamp(Vector2 vector, Vector2 min, Vector2 max) { return new( MathHelper.Clamp(vector.X, min.X, max.X), MathHelper.Clamp(vector.Y, min.Y, max.Y) ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Abs(in Vector2 a) { return new(Math.Abs(a.X), Math.Abs(a.Y)); } /// /// Calculates the dot product of two vectors. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Dot(Vector2 a, Vector2 b) { return a.X * b.X + a.Y * b.Y; } /// /// Perform the cross product on two vectors. In 2D this produces a scalar. /// public static float Cross(in Vector2 a, in Vector2 b) { return a.X * b.Y - a.Y * b.X; } /// /// Perform the cross product on a vector and a scalar. In 2D this produces /// a vector. /// public static Vector2 Cross(in Vector2 a, float s) { return new(s * a.Y, -s * a.X); } /// /// Perform the cross product on a scalar and a vector. In 2D this produces /// a vector. /// public static Vector2 Cross(float s, in Vector2 a) { return new(-s * a.Y, s * a.X); } /// /// Linearly interpolates two vectors so make a mix based on a factor. /// /// /// a when factor=0, b when factor=1, a linear interpolation between the two otherwise. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 Lerp(Vector2 a, Vector2 b, float factor) { return new( //factor * (b.X - a.X) + a.X, MathHelper.Lerp(a.X, b.X, factor), //factor * (b.Y - a.Y) + a.Y MathHelper.Lerp(a.Y, b.Y, factor) ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 LerpClamped(in Vector2 a, in Vector2 b, float factor) { if (factor <= 0) return a; if (factor >= 1) return b; return Lerp(a, b, factor); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 InterpolateCubic(Vector2 preA, Vector2 a, Vector2 b, Vector2 postB, float t) { return a + (b - preA + (preA * 2.0f - a * 5.0f + b * 4.0f - postB + ((a - b) * 3.0f + postB - preA) * t) * t) * t * 0.5f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Deconstruct(out float x, out float y) { x = X; y = Y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Vector2((float x, float y) tuple) { var (x, y) = tuple; return new Vector2(x, y); } /// /// Returns a string that represents the current Vector2. /// public override readonly string ToString() { return $"({X}, {Y})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Vector2 a, Vector2 b) { return a.Equals(b); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Vector2 a, Vector2 b) { return !a.Equals(b); } /// /// Compare a vector to another vector and check if they are equal. /// /// Other vector to check. /// True if the two vectors are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool Equals(Vector2 other) { // ReSharper disable CompareOfFloatsByEqualityOperator return X == other.X && Y == other.Y; // ReSharper restore CompareOfFloatsByEqualityOperator } /// /// Compare a vector to an object and check if they are equal. /// /// Other object to check. /// True if Object and vector are equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override readonly bool Equals(object? obj) { return obj is Vector2 vec && Equals(vec); } /// /// Returns the hash code for this instance. /// /// A unique hash code for this instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override readonly int GetHashCode() { unchecked { return (X.GetHashCode() * 397) ^ Y.GetHashCode(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool EqualsApprox(Vector2 other) { return MathHelper.CloseTo(X, other.X) && MathHelper.CloseTo(Y, other.Y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool EqualsApprox(Vector2 other, double tolerance) { return MathHelper.CloseTo(X, other.X, tolerance) && MathHelper.CloseTo(Y, other.Y, tolerance); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly bool EqualsApproxPercent(Vector2 other, double tolerance = 0.0001) { return MathHelper.CloseToPercent(X, other.X, tolerance) && MathHelper.CloseToPercent(Y, other.Y, tolerance); } } }