nodes/hex_math.pyΒΆ

Part of Hextris.

 1"""Hex geometry helpers for the board: angles, vertices, slices and blocks.
 2
 3Conventions:
 4- 6 sides indexed 0..5, clockwise, side 0 is the TOP.
 5- Side `i` centre direction (unit vector, screen coords with +y down):
 6      angle_rad = i * pi/3 - pi/2      (i=0 -> -pi/2, i.e. up)
 7      direction = (cos(angle), sin(angle))
 8- Hexagon "flat" of side `i` is the segment perpendicular to direction(i),
 9  at distance `apothem = side_length * sqrt(3) / 2` from the centre.
10"""
11
12import math
13
14SQRT3 = math.sqrt(3.0)
15
16
17def side_angle(side: int) -> float:
18    """Angle (radians) from centre to the midpoint of side `side`. 0 = top."""
19    return side * (math.pi / 3.0) - math.pi / 2.0
20
21
22def apothem(side_length: float) -> float:
23    """Distance from centre of regular hexagon to midpoint of any side."""
24    return side_length * SQRT3 / 2.0
25
26
27def hex_vertices(cx: float, cy: float, side_length: float, rotation: float = 0.0) -> list[tuple[float, float]]:
28    """Six vertices of a flat-top regular hexagon centred at (cx, cy).
29
30    `rotation` is an extra rotation (radians) applied to all vertices,
31    used for the slow visual rotation of the central hex.
32    Vertex i is the corner BETWEEN side i-1 and side i (clockwise).
33    """
34    out = []
35    # Vertices sit at midpoint angles + 30deg
36    for i in range(6):
37        a = side_angle(i) + math.pi / 6.0 + rotation
38        out.append((cx + math.cos(a) * side_length, cy + math.sin(a) * side_length))
39    return out
40
41
42def slice_triangle(
43    cx: float, cy: float, side_length: float, side: int, rotation: float = 0.0
44) -> list[tuple[float, float]]:
45    """Return the 3 vertices of one coloured triangular slice of the hex."""
46    a0 = side_angle(side) - math.pi / 6.0 + rotation  # left corner
47    a1 = side_angle(side) + math.pi / 6.0 + rotation  # right corner
48    return [
49        (cx, cy),
50        (cx + math.cos(a0) * side_length, cy + math.sin(a0) * side_length),
51        (cx + math.cos(a1) * side_length, cy + math.sin(a1) * side_length),
52    ]
53
54
55def block_quad(
56    cx: float,
57    cy: float,
58    side: int,
59    distance: float,
60    height: float,
61    rotation: float = 0.0,
62) -> list[tuple[float, float]]:
63    """Return 4 vertices of a falling/stacked block on `side`.
64
65    `distance` is the inner edge's distance from hex centre (the side closest
66    to the hex). `height` is the radial thickness of the block. `rotation` is
67    the additional radial rotation (used by the rotating central stack).
68
69    The block is a trapezoid: the outer edge is wider than the inner edge to
70    follow the hex geometry (matches upstream's `widthWide`).
71    """
72    a = side_angle(side) + rotation
73    # Inner half-width follows the hex side: perpendicular extent at the inner radius.
74    inner_half = distance / SQRT3
75    outer_half = (distance + height) / SQRT3
76    cos_a, sin_a = math.cos(a), math.sin(a)
77    # Local axis: radial = (cos_a, sin_a); tangential = (-sin_a, cos_a).
78    rx, ry = cos_a, sin_a
79    tx, ty = -sin_a, cos_a
80    inner_cx = cx + rx * distance
81    inner_cy = cy + ry * distance
82    outer_cx = cx + rx * (distance + height)
83    outer_cy = cy + ry * (distance + height)
84    return [
85        (inner_cx + tx * -inner_half, inner_cy + ty * -inner_half),
86        (inner_cx + tx * inner_half, inner_cy + ty * inner_half),
87        (outer_cx + tx * outer_half, outer_cy + ty * outer_half),
88        (outer_cx + tx * -outer_half, outer_cy + ty * -outer_half),
89    ]