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); }
}
}