using System; using System.ComponentModel; using System.Xml.Serialization; namespace JuicyGraphics.Mathematics { using Exceptions; [ImmutableObject(true), Serializable] public struct vec2 : IComparable, IComparable, IEquatable, IFormattable { private readonly double _x; private readonly double _y; public vec2(double x, double y) { _x = x; _y = y; } public vec2(double[] xy) { if (xy.Length == 2) { _x = xy[0]; _y = xy[1]; } else { throw new ArgumentException(TWO_COMPONENTS); } } public vec2(vec2 v1) { _x = v1.x; _y = v1.y; } public double x { get { return _x; } } public double y { get { return _y; } } public vec2 xx { get { return new vec2(_x, _x); } } public vec2 xy { get { return this; } } public vec2 yx { get { return new vec2(_y, _x); } } public vec2 yy { get { return new vec2(_y, _y); } } public vec2 normal { get { return normalize(); } } public double magnitude { get { return Math.Sqrt(sumComponentSqrs()); } } [XmlIgnore] public double[] array { get { return new[] { x, y }; } } public double this[int index] { get { switch (index) { case 0: return _x; case 1: return _y; default: throw new ArgumentException(TWO_COMPONENTS, "index"); } } } public static vec2 operator +(vec2 v1, vec2 v2) { return new vec2( v1.x + v2.x, v1.y + v2.y); } public static vec2 operator -(vec2 v1, vec2 v2) { return new vec2( v1.x - v2.x, v1.y - v2.y); } public static vec2 operator *(vec2 v1, double s2) { return new vec2( v1.x * s2, v1.y * s2); } public static vec2 operator *(double s1, vec2 v2) { return v2 * s1; } public static vec2 operator /(vec2 v1, double s2) { return new vec2( v1.x / s2, v1.y / s2); } public static vec2 operator -(vec2 v1) { return new vec2( -v1.x, -v1.y); } public static vec2 operator +(vec2 v1) { return new vec2( +v1.x, +v1.y); } public static bool operator <(vec2 v1, vec2 v2) { return v1.sumComponentSqrs() < v2.sumComponentSqrs(); } public static bool operator >(vec2 v1, vec2 v2) { return v1.sumComponentSqrs() > v2.sumComponentSqrs(); } public static bool operator <=(vec2 v1, vec2 v2) { return v1.sumComponentSqrs() <= v2.sumComponentSqrs(); } public static bool operator >=(vec2 v1, vec2 v2) { return v1.sumComponentSqrs() >= v2.sumComponentSqrs(); } public static bool operator ==(vec2 v1, vec2 v2) { return v1.x == v2.x && v1.y == v2.y; } public static bool operator !=(vec2 v1, vec2 v2) { return !(v1 == v2); } public static vec2 scale(vec2 vector, double magnitude) { if (magnitude < 0) { throw new ArgumentOutOfRangeException("magnitude", magnitude, NEGATIVE_magnitude); } if (vector == new vec2(0, 0)) { throw new ArgumentException(ORIGIN_VECTOR_magnitude, "vector"); } return vector * (magnitude / vector.magnitude); } public vec2 scale(double magnitude) { return vec2.scale(this, magnitude); } public static double dotProduct(vec2 v1, vec2 v2) { return v1.x * v2.x + v1.y * v2.y; } public double dotProduct(vec2 other) { return dotProduct(this, other); } public static vec2 normalize(vec2 v1) { if (double.IsInfinity(v1.magnitude)) { v1 = normalizeSpecialCasesOrOrigional(v1); if (v1.isNaN()) { throw new normalizeVectorException(NORMALIZE_Inf); } } if (v1.magnitude == 0) { throw new normalizeVectorException(NORMALIZE_0); } if (v1.isNaN()) { throw new normalizeVectorException(NORMALIZE_NaN); } return normalizeOrNaN(v1); } public static vec2 normalizeOrDefault(vec2 v1) { v1 = normalizeSpecialCasesOrOrigional(v1); if (v1.magnitude == 0) { return origin; } if (v1.isNaN()) { return NaN; } return normalizeOrNaN(v1); } public vec2 normalize() { return normalize(this); } public vec2 normalizeOrDefault() { return normalizeOrDefault(this); } private static vec2 normalizeOrNaN(vec2 v1) { double inverse = 1 / v1.magnitude; return new vec2( v1.x * inverse, v1.y * inverse); } private static vec2 normalizeSpecialCasesOrOrigional(vec2 v1) { if (double.IsInfinity(v1.magnitude)) { var x = v1.x == 0 ? 0 : v1.x == -0 ? -0 : double.IsPositiveInfinity(v1.x) ? 1 : double.IsNegativeInfinity(v1.x) ? -1 : double.NaN; var y = v1.y == 0 ? 0 : v1.y == -0 ? -0 : double.IsPositiveInfinity(v1.y) ? 1 : double.IsNegativeInfinity(v1.y) ? -1 : double.NaN; return new vec2(x, y); } return v1; } public static vec2 interpolate(vec2 v1, vec2 v2, double control, bool allowExtrapolation) { if (!allowExtrapolation && (control > 1 || control < 0)) { throw new ArgumentOutOfRangeException( "control", control, INTERPOLATION_RANGE + "\n" + ARGUMENT_VALUE + control); } return new vec2( v1.x * (1 - control) + v2.x * control, v1.y * (1 - control) + v2.y * control); } public static vec2 interpolate(vec2 v1, vec2 v2, double control) { return interpolate(v1, v2, control, false); } public vec2 interpolate(vec2 other, double control) { return interpolate(this, other, control); } public vec2 interpolate(vec2 other, double control, bool allowExtrapolation) { return interpolate(this, other, control); } public static double distance(vec2 v1, vec2 v2) { return Math.Sqrt( (v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y)); } public double distance(vec2 other) { return distance(this, other); } public static double angle(vec2 v1, vec2 v2) { if (v1 == v2) { return 0; } return Math.Acos( Math.Min(1.0f, normalizeOrDefault(v1).dotProduct(normalizeOrDefault(v2)))); } public double angle(vec2 other) { return angle(this, other); } public static vec2 max(vec2 v1, vec2 v2) { return v1 >= v2 ? v1 : v2; } public vec2 max(vec2 other) { return max(this, other); } public static vec2 min(vec2 v1, vec2 v2) { return v1 <= v2 ? v1 : v2; } public vec2 min(vec2 other) { return min(this, other); } public static vec2 rotate(vec2 v1, double rad) { double x = (v1.x * Math.Cos(rad)) - (v1.y * Math.Sin(rad)); double y = (v1.x * Math.Sin(rad)) + (v1.y * Math.Cos(rad)); return new vec2(x, y); } public vec2 rotate(double rad) { return rotate(this, rad); } public static vec2 rotate(vec2 v1, double xOff, double yOff, double rad) { double x = (v1.x * Math.Cos(rad)) - (v1.y * Math.Sin(rad)) + (xOff * (1 - Math.Cos(rad)) + yOff * Math.Sin(rad)); double y = (v1.x * Math.Sin(rad)) + (v1.y * Math.Cos(rad)) + (yOff * (1 - Math.Cos(rad)) - xOff * Math.Sin(rad)); return new vec2(x, y); } public vec2 rotate(double xOff, double yOff, double rad) { return rotate(this, xOff, yOff, rad); } public static vec2 projection(vec2 v1, vec2 v2) { return new vec2(v2 * (v1.dotProduct(v2) / Math.Pow(v2.magnitude, 2))); } public vec2 projection(vec2 direction) { return projection(this, direction); } public static vec2 rejection(vec2 v1, vec2 v2) { return v1 - v1.projection(v2); } public vec2 rejection(vec2 direction) { return rejection(this, direction); } public vec2 reflection(vec2 reflector) { this = vec2.reflection(this, reflector); return this; } public static vec2 reflection(vec2 v1, vec2 v2) { if (Math.Abs(Math.Abs(v1.angle(v2)) - Math.PI / 2) < Double.Epsilon) { return -v1; } vec2 retval = new vec2(2 * v1.projection(v2) - v1); return retval.scale(v1.magnitude); } public static Double abs(vec2 v1) { return v1.magnitude; } public double abs() { return magnitude; } public static double sumComponents(vec2 v1) { return v1.x + v1.y; } public double sumComponents() { return sumComponents(this); } public static double sumComponentSqrs(vec2 v1) { vec2 v2 = sqrComponents(v1); return v2.sumComponents(); } public double sumComponentSqrs() { return sumComponentSqrs(this); } public static vec2 powComponents(vec2 v1, double power) { return new vec2( Math.Pow(v1.x, power), Math.Pow(v1.y, power)); } public vec2 powComponents(double power) { return powComponents(this, power); } public static vec2 sqrtComponents(vec2 v1) { return new vec2( Math.Sqrt(v1.x), Math.Sqrt(v1.y)); } public vec2 sqrtComponents() { return sqrtComponents(this); } public static vec2 sqrComponents(vec2 v1) { return new vec2( v1.x * v1.x, v1.y * v1.y); } public vec2 sqrComponents() { return sqrComponents(this); } public static vec2 round(vec2 v1) { return new vec2(Math.Round(v1.x), Math.Round(v1.y)); } public static vec2 round(vec2 v1, int digits) { return new vec2(Math.Round(v1.x, digits), Math.Round(v1.y, digits)); } public static vec2 round(vec2 v1, MidpointRounding mode) { return new vec2(Math.Round(v1.x, mode), Math.Round(v1.y, mode)); } public static vec2 round(vec2 v1, int digits, MidpointRounding mode) { return new vec2(Math.Round(v1.x, digits, mode), Math.Round(v1.y, digits, mode)); } public vec2 round() { return new vec2(Math.Round(x), Math.Round(y)); } public vec2 round(int digits) { return new vec2(Math.Round(x, digits), Math.Round(y, digits)); } public vec2 round(MidpointRounding mode) { return new vec2(Math.Round(x, mode), Math.Round(y, mode)); } public vec2 round(int digits, MidpointRounding mode) { return new vec2(Math.Round(x, digits, mode), Math.Round(y, digits, mode)); } public override string ToString() { return ToString(null, null); } public string ToVerbString() { string output = null; if (isUnitVector()) { output += UNIT_VECTOR; } else { output += POSITIONAL_VECTOR; } output += string.Format("( x={0}, y={1} )", x, y); output += magnitude + magnitude; return output; } public string ToString(string format, IFormatProvider formatProvider) { if (format == null || format == "") { return string.Format("({0}, {1})", x, y); } char firstChar = format[0]; string remainder = null; if (format.Length > 1) { remainder = format.Substring(1); } switch (firstChar) { case 'x': return x.ToString(remainder, formatProvider); case 'y': return y.ToString(remainder, formatProvider); default: return String.Format( "({0}, {1})", x.ToString(format, formatProvider), y.ToString(format, formatProvider)); } } public override int GetHashCode() { unchecked { var hashCode = x.GetHashCode(); hashCode = (hashCode * 397) ^ y.GetHashCode(); return hashCode; } } public override bool Equals(object other) { if (other is vec2) { return other.Equals(this); } else { return false; } } public bool Equals(object other, double tolerance) { if (other is vec2) { return Equals((vec2)other, tolerance); } return false; } public bool Equals(vec2 other) { return x.Equals(other.x) && y.Equals(other.y); } public bool Equals(vec2 other, double tolerance) { return x.almostEqualsWithAbsTolerance(other.x, tolerance) && y.almostEqualsWithAbsTolerance(other.y, tolerance); } public int CompareTo(vec2 other) { if (this < other) { return -1; } if (this > other) { return 1; } return 0; } public int CompareTo(object other) { if (other is vec2) { return CompareTo((vec2)other); } throw new ArgumentException( NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(), "other"); } public int CompareTo(vec2 other, double tolerance) { var bothInfinite = double.IsInfinity(sumComponentSqrs()) && double.IsInfinity(other.sumComponentSqrs()); if (Equals(other, tolerance) || bothInfinite) { return 0; } if (this < other) { return -1; } return 1; } public int CompareTo(object other, double tolerance) { if (other is vec2) { return CompareTo((vec2)other, tolerance); } throw new ArgumentException( NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + other.GetType().ToString(), "other"); } public static bool isUnitVector(vec2 v1, double tolerance) { return v1.magnitude.almostEqualsWithAbsTolerance(1, tolerance); } public static bool isUnitVector(vec2 v1) { return v1.magnitude == 1; } public bool isUnitVector() { return isUnitVector(this); } public bool isUnitVector(double tolerance) { return isUnitVector(this, tolerance); } public static bool isNaN(vec2 v1) { return double.IsNaN(v1.x) || double.IsNaN(v1.y); } public bool isNaN() { return isNaN(this); } public static readonly vec2 origin = new vec2(0, 0); public static readonly vec2 right = new vec2(1, 0); public static readonly vec2 left = new vec2(-1, 0); public static readonly vec2 down = new vec2(0, 1); public static readonly vec2 up = new vec2(0, -1); private const string NORMALIZE_NaN = "Cannot normalize a vector when it's magnitude is NaN"; private const string NORMALIZE_0 = "Cannot normalize a vector when it's magnitude is zero"; private const string NORMALIZE_Inf = "Cannot normalize a vector when it's magnitude is infinite except under special conditions"; private const string TWO_COMPONENTS = "Array must contain exactly two components (x, y)"; private const string INTERPOLATION_RANGE = "Control parameter must be a value between 0 & 1"; private const string NON_VECTOR_COMPARISON = "Cannot compare a vec2 to a non-vec2"; private const string ARGUMENT_TYPE = "The argument provided is a type of "; private const string ARGUMENT_VALUE = "The argument provided has a value of "; private const string ARGUMENT_LENGTH = "The argument provided has a length of "; private const string NEGATIVE_magnitude = "The magnitude of a vec2 must be a positive value, (i.e. greater than 0)"; private const string ORIGIN_VECTOR_magnitude = "Cannot change the magnitude of vec2(0, 0)"; private const string UNIT_VECTOR = "Unit vector composing of "; private const string POSITIONAL_VECTOR = "Positional vector composing of "; private const string MAGNITUDE = " of magnitude "; public static readonly vec2 minValue = new vec2(Double.MinValue, Double.MinValue); public static readonly vec2 maxValue = new vec2(Double.MaxValue, Double.MaxValue); public static readonly vec2 epsilon = new vec2(Double.Epsilon, Double.Epsilon); public static readonly vec2 zero = origin; public static readonly vec2 NaN = new vec2(double.NaN, double.NaN); } }