#include "hexlib.h" // Generated code -- http://www.redblobgames.com/grids/hexagons/ #include #include #include using std::abs; using std::max; using std::vector; struct OffsetCoord { const int col; const int row; OffsetCoord(int col_, int row_): col(col_), row(row_) {} }; Hex hex_add(Hex a, Hex b) { return Hex(a.q + b.q, a.r + b.r, a.s + b.s); } Hex hex_subtract(Hex a, Hex b) { return Hex(a.q - b.q, a.r - b.r, a.s - b.s); } Hex hex_scale(Hex a, int k) { return Hex(a.q * k, a.r * k, a.s * k); } const vector hex_directions = {Hex(1, 0, -1), Hex(1, -1, 0), Hex(0, -1, 1), Hex(-1, 0, 1), Hex(-1, 1, 0), Hex(0, 1, -1)}; Hex hex_direction(int direction) { return hex_directions[direction]; } Hex hex_neighbor(Hex hex, int direction) { return hex_add(hex, hex_direction(direction)); } const vector hex_diagonals = {Hex(2, -1, -1), Hex(1, -2, 1), Hex(-1, -1, 2), Hex(-2, 1, 1), Hex(-1, 2, -1), Hex(1, 1, -2)}; Hex hex_diagonal_neighbor(Hex hex, int direction) { return hex_add(hex, hex_diagonals[direction]); } int hex_length(Hex hex) { return int((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2); } int hex_distance(Hex a, Hex b) { return hex_length(hex_subtract(a, b)); } Hex hex_round(FractionalHex h) { int q = int(round(h.q)); int r = int(round(h.r)); int s = int(round(h.s)); double q_diff = abs(q - h.q); double r_diff = abs(r - h.r); double s_diff = abs(s - h.s); if (q_diff > r_diff && q_diff > s_diff) { q = -r - s; } else if (r_diff > s_diff) { r = -q - s; } else { s = -q - r; } return Hex(q, r, s); } FractionalHex hex_lerp(FractionalHex a, FractionalHex b, double t) { return FractionalHex(a.q * (1 - t) + b.q * t, a.r * (1 - t) + b.r * t, a.s * (1 - t) + b.s * t); } vector hex_linedraw(Hex a, Hex b) { int N = hex_distance(a, b); FractionalHex a_nudge = FractionalHex(a.q + 0.000001, a.r + 0.000001, a.s - 0.000002); FractionalHex b_nudge = FractionalHex(b.q + 0.000001, b.r + 0.000001, b.s - 0.000002); vector results = {}; double step = 1.0 / max(N, 1); for (int i = 0; i <= N; i++) { results.push_back(hex_round(hex_lerp(a_nudge, b_nudge, step * i))); } return results; } const int EVEN = 1; const int ODD = -1; OffsetCoord qoffset_from_cube(int offset, Hex h) { int col = h.q; int row = h.r + int((h.q + offset * (h.q & 1)) / 2); return OffsetCoord(col, row); } Hex qoffset_to_cube(int offset, OffsetCoord h) { int q = h.col; int r = h.row - int((h.col + offset * (h.col & 1)) / 2); int s = -q - r; return Hex(q, r, s); } OffsetCoord roffset_from_cube(int offset, Hex h) { int col = h.q + int((h.r + offset * (h.r & 1)) / 2); int row = h.r; return OffsetCoord(col, row); } Hex roffset_to_cube(int offset, OffsetCoord h) { int q = h.col - int((h.row + offset * (h.row & 1)) / 2); int r = h.row; int s = -q - r; return Hex(q, r, s); } Point hex_to_pixel(Layout layout, Hex h) { Orientation M = layout.orientation; Point size = layout.size; Point origin = layout.origin; double x = (M.f0 * h.q + M.f1 * h.r) * size.x; double y = (M.f2 * h.q + M.f3 * h.r) * size.y; return Point(x + origin.x, y + origin.y); } FractionalHex pixel_to_hex(Layout layout, Point p) { Orientation M = layout.orientation; Point size = layout.size; Point origin = layout.origin; Point pt = Point((p.x - origin.x) / size.x, (p.y - origin.y) / size.y); double q = M.b0 * pt.x + M.b1 * pt.y; double r = M.b2 * pt.x + M.b3 * pt.y; return FractionalHex(q, r, -q - r); } Point hex_corner_offset(Layout layout, int corner) { Orientation M = layout.orientation; Point size = layout.size; double angle = 2.0 * M_PI * (M.start_angle - corner) / 6; return Point(size.x * cos(angle), size.y * sin(angle)); } vector polygon_corners(Layout layout, Hex h) { vector corners = {}; Point center = hex_to_pixel(layout, h); for (int i = 0; i < 6; i++) { Point offset = hex_corner_offset(layout, i); corners.push_back(Point(center.x + offset.x, center.y + offset.y)); } return corners; } // custom hex functions bool hex_equal(Hex a, Hex b) { return (a.q == b.q && a.r == b.r && a.s == b.s); } // NOTE: implementation of this line crossing test // https://graphics.stanford.edu/pub/Graphics/RTNews/html/rtnv5n3.html#art3 // NOTE: assumes use of convex polygons with vertices in CCW layout bool crossingTest(vector vertices, Point p) { int numVertices = vertices.size(), intersect_count = 0; Point vert0, vert1; double m, x; if (numVertices < 3) return false; vert0 = vertices[0]; for (int i = 1; i < numVertices + 1; i++) { // use first vertex for the last edge if (i == numVertices) vert1 = vertices[0]; else vert1 = vertices[i]; // check if edge can intersect the +X ray // NOTE: upward/downward crossing excludes one vertex in the case that // the ray intersects a vertex if ((vert0.y > p.y && vert1.y <= p.y) || // downward crossing (vert0.y <= p.y && vert1.y > p.y)) // upward crossing { m = (vert1.y - vert0.y) / (vert1.x - vert0.x); x = (p.y - vert0.y) / m + vert0.x; // if x is right of p.x it must intersect if (x >= p.x) intersect_count++; // 2 intersections of convex polygon means we started outside if (intersect_count > 1) return false; } // start with previous vertex on next loop vert0 = vert1; } return (intersect_count == 1); }