README.md: mention also the tetraflexagon example
[flexagon-toolkit.git] / src / flexagon / tetraflexagon_diagram.py
1 #!/usr/bin/env python
2 #
3 # A class to draw tetraflexagons
4 #
5 # Copyright (C) 2018  Antonio Ospite <ao2@ao2.it>
6 #
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.
11 #
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.
16 #
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/>.
19
20 from .tritetraflexagon import TriTetraflexagon
21
22
23 class TetraflexagonDiagram(object):
24     def __init__(self, x_border, backend=None):
25         self.x_border = x_border
26         self.backend = backend
27
28         self.tetraflexagon = TriTetraflexagon()
29
30         num_squares = len(self.tetraflexagon.squares)
31         self.square_side = (self.backend.height - (x_border * 3)) / (num_squares)
32         self.square_radius = self.square_side / 2
33         self.tile_side = self.square_radius
34         self.tile_radius = self.tile_side / 2
35
36         self._init_centers()
37
38         # draw the plan centered wrt. the squares
39         self.plan_origin = ((self.backend.width - self.tile_side * 5) / 2,
40                             self.x_border)
41
42         self.backfaces_origin = (self.squares_centers[0][0] - self.tile_side,
43                                  self.squares_centers[0][1] + self.x_border * 2 + self.tile_side)
44
45         self.squares_color_map = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
46
47     def _init_centers(self):
48         # Preallocate the lists to be able to access them by indices in the
49         # loops below.
50         self.squares_centers = [None for h in self.tetraflexagon.squares]
51         self.tiles_centers = [[None for t in h.tiles] for h in self.tetraflexagon.squares]
52
53         cy = self.backend.height - (self.square_radius + self.x_border)
54         for square in self.tetraflexagon.squares:
55             cx = self.x_border + (2 * self.square_radius + self.x_border) * (square.index + 1)
56             self.squares_centers[square.index] = (cx, cy)
57
58             for tile in square.tiles:
59                 # offset by 1 or -1 times the tile radius
60                 tile_xoffset, tile_yoffset = tile.calc_offset_in_square(self.tile_side)
61                 self.tiles_centers[square.index][tile.index] = (cx + tile_xoffset, cy + tile_yoffset)
62
63     def get_square_center(self, square):
64         return self.squares_centers[square.index]
65
66     def get_tile_center(self, tile):
67         return self.tiles_centers[tile.square.index][tile.index]
68
69     def get_tile_center_in_plan(self, tile):
70         x0, y0 = self.plan_origin
71         i, j = self.tetraflexagon.get_tile_plan_position(tile)
72         x, y = tile.calc_plan_coordinates(self.tile_side, i, j)
73         return x0 + x, y0 + y
74
75     def get_backface_tile_transform(self, tile):
76         src_x, src_y = self.get_tile_center(tile)
77         tile_xoffset, tile_yoffset = tile.calc_offset_in_square(self.tile_side)
78         # When calculating dest_x the minus in the formula switches the columns.
79         dest_x = self.backfaces_origin[0] + self.tile_side - tile_xoffset
80         dest_y = self.backfaces_origin[1] + self.tile_side + tile_yoffset
81
82         return self.backend.calc_rotate_translate_transform(src_x, src_y,
83                                                             dest_x, dest_y, 0)
84
85     def get_tile_transform(self, tile):
86         """Calculate the transformation matrix from a tile in an square to
87         the correspondent tile in the plan.
88
89         Return the matrix as a list of values sorted in row-major order."""
90
91         src_x, src_y = self.get_tile_center(tile)
92         dest_x, dest_y = self.get_tile_center_in_plan(tile)
93
94         i, j = self.tetraflexagon.get_tile_plan_position(tile)
95         theta = tile.calc_angle_in_plan(i, j)
96
97         return self.backend.calc_rotate_translate_transform(src_x, src_y,
98                                                             dest_x, dest_y, theta)
99
100     def draw_square_template(self, square):
101         for tile in square.tiles:
102             cx, cy = self.get_tile_center(tile)
103             self.draw_tile_template(tile, cx, cy, 0)
104
105     def draw_tile_template(self, tile, cx, cy, theta):
106         side = self.tile_side
107         color = self.squares_color_map[tile.square.index]
108
109         self.backend.draw_rect_from_center(cx, cy, side, side, theta,
110                                            stroke_color=color,
111                                            fill_color=None)
112
113         corners_labels = "ABC"
114         corner_text = corners_labels[tile.square.index] + str(tile.index + 1)
115         self.backend.draw_centered_text(cx, cy, corner_text, 0, color)
116
117     def draw_plan_template(self):
118         x0, y0 = self.plan_origin
119         for square in self.tetraflexagon.squares:
120             for tile in square.tiles:
121                 i, j = self.tetraflexagon.get_tile_plan_position(tile)
122                 x, y = tile.calc_plan_coordinates(self.tile_radius, i, j)
123                 theta = tile.get_angle_in_plan(i, j)
124                 self.draw_tile_template(tile, x0 + x, y0 + y, theta)
125
126     def draw_template(self):
127         for square in self.tetraflexagon.squares:
128             self.draw_square_template(square)
129
130         self.draw_plan_template()