using System; namespace Luneraria.Core { /// /// Ground vector (x, z) with 32-bit components and plain single-precision arithmetic, like /// Godot's Vector2 (tools/godot-golden/float_probe.gd: 4000 of 4000 samples identical). /// Scalars of the rules are doubles; they are rounded to float where they meet a vector. /// Matching Godot's arithmetic keeps the golden comparisons tight; the product requirement /// is determinism on one machine, not identical bits across platforms (docs/port/U1-RULES.md). /// [Serializable] public struct Vec2 : IEquatable { public float X; public float Y; public static readonly Vec2 Zero = new Vec2(0f, 0f); public static readonly Vec2 Right = new Vec2(1f, 0f); public static readonly Vec2 Left = new Vec2(-1f, 0f); public Vec2(float x, float y) { X = x; Y = y; } /// From doubles: each component rounded to float. public Vec2(double x, double y) { X = (float)x; Y = (float)y; } public static Vec2 operator +(Vec2 a, Vec2 b) { return new Vec2(a.X + b.X, a.Y + b.Y); } public static Vec2 operator -(Vec2 a, Vec2 b) { return new Vec2(a.X - b.X, a.Y - b.Y); } public static Vec2 operator -(Vec2 a) { return new Vec2(-a.X, -a.Y); } public static Vec2 operator *(Vec2 a, double s) { float f = (float)s; return new Vec2(a.X * f, a.Y * f); } public static Vec2 operator *(double s, Vec2 a) { float f = (float)s; return new Vec2(f * a.X, f * a.Y); } public static Vec2 operator /(Vec2 a, double s) { float f = (float)s; return new Vec2(a.X / f, a.Y / f); } public static bool operator ==(Vec2 a, Vec2 b) { return a.X == b.X && a.Y == b.Y; } public static bool operator !=(Vec2 a, Vec2 b) { return !(a == b); } public float Length() { return MathF.Sqrt(X * X + Y * Y); } public float LengthSquared() { return X * X + Y * Y; } public float DistanceTo(Vec2 p) { float dx = X - p.X; float dy = Y - p.Y; return MathF.Sqrt(dx * dx + dy * dy); } public float DistanceSquaredTo(Vec2 p) { float dx = X - p.X; float dy = Y - p.Y; return dx * dx + dy * dy; } public float Dot(Vec2 p) { return X * p.X + Y * p.Y; } public Vec2 Normalized() { float l = X * X + Y * Y; if (l != 0f) { l = MathF.Sqrt(l); return new Vec2(X / l, Y / l); } return this; } /// from + (to - from) * weight per component, weight rounded to float. public Vec2 Lerp(Vec2 to, double weight) { float w = (float)weight; return new Vec2(X + (to.X - X) * w, Y + (to.Y - Y) * w); } public Vec2 LimitLength(double length) { float limit = (float)length; float l = Length(); Vec2 v = this; if (l > 0f && limit < l) { v.X /= l; v.Y /= l; v.X *= limit; v.Y *= limit; } return v; } public Vec2 Rotated(double angle) { float a = (float)angle; float sine = MathF.Sin(a); float cosi = MathF.Cos(a); return new Vec2(X * cosi - Y * sine, X * sine + Y * cosi); } public bool Equals(Vec2 other) { return X.Equals(other.X) && Y.Equals(other.Y); } public override bool Equals(object obj) { return obj is Vec2 other && Equals(other); } public override int GetHashCode() { return (X.GetHashCode() * 397) ^ Y.GetHashCode(); } public override string ToString() { return "(" + X.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ", " + Y.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ")"; } } /// Integer grid cell for the spatial buckets of a combat step. public struct Vec2I : IEquatable { public int X; public int Y; public Vec2I(int x, int y) { X = x; Y = y; } public bool Equals(Vec2I other) { return X == other.X && Y == other.Y; } public override bool Equals(object obj) { return obj is Vec2I other && Equals(other); } public override int GetHashCode() { return (X * 73856093) ^ (Y * 19349663); } } }