2022-12-02 21:09:40 +01:00
|
|
|
using System;
|
2023-07-15 21:50:38 +02:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
2022-12-02 21:09:40 +01:00
|
|
|
using Godot;
|
2023-07-15 21:50:38 +02:00
|
|
|
using Godot.Collections;
|
|
|
|
using Priority_Queue;
|
|
|
|
using Array = Godot.Collections.Array;
|
|
|
|
using AxialCoordDirectionPair = System.Tuple<Godot.Vector2, Godot.Vector3>;
|
2022-12-02 21:09:40 +01:00
|
|
|
|
|
|
|
public class HexGrid : Resource
|
|
|
|
{
|
2023-07-15 21:50:38 +02:00
|
|
|
|
2022-12-02 21:09:40 +01:00
|
|
|
private Vector2 _baseHexSize = new Vector2(1, Mathf.Sqrt(3) / 2);
|
|
|
|
private Vector2 _hexSize = new Vector2(1, Mathf.Sqrt(3) / 2);
|
|
|
|
private Vector2 _hexScale = new Vector2(1, 1);
|
|
|
|
private Godot.Transform2D _hexTransform;
|
|
|
|
private Godot.Transform2D _hexTransformInv;
|
2023-07-15 21:50:38 +02:00
|
|
|
private HexCell _minCoords = new HexCell();
|
|
|
|
private HexCell _maxCoords = new HexCell();
|
|
|
|
private Rect2 _bounds = new Rect2();
|
|
|
|
|
|
|
|
public System.Collections.Generic.Dictionary<Vector2, float> Obstacles = new System.Collections.Generic.Dictionary<Vector2, float>();
|
|
|
|
|
|
|
|
public System.Collections.Generic.Dictionary<(Vector2, Vector3), float> Barriers =
|
|
|
|
new System.Collections.Generic.Dictionary<(Vector2, Vector3), float>();
|
|
|
|
|
|
|
|
|
|
|
|
public float PathCostDefault = 1;
|
2023-07-17 22:58:15 +02:00
|
|
|
public int FindPathCheckedCells = 0;
|
2022-12-02 21:09:40 +01:00
|
|
|
|
|
|
|
public Vector2 HexSize
|
|
|
|
{
|
|
|
|
get { return _hexSize; }
|
|
|
|
}
|
2022-12-28 16:22:53 +01:00
|
|
|
|
2022-12-02 21:09:40 +01:00
|
|
|
public Vector2 HexScale
|
|
|
|
{
|
2022-12-28 16:22:53 +01:00
|
|
|
get { return _hexScale; }
|
2022-12-02 21:09:40 +01:00
|
|
|
set
|
|
|
|
{
|
|
|
|
_hexScale = value;
|
|
|
|
_hexSize = _baseHexSize * _hexScale;
|
|
|
|
_hexTransform = new Transform2D(
|
2022-12-28 16:22:53 +01:00
|
|
|
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-28 16:22:53 +01:00
|
|
|
|
2022-12-02 21:09:40 +01:00
|
|
|
_hexTransformInv = _hexTransform.AffineInverse();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public HexGrid()
|
|
|
|
{
|
|
|
|
HexScale = new Vector2(1, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Vector2 GetHexCenter(HexCell cell)
|
|
|
|
{
|
|
|
|
return _hexTransform * cell.AxialCoords;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Vector2 GetHexCenterFromOffset(Vector2 offsetCoord)
|
|
|
|
{
|
|
|
|
HexCell cell = new HexCell();
|
|
|
|
cell.OffsetCoords = offsetCoord;
|
|
|
|
return GetHexCenter(cell);
|
|
|
|
}
|
|
|
|
|
|
|
|
public HexCell GetHexAtOffset(Vector2 offsetCoord)
|
|
|
|
{
|
|
|
|
HexCell cell = new HexCell();
|
|
|
|
cell.OffsetCoords = offsetCoord;
|
|
|
|
return cell;
|
|
|
|
}
|
|
|
|
|
|
|
|
public HexCell GetHexAt(Vector2 planeCoord)
|
|
|
|
{
|
|
|
|
HexCell result = new HexCell(_hexTransformInv * planeCoord);
|
|
|
|
return result;
|
|
|
|
}
|
2023-07-15 21:50:38 +02:00
|
|
|
|
|
|
|
public void SetBounds(Vector2 minAxial, Vector2 maxAxial)
|
|
|
|
{
|
|
|
|
SetBounds(new HexCell(minAxial), new HexCell(maxAxial));
|
|
|
|
}
|
|
|
|
|
|
|
|
public void SetBounds(HexCell minCell, HexCell maxCell)
|
|
|
|
{
|
|
|
|
_minCoords = minCell;
|
|
|
|
_maxCoords = maxCell;
|
2023-07-17 22:58:15 +02:00
|
|
|
_bounds = new Rect2(_minCoords.OffsetCoords, (_maxCoords.OffsetCoords - _minCoords.OffsetCoords) + Vector2.One);
|
2023-07-15 21:50:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public void AddObstacle(Vector2 axialCoords, float cost = 0)
|
|
|
|
{
|
|
|
|
AddObstacle(new HexCell(axialCoords), cost);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void AddObstacle(HexCell cell, float cost = 0)
|
|
|
|
{
|
|
|
|
if (Obstacles.ContainsKey(cell.AxialCoords))
|
|
|
|
{
|
|
|
|
Obstacles[cell.AxialCoords] = cost;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Obstacles.Add(cell.AxialCoords, cost);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void RemoveObstacle(HexCell cell)
|
|
|
|
{
|
|
|
|
Obstacles.Remove(cell.AxialCoords);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddBarrier(Vector2 axialCoords, Vector3 directionCube, float cost = 0)
|
|
|
|
{
|
|
|
|
AddBarrier(new HexCell(axialCoords), directionCube, cost);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void AddBarrier(HexCell cell, Vector3 directionCube, float cost = 0)
|
|
|
|
{
|
|
|
|
AxialCoordDirectionPair barrierKey = new AxialCoordDirectionPair(cell.AxialCoords, directionCube);
|
|
|
|
Barriers.Add((cell.AxialCoords, directionCube), cost);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void RemoveBarrier(HexCell cell, Vector3 directionCube)
|
|
|
|
{
|
|
|
|
AxialCoordDirectionPair barrierKey = new AxialCoordDirectionPair(cell.AxialCoords, directionCube);
|
|
|
|
if (Barriers.ContainsKey((cell.AxialCoords, directionCube)))
|
|
|
|
{
|
|
|
|
Barriers.Remove((cell.AxialCoords, directionCube));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public float GetHexCost(HexCell cell)
|
|
|
|
{
|
|
|
|
return GetHexCost(cell.AxialCoords);
|
|
|
|
}
|
|
|
|
|
|
|
|
public float GetHexCost(Vector2 axialCoords)
|
|
|
|
{
|
2023-07-17 22:58:15 +02:00
|
|
|
HexCell cell = new HexCell(axialCoords);
|
|
|
|
if (!_bounds.HasPoint(cell.OffsetCoords))
|
2023-07-15 21:50:38 +02:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
float value;
|
|
|
|
return Obstacles.TryGetValue(axialCoords, out value) ? value : PathCostDefault;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float GetMoveCost(Vector2 axialCoords, Vector3 directionCube)
|
|
|
|
{
|
|
|
|
HexCell startCell = new HexCell(axialCoords);
|
|
|
|
HexCell targetCell = new HexCell(startCell.CubeCoords + directionCube);
|
|
|
|
|
|
|
|
float cost = GetHexCost(axialCoords);
|
|
|
|
if (cost == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
cost = GetHexCost(targetCell.AxialCoords);
|
|
|
|
if (cost == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
float barrierCost;
|
|
|
|
AxialCoordDirectionPair barrierKey = new AxialCoordDirectionPair(axialCoords, directionCube);
|
|
|
|
if (Barriers.ContainsKey((axialCoords, directionCube)))
|
|
|
|
{
|
|
|
|
barrierCost = Barriers[(axialCoords, directionCube)];
|
|
|
|
if (barrierCost == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
cost += barrierCost;
|
|
|
|
}
|
|
|
|
|
|
|
|
AxialCoordDirectionPair reversedBarrierKey = new AxialCoordDirectionPair(targetCell.AxialCoords, -directionCube);
|
|
|
|
if (Barriers.ContainsKey((targetCell.AxialCoords, -directionCube)))
|
|
|
|
{
|
|
|
|
barrierCost = Barriers[(targetCell.AxialCoords, -directionCube)];
|
|
|
|
if (barrierCost == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
cost += barrierCost;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cost;
|
|
|
|
}
|
|
|
|
|
2023-07-28 22:14:28 +02:00
|
|
|
|
|
|
|
public HexCell GetClosestWalkableCell(HexCell fromCell, HexCell toCell)
|
|
|
|
{
|
|
|
|
if (GetHexCost(toCell) == 0)
|
|
|
|
{
|
|
|
|
HexCell[] line = fromCell.LineTo(toCell);
|
|
|
|
|
|
|
|
foreach (int i in Enumerable.Range(1, line.Length))
|
|
|
|
{
|
|
|
|
if (GetHexCost(line[i]) == 0)
|
|
|
|
{
|
|
|
|
toCell = line[i - 1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return toCell;
|
|
|
|
}
|
|
|
|
|
2023-07-15 21:50:38 +02:00
|
|
|
public List<HexCell> FindPath(HexCell startHex, HexCell goalHex)
|
|
|
|
{
|
|
|
|
Vector2 startAxialCoords = startHex.AxialCoords;
|
|
|
|
Vector2 goalAxialCoords = goalHex.AxialCoords;
|
|
|
|
|
|
|
|
SimplePriorityQueue<Vector2, float> frontier = new SimplePriorityQueue<Vector2, float>();
|
|
|
|
frontier.Enqueue(startHex.AxialCoords, 0);
|
|
|
|
System.Collections.Generic.Dictionary<Vector2, Vector2> cameFrom = new System.Collections.Generic.Dictionary<Vector2, Vector2>();
|
|
|
|
System.Collections.Generic.Dictionary<Vector2, float> costSoFar = new System.Collections.Generic.Dictionary<Vector2, float>();
|
|
|
|
|
|
|
|
cameFrom.Add(startHex.AxialCoords, startHex.AxialCoords);
|
|
|
|
costSoFar.Add(startHex.AxialCoords, 0);
|
|
|
|
|
2023-07-17 22:58:15 +02:00
|
|
|
FindPathCheckedCells = 0;
|
|
|
|
|
2023-07-15 21:50:38 +02:00
|
|
|
while (frontier.Any())
|
|
|
|
{
|
2023-07-17 22:58:15 +02:00
|
|
|
FindPathCheckedCells++;
|
2023-07-15 21:50:38 +02:00
|
|
|
HexCell currentHex = new HexCell(frontier.Dequeue());
|
|
|
|
Vector2 currentAxial = currentHex.AxialCoords;
|
|
|
|
if (currentHex == goalHex)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (HexCell nextHex in currentHex.GetAllAdjacent())
|
|
|
|
{
|
|
|
|
Vector2 nextAxial = nextHex.AxialCoords;
|
|
|
|
float nextCost = GetMoveCost(currentAxial, new HexCell(nextAxial - currentHex.AxialCoords).CubeCoords);
|
|
|
|
|
|
|
|
if ((nextHex == goalHex) && (GetHexCost(nextAxial) == 0))
|
|
|
|
{
|
|
|
|
// Goal ist an obstacle
|
|
|
|
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
|
|
|
|
frontier.Clear();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextCost == 0)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
nextCost += costSoFar[currentHex.AxialCoords];
|
|
|
|
if (!costSoFar.ContainsKey(nextHex.AxialCoords) || nextCost < costSoFar[nextHex.AxialCoords])
|
|
|
|
{
|
|
|
|
costSoFar[nextHex.AxialCoords] = nextCost;
|
|
|
|
float priority = nextCost + nextHex.DistanceTo(goalHex);
|
|
|
|
|
|
|
|
frontier.Enqueue(nextHex.AxialCoords, priority);
|
|
|
|
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-17 22:58:15 +02:00
|
|
|
// GD.Print("Checked Cell Count: " + FindPathCheckedCells);
|
|
|
|
|
2023-07-15 21:50:38 +02:00
|
|
|
if (!cameFrom.ContainsKey(goalHex.AxialCoords))
|
|
|
|
{
|
|
|
|
return new List<HexCell>();
|
|
|
|
}
|
|
|
|
|
|
|
|
List<HexCell> result = new List<HexCell>();
|
|
|
|
if (GetHexCost(goalAxialCoords) != 0)
|
|
|
|
{
|
|
|
|
result.Add(goalHex);
|
|
|
|
}
|
|
|
|
|
|
|
|
HexCell pathHex = goalHex;
|
|
|
|
while (pathHex != startHex)
|
|
|
|
{
|
|
|
|
pathHex = new HexCell(cameFrom[pathHex.AxialCoords]);
|
|
|
|
result.Insert(0, pathHex);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2022-12-02 21:09:40 +01:00
|
|
|
}
|