GodotComponentTest/HexCell.cs

247 lines
7.4 KiB
C#

using System;
using System.Linq;
using Godot;
using GodotComponentTest.utils;
public class HexCell : IEquatable<HexCell> {
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != 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(-Width / 4, -Height / 2);
private static readonly Vector2 CornerNE = new(Width / 4, -Height / 2);
private static readonly Vector2 CornerE = new(Width / 2, 0);
private static readonly Vector2 CornerSE = new(Width / 4, Height / 2);
private static readonly Vector2 CornerSW = new(-Width / 4, Height / 2);
private static readonly Vector2 CornerW = new(-Width / 2, 0);
private static readonly Vector2 PlaneNormalN = new(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(0, -1);
private static readonly Vector2 PlaneNormalSW = new(Mathf.Cos(Mathf.Deg2Rad(30)), -Mathf.Sin(Mathf.Deg2Rad(30)));
private static readonly Vector2 PlaneNormalNW = new(Mathf.Cos(Mathf.Deg2Rad(-30)), -Mathf.Sin(Mathf.Deg2Rad(-30)));
private static readonly Plane2D[] BoundaryPlanes = {
new(CornerNE, PlaneNormalN),
new(CornerNW, PlaneNormalNW),
new(CornerW, PlaneNormalSW),
new(CornerSW, PlaneNormalS),
new(CornerSE, PlaneNormalSE),
new(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();
result.OffsetCoords = offsetCoords;
return result;
}
private Vector3 _cubeCoords;
public Vector3 CubeCoords {
get => _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(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(
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(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();
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 = float.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 > float.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]);
}
}