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 # The offset of the backfaces relative to the first hexagon
47 self.backfaces_offsets = (0,
48 (self.hexagon_radius + self.x_border) * 2)
50 self.hexagons_color_map = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
52 def _init_centers(self):
53 # Preallocate the lists to be able to access them by indices in the
55 self.hexagons_centers = [None for h in self.hexaflexagon.hexagons]
56 self.triangles_centers = [[None for t in h.triangles] for h in self.hexaflexagon.hexagons]
58 cy = self.backend.height - (self.hexagon_radius + self.x_border)
59 for hexagon in self.hexaflexagon.hexagons:
60 cx = self.x_border + self.hexagon_radius + (2 * self.hexagon_radius + self.x_border) * hexagon.index
61 self.hexagons_centers[hexagon.index] = (cx, cy)
63 triangles_centers = self.backend.get_regular_polygon(cx, cy, 6, self.triangle_radius, pi / 6)
64 for triangle in hexagon.triangles:
65 self.triangles_centers[hexagon.index][triangle.index] = triangles_centers[triangle.index]
67 def get_hexagon_center(self, hexagon):
68 return self.hexagons_centers[hexagon.index]
70 def get_triangle_center(self, triangle):
71 return self.triangles_centers[triangle.hexagon.index][triangle.index]
73 def get_triangle_center_in_backfaces(self, triangle):
74 """Get the center of this triangle but in the backface"""
75 x0, y0 = self.backfaces_offsets
77 backface_triangle_index = triangle.get_backface_index()
78 x, y = self.triangles_centers[triangle.hexagon.index][backface_triangle_index]
82 def get_triangle_center_in_plan(self, triangle):
83 x0, y0 = self.plan_origin
84 i, j = self.hexaflexagon.get_triangle_plan_position(triangle)
85 x, y = triangle.calc_plan_coordinates(self.triangle_radius, i, j)
88 def get_triangle_verts(self, triangle):
89 cx, cy = self.get_triangle_center(triangle)
90 theta = triangle.get_angle_in_hexagon()
91 verts = self.backend.get_regular_polygon(cx, cy, 3, self.triangle_radius, theta)
94 def get_triangle_transform(self, triangle):
95 """Calculate the transformation matrix from a triangle in an hexagon to
96 the correspondent triangle in the plan.
98 Return the matrix as a list of values sorted in row-major order."""
100 src_x, src_y = self.get_triangle_center(triangle)
101 dest_x, dest_y = self.get_triangle_center_in_plan(triangle)
102 theta = triangle.get_angle_in_plan_relative_to_hexagon()
104 return self.backend.calc_rotate_translate_transform(src_x, src_y,
105 dest_x, dest_y, theta)
107 def get_triangle_backfaces_transform(self, triangle):
108 """Calculate the transformation matrix from a triangle in an hexagon to
109 the correspondent triangle in a backface.
111 Return the matrix as a list of values sorted in row-major order."""
112 src_x, src_y = self.get_triangle_center(triangle)
113 dest_x, dest_y = self.get_triangle_center_in_backfaces(triangle)
114 theta = triangle.get_angle_in_backface_relative_to_hexagon()
116 return self.backend.calc_rotate_translate_transform(src_x, src_y,
117 dest_x, dest_y, theta)
119 def draw_hexagon_template(self, hexagon):
120 for triangle in hexagon.triangles:
121 cx, cy = self.get_triangle_center(triangle)
122 theta = triangle.get_angle_in_hexagon()
123 self.draw_triangle_template(triangle, cx, cy, theta)
125 def draw_triangle_template(self, triangle, cx, cy, theta):
126 radius = self.triangle_radius
127 color = self.hexagons_color_map[triangle.hexagon.index]
129 tverts = self.backend.draw_regular_polygon(cx, cy, 3, radius, theta, color)
131 self.backend.draw_apothem_star(cx, cy, 3, radius, theta, color)
133 # Because of how draw_regular_polygon() is implemented, triangles are
134 # drawn by default with the base on the top, so the text need to be
135 # rotated by 180 to look like it is in the same orientation as
136 # a triangle with the base on the bottom.
137 text_theta = pi - theta
139 # Draw the text closer to the vertices of the element
142 corners_labels = "ABC"
143 for i, v in enumerate(tverts):
144 tx = (1 - t) * v[0] + t * cx
145 ty = (1 - t) * v[1] + t * cy
146 corner_text = str(triangle.index + 1) + corners_labels[i]
147 self.backend.draw_centered_text(tx, ty, corner_text, text_theta, color)
149 def draw_plan_template(self):
150 for hexagon in self.hexaflexagon.hexagons:
151 for triangle in hexagon.triangles:
152 x, y = self.get_triangle_center_in_plan(triangle)
153 theta = triangle.get_angle_in_plan()
154 self.draw_triangle_template(triangle, x, y, theta)
156 def draw_template(self):
157 for hexagon in self.hexaflexagon.hexagons:
158 self.draw_hexagon_template(hexagon)
160 self.draw_plan_template()