3 # A class to draw hexaflexagons
5 # Copyright (C) 2018 Antonio Ospite <ao2@ao2.it>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from math import sin, cos, pi
21 from .trihexaflexagon import TriHexaflexagon
24 class HexaflexagonDiagram(object):
25 def __init__(self, x_border, backend=None):
26 self.x_border = x_border
27 self.backend = backend
29 self.hexaflexagon = TriHexaflexagon()
31 num_hexagons = len(self.hexaflexagon.hexagons)
32 self.hexagon_radius = (self.backend.width - (x_border * (num_hexagons + 1))) / (num_hexagons * 2)
34 # The hexagon apothem is the triangle height
35 hexagon_apothem = self.hexagon_radius * cos(pi / 6.)
37 # the triangle radius is 2/3 of its height
38 self.triangle_radius = hexagon_apothem * 2. / 3.
42 # draw the plan centered wrt. the hexagons
43 self.plan_origin = (self.x_border * 2. + self.hexagon_radius / 2.,
44 self.x_border + self.triangle_radius / 3.)
46 self.hexagons_color_map = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
48 def _init_centers(self):
49 # Preallocate the lists to be able to access them by indices in the
51 self.hexagons_centers = [None for h in self.hexaflexagon.hexagons]
52 self.triangles_centers = [[None for t in h.triangles] for h in self.hexaflexagon.hexagons]
54 cy = self.backend.height - (self.hexagon_radius + self.x_border)
55 for hexagon in self.hexaflexagon.hexagons:
56 cx = self.x_border + self.hexagon_radius + (2 * self.hexagon_radius + self.x_border) * hexagon.index
57 self.hexagons_centers[hexagon.index] = (cx, cy)
59 triangles_centers = self.backend.get_regular_polygon(cx, cy, 6, self.triangle_radius, pi / 6)
60 for triangle in hexagon.triangles:
61 self.triangles_centers[hexagon.index][triangle.index] = triangles_centers[triangle.index]
63 def get_hexagon_center(self, hexagon):
64 return self.hexagons_centers[hexagon.index]
66 def get_triangle_center(self, triangle):
67 return self.triangles_centers[triangle.hexagon.index][triangle.index]
69 def get_triangle_center_in_plan(self, triangle):
70 x0, y0 = self.plan_origin
71 i, j = self.hexaflexagon.get_triangle_plan_position(triangle)
72 x, y = triangle.calc_plan_coordinates(self.triangle_radius, i, j)
75 def get_triangle_verts(self, triangle):
76 cx, cy = self.get_triangle_center(triangle)
77 theta = triangle.get_angle_in_hexagon()
78 verts = self.backend.get_regular_polygon(cx, cy, 3, self.triangle_radius, theta)
81 def get_triangle_transform(self, triangle):
82 """Calculate the transformation matrix from a triangle in an hexagon to
83 the correspondent triangle in the plan.
85 Return the matrix as a list of values sorted in row-major order."""
87 src_x, src_y = self.get_triangle_center(triangle)
88 dest_x, dest_y = self.get_triangle_center_in_plan(triangle)
89 theta = triangle.get_angle_in_plan_relative_to_hexagon()
91 # The transformation from a triangle in the hexagon to the correspondent
92 # triangle in the plan is composed by these steps:
94 # 1. rotate by 'theta' around (src_x, src_y);
95 # 2. move to (dest_x, dest_y).
97 # Step 1 can be expressed by these sub-steps:
99 # 1a. translate by (-src_x, -src_y)
100 # 1b. rotate by 'theta'
101 # 1c. translate by (src_x, src_y)
103 # Step 2. can be expressed by a translation like:
105 # 2a. translate by (dest_x - src_x, dest_y - src_y)
107 # The consecutive translations 1c and 2a can be easily combined, so
108 # the final steps are:
110 # T1 -> translate by (-src_x, -src_y)
111 # R -> rotate by 'theta'
112 # T2 -> translate by (dest_x, dest_y)
114 # Using affine transformations these are expressed as:
117 # T1 = | 0 1 -src_y |
120 # | cos(theta) -sin(theta) 0 |
121 # R = | sin(theta) con(theta) 0 |
125 # T2 = | 0 1 dest_y |
128 # Composing these transformations into one is achieved by multiplying
129 # the matrices from right to left:
133 # NOTE: To remember this think about composing functions: T2(R(T1())),
134 # the inner one is performed first.
136 # The resulting T matrix is the one below.
138 cos(theta), -sin(theta), -src_x * cos(theta) + src_y * sin(theta) + dest_x,
139 sin(theta), cos(theta), -src_x * sin(theta) - src_y * cos(theta) + dest_y,
145 def draw_hexagon_template(self, hexagon):
146 for triangle in hexagon.triangles:
147 cx, cy = self.get_triangle_center(triangle)
148 theta = triangle.get_angle_in_hexagon()
149 self.draw_triangle_template(triangle, cx, cy, theta)
151 def draw_triangle_template(self, triangle, cx, cy, theta):
152 radius = self.triangle_radius
153 color = self.hexagons_color_map[triangle.hexagon.index]
155 tverts = self.backend.draw_regular_polygon(cx, cy, 3, radius, theta, color)
157 self.backend.draw_apothem_star(cx, cy, 3, radius, theta, color)
159 # Because of how draw_regular_polygon() is implemented, triangles are
160 # drawn by default with the base on the top, so the text need to be
161 # rotated by 180 to look like it is in the same orientation as
162 # a triangle with the base on the bottom.
163 text_theta = pi - theta
165 # Draw the text closer to the vertices of the element
168 corners_labels = "ABC"
169 for i, v in enumerate(tverts):
170 tx = (1 - t) * v[0] + t * cx
171 ty = (1 - t) * v[1] + t * cy
172 corner_text = str(triangle.index + 1) + corners_labels[i]
173 self.backend.draw_centered_text(tx, ty, corner_text, text_theta, color)
175 def draw_plan_template(self):
176 for hexagon in self.hexaflexagon.hexagons:
177 for triangle in hexagon.triangles:
178 x, y = self.get_triangle_center_in_plan(triangle)
179 theta = triangle.get_angle_in_plan()
180 self.draw_triangle_template(triangle, x, y, theta)
182 def draw_template(self):
183 for hexagon in self.hexaflexagon.hexagons:
184 self.draw_hexagon_template(hexagon)
186 self.draw_plan_template()