using System; using System.Collections.Generic; using System.Xml.Serialization; namespace TransportGame.Primitives { /// /// 2D vector /// public struct Vector2 { #region Constant vectors /// /// Zero vector /// public static readonly Vector2 Zero = new Vector2(0, 0); /// /// Unit vector /// public static readonly Vector2 Unit = new Vector2(1, 0); #endregion #region Properties /// /// Gets the X component /// [XmlAttribute("x")] public float X { get; set; } /// /// Gets the Y component /// [XmlAttribute("y")] public float Y { get; set; } #endregion #region Constructor /// /// Initializes a vector2 /// /// X component /// Y component public Vector2(float x, float y) : this() { X = x; Y = y; } /// /// Gets the vector corresponding with specified angle (in radians) /// /// Radians /// Vector public static Vector2 FromRadians(float rads) { return new Vector2((float)Math.Cos(rads), (float)Math.Sin(rads)); } /// /// Gets the vector corresponding with specified angle (in degrees) /// /// Degrees /// Vector public static Vector2 FromDegrees(float degs) { float rads = (degs * (float)Math.PI / 180f); return FromRadians(rads); } #endregion #region Properties (length, normalized) /// /// Gets the length of the vector /// public float Length { get { return (float)Math.Sqrt(LengthSq); } } /// /// Gets the length of the vector squared /// public float LengthSq { get { return X * X + Y * Y; } } /// /// Gets the normalized vector /// /// Normalized vector public Vector2 Normalized { get { float len = Length; return new Vector2(X / len, Y / len); } } /// /// Gets the normalized vector raised to second power /// /// /// This is less computationally expensive (no need to calculate square root). /// /// Normalized vector public Vector2 NormalizedSq { get { float len2 = LengthSq; return new Vector2(X * X / len2, Y * Y / len2); } } #endregion #region Operations /// /// Rotates vector by given number of radians /// /// /// public Vector2 Rotate(float radians) { float sin = (float)Math.Sin(radians); float cos = (float)Math.Cos(radians); return new Vector2(X * cos - Y * sin, X * sin + Y * cos); } /// /// Rotates vector by given number of degrees /// /// /// public Vector2 RotateDeg(float degrees) { return Rotate(degrees * (float)Math.PI / 180f); } /// /// Sum operator /// /// First vector /// Second vector /// Result of addition public static Vector2 operator +(Vector2 a, Vector2 b) { return new Vector2(a.X + b.X, a.Y + b.Y); } /// /// Subtract operator /// /// First vector /// Second vector /// Result of subtraction public static Vector2 operator -(Vector2 a, Vector2 b) { return new Vector2(a.X - b.X, a.Y - b.Y); } /// /// Negation operator /// /// Vector /// Negated vector public static Vector2 operator -(Vector2 a) { return new Vector2(-a.X, -a.Y); } /// /// Multiply by constant /// /// Vector /// Constant /// Result public static Vector2 operator *(Vector2 a, float c) { return new Vector2(a.X * c, a.Y * c); } /// /// Multiply by constant /// /// Constant /// Vector /// Result public static Vector2 operator *(float c, Vector2 a) { return new Vector2(a.X * c, a.Y * c); } /// /// Divide by constant /// /// Vector /// Constant /// Result public static Vector2 operator /(Vector2 a, float c) { return new Vector2(a.X / c, a.Y / c); } /// /// Equality operator /// /// First vector /// Second vector /// True if vectors are equal public static bool operator ==(Vector2 a, Vector2 b) { return a.X == b.X && a.Y == b.Y; } /// /// Inequality operator /// /// First vector /// Second vector /// True if vectors are not equal public static bool operator !=(Vector2 a, Vector2 b) { return a.X != b.X || a.Y != b.Y; } /// /// Calculates dot product of two vectors /// /// First vector /// Second vector /// Dot product public static float Dot(Vector2 a, Vector2 b) { return a.X * b.X + a.Y * b.Y; } /// /// Returns the magnitude of the cross product between the two vectors (z considered 0) /// /// First vector /// Second vector /// Magnitude of cross product public static float Cross(Vector2 a, Vector2 b) { return (a.X * b.Y) - (a.Y * b.X); } /// /// Tests if two vectors are colliniar /// /// a /// b /// True if vectors are colliniar public static bool AreColliniar(Vector2 a, Vector2 b) { return Math.Abs(Cross(a, b)) < 1e-12; } #endregion #region Object overrides /// /// Gets string representation of vector2 /// /// public override string ToString() { return String.Format("({0}, {1})", X, Y); } /// /// Tests if two vectors are equal. /// /// /// public override bool Equals(object obj) { if (obj is Vector2) { Vector2 other = (Vector2)obj; return X == other.X && Y == other.Y; } return false; } /// /// Gets hash code of vector2 /// /// public override int GetHashCode() { return X.GetHashCode() * 29 + Y.GetHashCode(); } #endregion #region Comparers private class LengthComparerImpl : IComparer { public int Compare(Vector2 a, Vector2 b) { if (a.LengthSq > b.LengthSq) return 1; if (a.LengthSq < b.LengthSq) return -1; return 0; } } private class TrigonometricComparerImpl : IComparer { private int Quad(Vector2 v) { if (v.Y >= 0) { if (v.X >= 0) return 0; return 1; } else { if (v.X < 0) return 2; return 3; } } public int Compare(Vector2 a, Vector2 b) { // If vectors are in different quadrants, we can use quadrant number int qa = Quad(a), qb = Quad(b); if (qa != qb) { return qa - qb; } // In same quadrant. Compute cross product which gives us sin(ab)*len(a)*len(b) // Vectors are in same quadrant, so angle should be less than 90deg float cross = Cross(a, b); if (cross < 0) // Angle > 180 degrees => a > b return 1; if (cross > 0) // Angle < 180 degrees => a < b return -1; // Points are on the same line. Use distance if (a.LengthSq > b.LengthSq) return 1; else if (a.LengthSq < b.LengthSq) return -1; // Points are equal return 0; } } /// /// Length comparer - compares vectors by length /// public static readonly IComparer LengthComparer = new LengthComparerImpl(); /// /// Trigonometric comparer - compares vectors by angle /// public static readonly IComparer TrigonometricComparer = new TrigonometricComparerImpl(); #endregion } }