|
// https://gist.github.com/inca/0b3d194b755e2efd9d7d8e3665e6cfbb |
|
// math from https://www.redblobgames.com/grids/hexagons/ |
|
|
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using UnityEngine; |
|
|
|
public static class HexVectorExtensions { |
|
|
|
public static Vector2 WorldToPlanar(this Vector3 world) { |
|
return new Vector2(world.x, world.z); |
|
} |
|
|
|
public static Vector3 PlanarToWorld(this Vector2 planar, float y = 0f) { |
|
return new Vector3(planar.x, y, planar.y); |
|
} |
|
|
|
public static Hex ToHex(this Vector3 world) { |
|
return Hex.FromWorld(world); |
|
} |
|
|
|
public static Hex ToHex(this Vector2 planar) { |
|
return Hex.FromPlanar(planar); |
|
} |
|
|
|
} |
|
|
|
[System.Serializable] |
|
public struct Hex { |
|
|
|
#region Static Members |
|
|
|
public static float RADIUS = 0.5f; |
|
public static Vector2 Q_BASIS = new Vector2(2f, 0) * RADIUS; |
|
public static Vector2 R_BASIS = new Vector2(1f, Mathf.Sqrt(3)) * RADIUS; |
|
public static Vector2 Q_INV = new(1f / 2, -Mathf.Sqrt(3) / 6); |
|
public static Vector2 R_INV = new(0, Mathf.Sqrt(3) / 3); |
|
|
|
public static Hex zero = new(0, 0); |
|
|
|
public static Hex operator +(Hex a, Hex b) => new Hex(a.q + b.q, a.r + b.r); |
|
public static Hex operator -(Hex a, Hex b) => new Hex(a.q - b.q, a.r - b.r); |
|
public static Hex operator *(Hex a, int k) => new Hex(a.q * k, a.r * k); |
|
public static Hex operator /(Hex a, int k) => new Hex(a.q / k, a.r / k); |
|
|
|
public enum HexDirections |
|
{ // pointy top orientation |
|
East = 0, Southeast = 1, Southwest = 2, West = 3, Northwest = 4, Northeast = 5 |
|
} |
|
|
|
public static Hex[] PRIMARY_DIRECTIONS = |
|
{ |
|
new(1, 0), // 0 |
|
new(0, 1), // 1 |
|
new(-1, 1), // 2 |
|
new(-1, 0), // 3 |
|
new(0, -1), // 4 |
|
new(1, -1), // 5 |
|
}; |
|
|
|
public static Hex HexDirection(HexDirections direction) |
|
{ |
|
return HexDirection((int)direction); |
|
} |
|
|
|
public static Hex HexDirection(int direction) |
|
{ |
|
//Hex incr = PRIMARY_DIRECTIONS[direction % PRIMARY_DIRECTIONS.Length]; |
|
//return this + incr; |
|
|
|
return PRIMARY_DIRECTIONS[direction]; |
|
} |
|
|
|
public static Hex[] DIAGONAL_DIRECTIONS = |
|
{ |
|
new(2, -1), |
|
new(1, 1), |
|
new(-1, 2), |
|
new(-2, 1), |
|
new(-1, -1), |
|
new(1, -2) |
|
}; |
|
|
|
public static Hex FromPlanar(Vector2 planar) { |
|
float q = Vector2.Dot(planar, Q_INV) / RADIUS; |
|
float r = Vector2.Dot(planar, R_INV) / RADIUS; |
|
return new Hex(q, r); |
|
} |
|
|
|
public static Hex FromWorld(Vector3 world) { |
|
return FromPlanar(world.WorldToPlanar()); |
|
} |
|
|
|
|
|
|
|
public static IEnumerable<Hex> Ring(Hex center, int radius) { |
|
Hex current = center + new Hex(0, -radius); |
|
foreach (Hex dir in PRIMARY_DIRECTIONS) { |
|
for (int i = 0; i < radius; i++) { |
|
yield return current; |
|
current = current + dir; |
|
} |
|
} |
|
} |
|
|
|
public static IEnumerable<Hex> Spiral(Hex center, int minRadius, int maxRadius) { |
|
if (minRadius == 0) { |
|
yield return center; |
|
minRadius += 1; |
|
} |
|
for (int r = minRadius; r <= maxRadius; r++) { |
|
var ring = Ring(center, r); |
|
foreach (Hex hex in ring) { |
|
yield return hex; |
|
} |
|
} |
|
} |
|
|
|
public static IEnumerable<Vector3> AStarSearchWorld(Hex start, Hex goal, IEnumerable<Hex> obstacles = null, IEnumerable<Hex> grid = null) |
|
{ |
|
return AStarSearch(start, goal, obstacles, grid).Select(hex => hex.ToWorld()); |
|
} |
|
|
|
public static IEnumerable<Hex> AStarSearch(Hex start, Hex goal, IEnumerable<Hex> obstacles = null, IEnumerable<Hex> grid = null) |
|
{ |
|
var cameFrom = new Dictionary<Hex, Hex>(); |
|
var costSoFar = new Dictionary<Hex, double>(); |
|
var frontier = new Utils.PriorityQueue<Hex, double>(); |
|
|
|
frontier.Enqueue(start, 0); |
|
cameFrom[start] = start; |
|
costSoFar[start] = 0; |
|
|
|
while (frontier.Count > 0) |
|
{ |
|
var current = frontier.Dequeue(); |
|
|
|
if (current.Equals(goal)) |
|
{ |
|
break; |
|
} |
|
|
|
var search = current.Neighbours(); |
|
if (obstacles != null) search = search.Except(obstacles); |
|
if (grid != null) search = search.Where(tile=>grid.Contains(tile)); |
|
|
|
foreach (var next in search) |
|
{ |
|
double newCost = costSoFar[current]; |
|
|
|
if ((!costSoFar.ContainsKey(next) || newCost < costSoFar[next])) |
|
{ |
|
costSoFar[next] = newCost; |
|
double priority = newCost + next.DistanceTo(goal); |
|
frontier.Enqueue(next, priority); |
|
cameFrom[next] = current; |
|
} |
|
} |
|
} |
|
|
|
// return the path we calculated |
|
var current2 = goal; |
|
while (!current2.Equals(start)) |
|
{ |
|
yield return current2; |
|
current2 = cameFrom[current2]; |
|
} |
|
|
|
yield return start; |
|
|
|
} |
|
|
|
public static IEnumerable<Hex> DrawLine(Hex start, Hex end) |
|
{ |
|
var n = start.DistanceTo(end); |
|
for (int i = 0; i <= n; i++) |
|
{ |
|
yield return LerpHex(start, end, (1f / n) * i); |
|
} |
|
} |
|
|
|
static Hex LerpHex(Hex a, Hex b, float t) |
|
{ |
|
return new Hex(Mathf.Lerp(a.q, b.q, t), Mathf.Lerp(a.r, b.r, t)); |
|
} |
|
|
|
|
|
#endregion |
|
|
|
#region Main Class |
|
|
|
public int q; |
|
public int r; |
|
public int s => -q - r; |
|
|
|
public Hex(int q, int r) { |
|
this.q = q; |
|
this.r = r; |
|
} |
|
|
|
public Hex(float floatQ, float floatR) |
|
{ |
|
var floatS = -floatQ - floatR; |
|
this = new Hex(floatQ, floatR, floatS); |
|
} |
|
|
|
public Hex(float floatQ, float floatR, float floatS) |
|
{ |
|
var intQ = Mathf.RoundToInt(floatQ); |
|
var intR = Mathf.RoundToInt(floatR); |
|
var intS = Mathf.RoundToInt(floatS); |
|
|
|
var q_diff = Mathf.Abs(intQ - floatQ); |
|
var r_diff = Mathf.Abs(intR - floatR); |
|
var s_diff = Mathf.Abs(intS - floatS); |
|
|
|
if (q_diff > r_diff && q_diff > s_diff) |
|
intQ = -intR - intS; |
|
else if (r_diff > s_diff) |
|
intR = -intQ - intS; |
|
|
|
q = intQ; |
|
r = intR; |
|
} |
|
|
|
public Vector2 ToPlanar() { |
|
return Q_BASIS * q + R_BASIS * r; |
|
} |
|
|
|
public Vector3 ToWorld(float y = 0f) { |
|
return ToPlanar().PlanarToWorld(y); |
|
} |
|
|
|
public Hex RotateLeft() => new(-s, -q, -r); |
|
|
|
public Hex RotateRight() => new(-r, -s, -q); |
|
|
|
public IEnumerable<Hex> Neighbours() { |
|
foreach (var dir in PRIMARY_DIRECTIONS) { |
|
yield return this + dir; |
|
} |
|
} |
|
|
|
public Hex GetNeighbor(int direction, bool diagonal = false) |
|
{ |
|
return diagonal ? this + DIAGONAL_DIRECTIONS[direction] : this + HexDirection(direction); |
|
} |
|
|
|
public IEnumerable<Hex> DrawLine(Hex end) |
|
{ |
|
var n = DistanceTo(end); |
|
for (int i = 0; i <= n; i++) |
|
{ |
|
yield return LerpHex(this, end, (1f / n) * i); |
|
} |
|
} |
|
|
|
public int Length() |
|
{ |
|
return (Mathf.Abs(q) + Mathf.Abs(r) + Mathf.Abs(s)) / 2; |
|
} |
|
|
|
public int DistanceTo(Hex to) |
|
{ |
|
//return (Mathf.Abs(q - to.q) + Mathf.Abs(r - to.r) + Mathf.Abs(s - to.s)) / 2; |
|
return (this - to).Length(); |
|
} |
|
|
|
#endregion |
|
#region Overrides |
|
|
|
public override bool Equals(System.Object obj) { |
|
Hex hex = (Hex)obj; |
|
return (q == hex.q) && (r == hex.r); |
|
} |
|
|
|
public override int GetHashCode() { |
|
return 23 + 31 * q + 37 * r; |
|
} |
|
|
|
public override string ToString() { |
|
return "(" + q + ";" + r + ")"; |
|
} |
|
|
|
#endregion |
|
|
|
} |