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 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 return self.backend.calc_rotate_translate_transform(src_x, src_y,
92 dest_x, dest_y, theta)
94 def draw_hexagon_template(self, hexagon):
95 for triangle in hexagon.triangles:
96 cx, cy = self.get_triangle_center(triangle)
97 theta = triangle.get_angle_in_hexagon()
98 self.draw_triangle_template(triangle, cx, cy, theta)
100 def draw_triangle_template(self, triangle, cx, cy, theta):
101 radius = self.triangle_radius
102 color = self.hexagons_color_map[triangle.hexagon.index]
104 tverts = self.backend.draw_regular_polygon(cx, cy, 3, radius, theta, color)
106 self.backend.draw_apothem_star(cx, cy, 3, radius, theta, color)
108 # Because of how draw_regular_polygon() is implemented, triangles are
109 # drawn by default with the base on the top, so the text need to be
110 # rotated by 180 to look like it is in the same orientation as
111 # a triangle with the base on the bottom.
112 text_theta = pi - theta
114 # Draw the text closer to the vertices of the element
117 corners_labels = "ABC"
118 for i, v in enumerate(tverts):
119 tx = (1 - t) * v[0] + t * cx
120 ty = (1 - t) * v[1] + t * cy
121 corner_text = str(triangle.index + 1) + corners_labels[i]
122 self.backend.draw_centered_text(tx, ty, corner_text, text_theta, color)
124 def draw_plan_template(self):
125 for hexagon in self.hexaflexagon.hexagons:
126 for triangle in hexagon.triangles:
127 x, y = self.get_triangle_center_in_plan(triangle)
128 theta = triangle.get_angle_in_plan()
129 self.draw_triangle_template(triangle, x, y, theta)
131 def draw_template(self):
132 for hexagon in self.hexaflexagon.hexagons:
133 self.draw_hexagon_template(hexagon)
135 self.draw_plan_template()