using Godot; namespace WorldSim; /// /// The world is a torus — a boundless globe with no edges or corners. Walk /// off the right and you reappear on the left; off the bottom, the top. Every /// position comparison in the sim must account for the seam: the shortest path /// between two points may cross an edge. All distance/direction/movement math /// routes through here so the whole world agrees the map wraps. /// /// The wrap size is World.GridSize (positions live in world units, same space /// as node cells and NPC positions). /// public static class Toroidal { public static float Size => World.GridSize; /// Wrap a scalar coordinate into [0, Size). public static float WrapCoord(float v) { float s = Size; v %= s; return v < 0f ? v + s : v; } /// Wrap a position so it lives inside the world. public static Vector2 Wrap(Vector2 p) => new(WrapCoord(p.X), WrapCoord(p.Y)); /// /// The shortest displacement FROM a TO b across the torus — each axis takes /// whichever way (direct or across the seam) is nearer. Result components /// are in [-Size/2, Size/2]. Use this for direction and distance. /// public static Vector2 Delta(Vector2 a, Vector2 b) { float s = Size, half = s * 0.5f; float dx = b.X - a.X, dy = b.Y - a.Y; if (dx > half) dx -= s; else if (dx < -half) dx += s; if (dy > half) dy -= s; else if (dy < -half) dy += s; return new Vector2(dx, dy); } /// Shortest distance between two points on the torus. public static float Distance(Vector2 a, Vector2 b) => Delta(a, b).Length(); /// Shortest squared distance — for cheap radius comparisons. public static float DistanceSquared(Vector2 a, Vector2 b) => Delta(a, b).LengthSquared(); /// Move `from` toward `target` by `step`, taking the seam-shortest /// path and wrapping the result back into the world. public static Vector2 MoveToward(Vector2 from, Vector2 target, float step) { Vector2 d = Delta(from, target); float len = d.Length(); if (len <= step || len < 0.0001f) return Wrap(target); return Wrap(from + d / len * step); } }