using System; using System.Linq; using Godot; using GodotComponentTest.utils; public class HexCell : IEquatable { public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != this.GetType()) return false; return Equals((HexCell)obj); } public override int GetHashCode() { return _cubeCoords.GetHashCode(); } public static readonly Vector2 Size = new(1, Mathf.Sqrt(3) / 2); private const float Width = 2; private static readonly float Height = Mathf.Sqrt(3); public static readonly Vector3 DIR_N = new(0, 1, -1); public static readonly Vector3 DIR_NE = new(1, 0, -1); public static readonly Vector3 DIR_SE = new(1, -1, 0); public static readonly Vector3 DIR_S = new(0, -1, 1); public static readonly Vector3 DIR_SW = new(-1, 0, 1); public static readonly Vector3 DIR_NW = new(-1, 1, 0); public static readonly Vector3[] NeighborDirections = { DIR_N, DIR_NW, DIR_SW, DIR_S, DIR_SE, DIR_NE }; private static readonly Vector2 CornerNW = new Vector2(-Width / 4, -Height / 2); private static readonly Vector2 CornerNE = new Vector2(Width / 4, -Height / 2); private static readonly Vector2 CornerE = new Vector2(Width / 2, 0); private static readonly Vector2 CornerSE = new Vector2(Width / 4, Height / 2); private static readonly Vector2 CornerSW = new Vector2(-Width / 4, Height / 2); private static readonly Vector2 CornerW = new Vector2(-Width / 2, 0); private static readonly Vector2 PlaneNormalN = new Vector2(0, 1); private static readonly Vector2 PlaneNormalNE = -new Vector2(Mathf.Cos(Mathf.Deg2Rad(30)), -Mathf.Sin(Mathf.Deg2Rad(30))); private static readonly Vector2 PlaneNormalSE = -new Vector2(Mathf.Cos(Mathf.Deg2Rad(-30)), -Mathf.Sin(Mathf.Deg2Rad(-30))); private static readonly Vector2 PlaneNormalS = new Vector2(0, -1); private static readonly Vector2 PlaneNormalSW = new Vector2(Mathf.Cos(Mathf.Deg2Rad(30)), -Mathf.Sin(Mathf.Deg2Rad(30))); private static readonly Vector2 PlaneNormalNW = new Vector2(Mathf.Cos(Mathf.Deg2Rad(-30)), -Mathf.Sin(Mathf.Deg2Rad(-30))); private static readonly Plane2D[] BoundaryPlanes = { new Plane2D(CornerNE, PlaneNormalN), new Plane2D(CornerNW, PlaneNormalNW), new Plane2D(CornerW, PlaneNormalSW), new Plane2D(CornerSW, PlaneNormalS), new Plane2D(CornerSE, PlaneNormalSE), new Plane2D(CornerE, PlaneNormalNE), }; public HexCell() { } public HexCell(float cubeX, float cubeY, float cubeZ) { CubeCoords = RoundCoords(new Vector3(cubeX, cubeY, cubeZ)); } public virtual bool Equals(HexCell other) { if (other == null) { return false; } return CubeCoords == other.CubeCoords; } public static bool operator ==(HexCell cellA, HexCell cellB) { return Equals(cellA, cellB); } public static bool operator !=(HexCell cellA, HexCell cellB) { return !(cellA == cellB); } public HexCell(Vector3 cubeCoords) { CubeCoords = cubeCoords; } public HexCell(float axialX, float axialY) { AxialCoords = new Vector2(axialX, axialY); } public HexCell(Vector2 axialCoords) { AxialCoords = axialCoords; } public HexCell(HexCell other) { CubeCoords = other.CubeCoords; } public static HexCell FromOffsetCoords(Vector2 offsetCoords) { HexCell result = new HexCell(); result.OffsetCoords = offsetCoords; return result; } private Vector3 _cubeCoords; public Vector3 CubeCoords { get { return _cubeCoords; } set { if (Mathf.Abs(value.x + value.y + value.z) > 0.0001) { GD.Print("Warning: Invalid cube coordinates for hex (x + y + z != 0): ", value.ToString()); } _cubeCoords = RoundCoords(value); } } public Vector2 AxialCoords { get => new Vector2(CubeCoords.x, CubeCoords.y); set { CubeCoords = AxialToCubeCoords(value); } } public Vector2 OffsetCoords { get { int x = (int)CubeCoords.x; int y = (int)CubeCoords.y; int offY = y + (x - (x & 1)) / 2; return new Vector2(x, offY); } set { int x = (int)value.x; int y = (int)value.y; int cubeY = y - (x - (x & 1)) / 2; AxialCoords = new Vector2(x, cubeY); } } public Vector3 AxialToCubeCoords(Vector2 axialCoords) { return new Vector3(axialCoords.x, axialCoords.y, -axialCoords.x - axialCoords.y); } public Vector3 RoundCoords(Vector2 coords) { Vector3 cubeCoords = AxialToCubeCoords(coords); return RoundCoords(cubeCoords); } public Vector3 RoundCoords(Vector3 cubeCoords) { Vector3 rounded = new Vector3( Mathf.Round(cubeCoords.x), Mathf.Round(cubeCoords.y), Mathf.Round(cubeCoords.z)); Vector3 diffs = (rounded - cubeCoords).Abs(); if (diffs.x > diffs.y && diffs.x > diffs.z) { rounded.x = -rounded.y - rounded.z; } else if (diffs.y > diffs.z) { rounded.y = -rounded.x - rounded.z; } else { rounded.z = -rounded.x - rounded.y; } return rounded; } public HexCell GetAdjacent(Vector3 dir) { return new HexCell(this.CubeCoords + dir); } public HexCell[] GetAllAdjacent() { return new[] { GetAdjacent(DIR_NE), GetAdjacent(DIR_SE), GetAdjacent(DIR_S), GetAdjacent(DIR_SW), GetAdjacent(DIR_NW), GetAdjacent(DIR_N) }; } public int DistanceTo(HexCell target) { return (int)( Mathf.Abs(_cubeCoords.x - target.CubeCoords.x) + Mathf.Abs(_cubeCoords.y - target.CubeCoords.y) + Mathf.Abs(_cubeCoords.z - target.CubeCoords.z) ) / 2; } public HexCell[] LineTo(HexCell target) { HexCell nudgedTarget = new HexCell(); nudgedTarget.CubeCoords = target.CubeCoords + new Vector3(1.0e-6f, 2.0e-6f, -3.0e-6f); int steps = DistanceTo(target); HexCell[] path = new HexCell[steps + 1]; foreach (int dist in Enumerable.Range(0, steps)) { path[dist] = new HexCell(); path[dist].CubeCoords = CubeCoords.LinearInterpolate(nudgedTarget.CubeCoords, (float)dist / steps); } path[steps] = target; return path; } public void QueryClosestCellBoundary(Vector2 pointLocal, Vector2 dir, out int neighbourIndex, out float distance) { distance = Single.PositiveInfinity; neighbourIndex = 0; foreach (int i in Enumerable.Range(0, 6)) { if (BoundaryPlanes[i].Normal.Dot(dir) >= Plane2D.DistancePrecision) { continue; } float planeDistance = BoundaryPlanes[i].DistanceToLineSegment(pointLocal, dir); if (planeDistance > Single.NegativeInfinity && planeDistance < distance) { distance = planeDistance; neighbourIndex = i; } } } public HexCell NextCellAlongLine(Vector2 pointLocal, Vector2 dir) { int planeIndex; QueryClosestCellBoundary(pointLocal, dir, out planeIndex, out _); return GetAdjacent(NeighborDirections[planeIndex]); } }