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 ]