GodotComponentTest/HexGrid.cs

294 lines
9.5 KiB
C#
Raw Normal View History

using System.Collections.Generic;
using System.Linq;
2022-12-02 21:09:40 +01:00
using Godot;
using Priority_Queue;
2023-08-13 21:18:46 +02:00
using AxialCoordDirectionPair = System.Tuple<Godot.Vector2, Godot.Vector3>;
2022-12-02 21:09:40 +01:00
2023-11-18 22:32:57 +01:00
public class HexGrid : Resource {
private readonly Vector2 _baseHexSize = new(1, Mathf.Sqrt(3) / 2);
private Rect2 _boundsAxialCoords = new(-Vector2.Inf, Vector2.Inf);
private Rect2 _boundsOffsetCoords = new(-Vector2.Inf, Vector2.Inf);
2023-08-13 21:18:46 +02:00
private Vector2 _hexScale = new(1, 1);
private Vector2 _hexSize = new(1, Mathf.Sqrt(3) / 2);
2023-08-13 21:18:46 +02:00
private Transform2D _hexTransform;
private Transform2D _hexTransformInv;
private HexCell _maxCoords = new();
private HexCell _minCoords = new();
2023-08-13 21:18:46 +02:00
public Dictionary<(Vector2, Vector3), float> Barriers = new();
public int FindPathCheckedCellCount;
public Dictionary<Vector2, float> Obstacles = new();
public float PathCostDefault = 1;
2022-12-02 21:09:40 +01:00
2023-11-18 22:32:57 +01:00
public HexGrid() {
HexScale = new Vector2(1, 1);
2022-12-02 21:09:40 +01:00
}
public Vector2 HexSize => _hexSize;
2023-11-18 22:32:57 +01:00
public Vector2 HexScale {
get => _hexScale;
2023-11-18 22:32:57 +01:00
set {
2022-12-02 21:09:40 +01:00
_hexScale = value;
_hexSize = _baseHexSize * _hexScale;
_hexTransform = new Transform2D(
new Vector2(_hexSize.x * 3 / 4, -_hexSize.y / 2),
2022-12-02 21:09:40 +01:00
new Vector2(0, -_hexSize.y),
new Vector2(0, 0)
);
2022-12-02 21:09:40 +01:00
_hexTransformInv = _hexTransform.AffineInverse();
}
}
2023-11-18 22:32:57 +01:00
public Vector2 GetHexCenter(HexCell cell) {
2022-12-02 21:09:40 +01:00
return _hexTransform * cell.AxialCoords;
}
2023-11-18 22:32:57 +01:00
public Vector2 GetHexCenterFromOffset(Vector2 offsetCoord) {
HexCell cell = new HexCell();
2022-12-02 21:09:40 +01:00
cell.OffsetCoords = offsetCoord;
return GetHexCenter(cell);
}
2023-11-18 22:32:57 +01:00
public Vector3 GetHexCenterVec3FromOffset(Vector2 offsetCoord) {
HexCell cell = new HexCell();
cell.OffsetCoords = offsetCoord;
2023-11-18 22:32:57 +01:00
Vector2 hexCenter = GetHexCenter(cell);
return new Vector3(hexCenter.x, 0, hexCenter.y);
}
2023-11-18 22:32:57 +01:00
public HexCell GetHexAtOffset(Vector2 offsetCoord) {
HexCell cell = new HexCell();
2022-12-02 21:09:40 +01:00
cell.OffsetCoords = offsetCoord;
return cell;
}
2023-11-18 22:32:57 +01:00
public HexCell GetHexAt(Vector2 planeCoord) {
HexCell result = new HexCell(_hexTransformInv * planeCoord);
2022-12-02 21:09:40 +01:00
return result;
}
2023-11-18 22:32:57 +01:00
public void SetBounds(Vector2 minAxial, Vector2 maxAxial) {
SetBounds(new HexCell(minAxial), new HexCell(maxAxial));
}
2023-11-18 22:32:57 +01:00
public void SetBounds(HexCell minCell, HexCell maxCell) {
_minCoords = minCell;
_maxCoords = maxCell;
2023-08-13 21:18:46 +02:00
_boundsAxialCoords = new Rect2(_minCoords.AxialCoords,
_maxCoords.AxialCoords - _minCoords.AxialCoords + Vector2.One);
}
2023-11-18 22:32:57 +01:00
public void SetBounds(HexCell center, int size) {
Vector2 centerOffset = center.OffsetCoords;
SetBounds(GetHexAtOffset(centerOffset - Vector2.One * size / 2),
GetHexAtOffset(centerOffset + Vector2.One * size / 2));
}
2023-11-18 22:32:57 +01:00
public void SetBoundsOffset(HexCell cellSouthEast, Vector2 size) {
_boundsOffsetCoords = new Rect2(cellSouthEast.OffsetCoords, size);
_boundsAxialCoords = new Rect2(-Vector2.Inf, Vector2.Inf);
}
2023-11-18 22:32:57 +01:00
public void AddObstacle(Vector2 axialCoords, float cost = 0) {
AddObstacle(new HexCell(axialCoords), cost);
}
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
public void AddObstacle(HexCell cell, float cost = 0) {
2023-08-13 21:18:46 +02:00
Obstacles[cell.AxialCoords] = cost;
}
2023-11-18 22:32:57 +01:00
public void RemoveObstacle(HexCell cell) {
Obstacles.Remove(cell.AxialCoords);
}
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
public void AddBarrier(Vector2 axialCoords, Vector3 directionCube, float cost = 0) {
AddBarrier(new HexCell(axialCoords), directionCube, cost);
}
2023-11-18 22:32:57 +01:00
public void AddBarrier(HexCell cell, Vector3 directionCube, float cost = 0) {
Barriers.Add((cell.AxialCoords, directionCube), cost);
}
2023-11-18 22:32:57 +01:00
public void RemoveBarrier(HexCell cell, Vector3 directionCube) {
if (Barriers.ContainsKey((cell.AxialCoords, directionCube))) {
Barriers.Remove((cell.AxialCoords, directionCube));
}
}
2023-11-18 22:32:57 +01:00
public float GetHexCost(HexCell cell) {
return GetHexCost(cell.AxialCoords);
}
2023-11-18 22:32:57 +01:00
public float GetHexCost(Vector2 axialCoords) {
if (!_boundsAxialCoords.HasPoint(axialCoords)) {
return 0;
}
2023-11-18 22:32:57 +01:00
if (!_boundsOffsetCoords.HasPoint(new HexCell(axialCoords).OffsetCoords)) {
return 0;
}
float value;
return Obstacles.TryGetValue(axialCoords, out value) ? value : PathCostDefault;
}
2023-11-18 22:32:57 +01:00
public float GetMoveCost(Vector2 axialCoords, Vector3 directionCube) {
HexCell startCell = new HexCell(axialCoords);
HexCell targetCell = new HexCell(startCell.CubeCoords + directionCube);
2023-11-18 22:32:57 +01:00
float cost = GetHexCost(axialCoords);
if (cost == 0) {
return 0;
}
cost = GetHexCost(targetCell.AxialCoords);
2023-11-18 22:32:57 +01:00
if (cost == 0) {
return 0;
}
float barrierCost;
2023-11-18 22:32:57 +01:00
if (Barriers.ContainsKey((axialCoords, directionCube))) {
barrierCost = Barriers[(axialCoords, directionCube)];
2023-11-18 22:32:57 +01:00
if (barrierCost == 0) {
return 0;
}
cost += barrierCost;
}
2023-11-18 22:32:57 +01:00
if (Barriers.ContainsKey((targetCell.AxialCoords, -directionCube))) {
barrierCost = Barriers[(targetCell.AxialCoords, -directionCube)];
2023-11-18 22:32:57 +01:00
if (barrierCost == 0) {
return 0;
}
cost += barrierCost;
}
2023-08-13 21:18:46 +02:00
return cost;
}
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
public HexCell GetClosestWalkableCell(HexCell fromCell, HexCell toCell) {
if (GetHexCost(toCell) == 0) {
HexCell[] line = fromCell.LineTo(toCell);
2023-11-18 22:32:57 +01:00
foreach (int i in Enumerable.Range(1, line.Length)) {
if (GetHexCost(line[i]) == 0) {
toCell = line[i - 1];
break;
}
2023-11-18 22:32:57 +01:00
}
}
return toCell;
}
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
public List<HexCell> FindPath(HexCell startHex, HexCell goalHex) {
Vector2 goalAxialCoords = goalHex.AxialCoords;
2023-11-18 22:32:57 +01:00
SimplePriorityQueue<Vector2, float> frontier = new SimplePriorityQueue<Vector2, float>();
frontier.Enqueue(startHex.AxialCoords, 0);
2023-11-18 22:32:57 +01:00
Dictionary<Vector2, Vector2> cameFrom =
2023-08-13 21:18:46 +02:00
new Dictionary<Vector2, Vector2>();
2023-11-18 22:32:57 +01:00
Dictionary<Vector2, float> costSoFar =
2023-08-13 21:18:46 +02:00
new Dictionary<Vector2, float>();
cameFrom.Add(startHex.AxialCoords, startHex.AxialCoords);
costSoFar.Add(startHex.AxialCoords, 0);
FindPathCheckedCellCount = 0;
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
while (frontier.Any()) {
FindPathCheckedCellCount++;
2023-11-18 22:32:57 +01:00
HexCell currentHex = new HexCell(frontier.Dequeue());
Vector2 currentAxial = currentHex.AxialCoords;
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
if (currentHex == goalHex) {
break;
}
2023-11-18 22:32:57 +01:00
foreach (HexCell nextHex in currentHex.GetAllAdjacent()) {
Vector2 nextAxial = nextHex.AxialCoords;
float nextCost = GetMoveCost(currentAxial, new HexCell(nextAxial - currentHex.AxialCoords).CubeCoords);
2023-11-18 22:32:57 +01:00
if (nextHex == goalHex && GetHexCost(nextAxial) == 0) {
// Goal ist an obstacle
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
frontier.Clear();
break;
}
2023-11-18 22:32:57 +01:00
if (nextCost == 0) {
continue;
}
nextCost += costSoFar[currentHex.AxialCoords];
2023-11-18 22:32:57 +01:00
if (!costSoFar.ContainsKey(nextHex.AxialCoords) || nextCost < costSoFar[nextHex.AxialCoords]) {
costSoFar[nextHex.AxialCoords] = nextCost;
2023-11-18 22:32:57 +01:00
float priority = nextCost + nextHex.DistanceTo(goalHex);
frontier.Enqueue(nextHex.AxialCoords, priority);
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
}
}
}
// GD.Print("Checked Cell Count: " + FindPathCheckedCellCount);
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
List<HexCell> result = new List<HexCell>();
if (!cameFrom.ContainsKey(goalHex.AxialCoords)) {
GD.Print("Failed to find path from " + startHex + " to " + goalHex);
2023-08-13 21:18:46 +02:00
return result;
}
2023-11-18 22:32:57 +01:00
if (GetHexCost(goalAxialCoords) != 0) {
result.Add(goalHex);
}
2023-11-18 22:32:57 +01:00
HexCell pathHex = goalHex;
while (pathHex != startHex) {
pathHex = new HexCell(cameFrom[pathHex.AxialCoords]);
result.Insert(0, pathHex);
}
return result;
}
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
public List<HexCell> GetCellsForLine(Vector2 fromPlane, Vector2 toPlane) {
List<HexCell> result = new List<HexCell>();
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
float distance = (toPlane - fromPlane).Length();
Vector2 direction = (toPlane - fromPlane) / distance;
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
Vector2 currentPointPlane = fromPlane;
HexCell currentCell = GetHexAt(currentPointPlane);
float currentDistance = 0;
2023-08-13 21:18:46 +02:00
2023-11-18 22:32:57 +01:00
do {
2023-08-13 21:18:46 +02:00
result.Add(currentCell);
GetHexCenter(currentCell);
2023-11-18 22:32:57 +01:00
Vector2 currentPointLocal = currentPointPlane - GetHexCenter(currentCell);
2023-08-13 21:18:46 +02:00
int neighbourIndex;
float boundaryPlaneDistance;
currentCell.QueryClosestCellBoundary(currentPointLocal, direction, out neighbourIndex,
out boundaryPlaneDistance);
2023-08-13 21:18:46 +02:00
currentCell = currentCell.GetAdjacent(HexCell.NeighborDirections[neighbourIndex]);
currentDistance += boundaryPlaneDistance * 1.001f;
currentPointPlane = fromPlane + direction * boundaryPlaneDistance;
} while (currentDistance < distance);
2023-08-13 21:18:46 +02:00
result.Add(currentCell);
return result;
}
2022-12-02 21:09:40 +01:00
}