#!/usr/bin/env python # # A class to draw hexaflexagons # # Copyright (C) 2018 Antonio Ospite # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from math import cos, pi from .trihexaflexagon import TriHexaflexagon class HexaflexagonDiagram(object): def __init__(self, x_border, backend=None): self.x_border = x_border self.backend = backend self.hexaflexagon = TriHexaflexagon() num_hexagons = len(self.hexaflexagon.hexagons) self.hexagon_radius = (self.backend.width - (x_border * (num_hexagons + 1))) / (num_hexagons * 2) # The hexagon apothem is the triangle height hexagon_apothem = self.hexagon_radius * cos(pi / 6.) # the triangle radius is 2/3 of its height self.triangle_radius = hexagon_apothem * 2. / 3. self._init_centers() # draw the plan centered wrt. the hexagons self.plan_origin = (self.x_border * 2. + self.hexagon_radius / 2., self.x_border + self.triangle_radius / 3.) # The offset of the backfaces relative to the first hexagon self.backfaces_offsets = (0, (self.hexagon_radius + self.x_border) * 2) self.hexagons_color_map = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] def _init_centers(self): # Preallocate the lists to be able to access them by indices in the # loops below. self.hexagons_centers = [None for h in self.hexaflexagon.hexagons] self.triangles_centers = [[None for t in h.triangles] for h in self.hexaflexagon.hexagons] cy = self.backend.height - (self.hexagon_radius + self.x_border) for hexagon in self.hexaflexagon.hexagons: cx = self.x_border + self.hexagon_radius + (2 * self.hexagon_radius + self.x_border) * hexagon.index self.hexagons_centers[hexagon.index] = (cx, cy) triangles_centers = self.backend.get_regular_polygon(cx, cy, 6, self.triangle_radius, pi / 6) for triangle in hexagon.triangles: self.triangles_centers[hexagon.index][triangle.index] = triangles_centers[triangle.index] def get_hexagon_center(self, hexagon): return self.hexagons_centers[hexagon.index] def get_triangle_center(self, triangle): return self.triangles_centers[triangle.hexagon.index][triangle.index] def get_triangle_center_in_backfaces(self, triangle): """Get the center of this triangle but in the backface""" x0, y0 = self.backfaces_offsets backface_triangle_index = triangle.get_backface_index() x, y = self.triangles_centers[triangle.hexagon.index][backface_triangle_index] return x0 + x, y0 + y def get_triangle_center_in_plan(self, triangle): x0, y0 = self.plan_origin i, j = self.hexaflexagon.get_triangle_plan_position(triangle) x, y = triangle.calc_plan_coordinates(self.triangle_radius, i, j) return x0 + x, y0 + y def get_triangle_verts(self, triangle): cx, cy = self.get_triangle_center(triangle) theta = triangle.get_angle_in_hexagon() verts = self.backend.get_regular_polygon(cx, cy, 3, self.triangle_radius, theta) return verts def get_triangle_transform(self, triangle): """Calculate the transformation matrix from a triangle in an hexagon to the correspondent triangle in the plan. Return the matrix as a list of values sorted in row-major order.""" src_x, src_y = self.get_triangle_center(triangle) dest_x, dest_y = self.get_triangle_center_in_plan(triangle) theta = triangle.get_angle_in_plan_relative_to_hexagon() return self.backend.calc_rotate_translate_transform(src_x, src_y, dest_x, dest_y, theta) def get_triangle_backfaces_transform(self, triangle): """Calculate the transformation matrix from a triangle in an hexagon to the correspondent triangle in a backface. Return the matrix as a list of values sorted in row-major order.""" src_x, src_y = self.get_triangle_center(triangle) dest_x, dest_y = self.get_triangle_center_in_backfaces(triangle) theta = triangle.get_angle_in_backface_relative_to_hexagon() return self.backend.calc_rotate_translate_transform(src_x, src_y, dest_x, dest_y, theta) def draw_hexagon_template(self, hexagon): for triangle in hexagon.triangles: cx, cy = self.get_triangle_center(triangle) theta = triangle.get_angle_in_hexagon() self.draw_triangle_template(triangle, cx, cy, theta) def draw_triangle_template(self, triangle, cx, cy, theta): radius = self.triangle_radius color = self.hexagons_color_map[triangle.hexagon.index] tverts = self.backend.draw_regular_polygon(cx, cy, 3, radius, theta, color) self.backend.draw_apothem_star(cx, cy, 3, radius, theta, color) # Because of how draw_regular_polygon() is implemented, triangles are # drawn by default with the base on the top, so the text need to be # rotated by 180 to look like it is in the same orientation as # a triangle with the base on the bottom. text_theta = pi - theta # Draw the text closer to the vertices of the element t = 0.3 corners_labels = "ABC" for i, v in enumerate(tverts): tx = (1 - t) * v[0] + t * cx ty = (1 - t) * v[1] + t * cy corner_text = str(triangle.index + 1) + corners_labels[i] self.backend.draw_centered_text(tx, ty, corner_text, text_theta, color) def draw_plan_template(self): for hexagon in self.hexaflexagon.hexagons: for triangle in hexagon.triangles: x, y = self.get_triangle_center_in_plan(triangle) theta = triangle.get_angle_in_plan() self.draw_triangle_template(triangle, x, y, theta) def draw_template(self): for hexagon in self.hexaflexagon.hexagons: self.draw_hexagon_template(hexagon) self.draw_plan_template()