Сеточные системы координат, в которых плоскость делится на одинаковые симметричные элементы — на квадраты, треугольники, шестиугольники, достаточно известны. Им соответствуют квадратная, треугольная, шестиугольная симметрия. Но еще существует симметрия десятиугольная.
В ней плоскость не делится на десятиугольники, вместо этого все линии расположены под углами кратными 36°. Координаты в этой системе можно записывать целыми числами, по два целых числа на горизонтальное и вертикальное направление.
![](https://habrastorage.org/r/w1560/webt/gf/xz/73/gfxz73dxe0-owjktjad1d-ohf3o.png)
Расскажу как это нарисовать.
Количество единичных векторов по всем направлениям в этой системе отсчета десять. Если учесть симметрию обратного направления, то пять. Если учесть горизонтальную симметрию, то три. Обозначим их как
и выведем для них аналитические выражения.
Обычная формула расчета координат при повороте:
![$x_a = \cos(a) x - \sin(a) y$](https://habrastorage.org/getpro/habr/formulas/eb2/70d/dce/eb270ddce343b9e04c705ff04813920b.svg)
![$y_a = \sin(a) x + \cos(a) y$](https://habrastorage.org/getpro/habr/formulas/0d5/c78/e1d/0d5c78e1db502015020044c607342938.svg)
Двойной угол по этой формуле:
![$m = x x - y y = x ^ 2 - y ^ 2$](https://habrastorage.org/getpro/habr/formulas/0c4/f57/cb6/0c4f57cb627c731cdb73184432ae49ca.svg)
![$n = y x + x y = 2 x y$](https://habrastorage.org/getpro/habr/formulas/1d5/715/c78/1d5715c78d865681afd1971101cddffa.svg)
Значит
![$m = x ^ 2 - (1 - x ^ 2)$](https://habrastorage.org/getpro/habr/formulas/241/174/18c/24117418ce5939c505055d9d36fa4c7a.svg)
![$m = 2 x ^ 2 - 1$](https://habrastorage.org/getpro/habr/formulas/237/ebf/b08/237ebfb0869cd6e029ad7fae10ea4c38.svg)
И исходя из того что существует разница координат![$d$](https://habrastorage.org/getpro/habr/formulas/35e/a85/36b/35ea8536b3e6152e60442ccecbc46812.svg)
![$m = x - d$](https://habrastorage.org/getpro/habr/formulas/b64/09f/291/b6409f291521ab48170c8ad5a3878f1c.svg)
Получим квадратное уравнение
![$2 x ^ 2 - 1 = x - d$](https://habrastorage.org/getpro/habr/formulas/436/b90/f10/436b90f10380654d29a48e8988269610.svg)
![$2 x ^ 2 - x - (1 - d) = 0$](https://habrastorage.org/getpro/habr/formulas/4ec/16c/cf5/4ec16ccf57dbb9e5cb4e3a3efcc6c784.svg)
Которое решается
![$\\x = \left (1 \pm \sqrt{1 + 8(1 - d)}\right )/4$](https://habrastorage.org/getpro/habr/formulas/35f/cd8/a7d/35fcd8a7d992d10e610f7a92479378d8.svg)
Это говорит о том, что существует как положительное так и отрицательное значение координаты
, при которых различие координат двойного и одинарного угла
то же самое.
![](https://habrastorage.org/r/w1560/webt/94/ck/nl/94cknlncxymcwpihyqexxgplwxc.png)
У десятиугольника такая симметрия, что разница горизонтальной координаты между одинарным и двойным углом при увеличении углов в три раза, то есть, между тройным углом и шестикратным, сохраняет точно такую же величину. Абсолютные значения координат m и x в парном решении меняются местами и меняют свой знак, оставляя значение у разницы тем же самым. Так что, можно связать второе решение квадратного уравнения с тройным углом.
Используя что
![$m_1 = x_1 - d = -x_2$](https://habrastorage.org/getpro/habr/formulas/9ae/5ef/615/9ae5ef615bdad337a2c6939bcea781f7.svg)
![$m_2 = x_2 - d = -x_1$](https://habrastorage.org/getpro/habr/formulas/e26/4fb/605/e264fb60540414d1380b9ac2e764b3c9.svg)
Получим
![$\\d = x_1 + x_2 = 1 / 2$](https://habrastorage.org/getpro/habr/formulas/d1e/cba/d9f/d1ecbad9fe8154978b42e6b6d02f255b.svg)
И сразу получим остальные значения.
![$\\x = (1 + \sqrt{5})/ 4 \;\;\;\;\;\;\;\;\;\; = \varPhi / 2 \;\;\;\;\;\;\;\;\;\;\;\; = \varphi / 2 + 1 / 2$](https://habrastorage.org/getpro/habr/formulas/33b/591/1f6/33b5911f6b51a8428d59912530493051.svg)
![$y = \sqrt{(5 - \sqrt{5}) / 2} / 2 \;\;\; = \sqrt{3 - \varPhi} / 2 \;\;\;\; = \sqrt{2 - \varphi} / 2$](https://habrastorage.org/getpro/habr/formulas/3c5/e1f/ee3/3c5e1fee32b2f08d661413425fd5c5b9.svg)
![$m = (\sqrt{5} - 1)/ 4 \;\;\;\;\;\;\;\;\; = \varPhi / 2 - 1 / 2 \;\;\;\; = \varphi / 2 \;\;\;\;\;\;\;\;\;\;$](https://habrastorage.org/getpro/habr/formulas/ae0/cef/cf6/ae0cefcf69162ce9a54ff956ec8647da.svg)
![$n = \sqrt{(5 + \sqrt{5}) / 2} / 2\;\;\; = \sqrt{2 + \varPhi} / 2 \;\;\;\; = \sqrt{3 + \varphi} / 2$](https://habrastorage.org/getpro/habr/formulas/3a8/57a/9dc/3a857a9dc05b34fe19987536452d6c25.svg)
Число
это малый коэффициент золотого сечения.
Число
это большой коэффициент золотого сечения.
Их основные свойства:
![$\varphi ^ 2 = 1 - \varphi$](https://habrastorage.org/getpro/habr/formulas/44a/4ca/335/44a4ca3350714bb007b0ebe6b49e2d32.svg)
![$\varphi + 1 = \varPhi = 1 / \varphi$](https://habrastorage.org/getpro/habr/formulas/363/9c4/464/3639c44643f492bf3fbeb82bcdafeeb3.svg)
![$1 + \varPhi = \varPhi ^ 2$](https://habrastorage.org/getpro/habr/formulas/2fd/df9/e47/2fddf9e4785594b4d42ce3e22525afe5.svg)
![$\varphi + \varPhi = \sqrt{5}$](https://habrastorage.org/getpro/habr/formulas/638/655/95d/63865595d8ff9d9f091412dc552ea829.svg)
![$2\varphi + 1 = \sqrt{5} = 2\varPhi - 1$](https://habrastorage.org/getpro/habr/formulas/735/5b6/297/7355b6297db0b755e9714fce06abf765.svg)
Координатная система в которой координаты целые числа, и при этом можно делать повороты на 36° определяется так:
![$\{n_1,n_2,n_3,n_4\}=(n_1\cdot C_{xa}+n_2 \cdot C_{xb},n_3 \cdot C_{ya} + n_4 \cdot C_{yb})$](https://habrastorage.org/getpro/habr/formulas/8dd/149/c07/8dd149c073a7fb46c9446689ffc5f9c6.svg)
Используемые константы равны
![$C_{xa} = 1 / 2$](https://habrastorage.org/getpro/habr/formulas/61d/e1d/04a/61de1d04a5e64e68f52ca4d051f0603d.svg)
![$C_{xb} = \varphi / 2$](https://habrastorage.org/getpro/habr/formulas/1f3/fce/980/1f3fce980131641be617a38ca5fde829.svg)
![$C_{ya} = \sqrt{3 + \varphi} / 2$](https://habrastorage.org/getpro/habr/formulas/fcd/07f/4cb/fcd07f4cba0eb7585ae1a7bf70f0cfad.svg)
![$C_{yb} = \sqrt{2 - \varphi} / 2$](https://habrastorage.org/getpro/habr/formulas/35b/db9/029/35bdb90298266351f6f705d35e0ec171.svg)
Это позволяет представить три базовых вектора
как
![$(2 \cdot C_{xa}\;\;\;\;\;\;,0\;\;\;) = \{2,0,0,0\}$](https://habrastorage.org/getpro/habr/formulas/ad8/415/5f6/ad84155f683095d2403486f2440a5f90.svg)
![$(C_{xa}+C_{xb},C_{yb}) = \{1,1,0,1\}$](https://habrastorage.org/getpro/habr/formulas/ff6/51a/f2f/ff651af2f09ddcdb5473c6de262e0036.svg)
![$(C_{xb}\;\;\;\;\;\;\;\;\;\;,C_{ya}) = \{0,1,1,0\}$](https://habrastorage.org/getpro/habr/formulas/669/ad1/eb7/669ad1eb7d5005e5d08dffc77c88af57.svg)
![](https://habrastorage.org/r/w1560/webt/m9/50/xa/m950xaltid59a4cus0d6q-cukj8.png)
При комбинации единичных векторов групповая чётность координат сохраняется, и может быть только одного из следующих типов:
.
То есть, на координаты накладываются ограничения
![$x = k_1 + k_2\varphi \;\;\;\;\;\;\;\;\;\;\;\leftrightarrow y = k_3 K_3 + k_4 K_2$](https://habrastorage.org/getpro/habr/formulas/262/b1c/07f/262b1c07f92c52317232c4d9c8d7a3f4.svg)
![$x = k_1 + k_2\varphi \pm \frac{1\pm\varphi}{2} \leftrightarrow y = k_3 K_3 + k_4 K_2 \pm \frac{K_2}{2}$](https://habrastorage.org/getpro/habr/formulas/2f5/88d/565/2f588d5656ea39c45a2911e2d4a3ded1.svg)
![$x = k_1 + k_2\varphi \pm \;\; \frac{\varphi}{2} \;\;\leftrightarrow y = k_3 K_3 + k_4 K_2 \pm \frac{K_3}{2}$](https://habrastorage.org/getpro/habr/formulas/7d0/719/113/7d0719113317643e99b82d8bb8657f1e.svg)
![$x = k_1 + k_2\varphi \pm \;\;\frac{1}{2} \;\;\leftrightarrow y = k_3 K_3 + k_4 K_2 \pm \frac{K_3 \pm K_2}{2}$](https://habrastorage.org/getpro/habr/formulas/5ce/8cc/631/5ce8cc631520dc7471e5d383f82d922e.svg)
где
— целые числа.
![$K_3 = 2 C_{ya} = \sqrt{3 + \varphi}$](https://habrastorage.org/getpro/habr/formulas/e1b/0fa/9c3/e1b0fa9c363a09de7cb711a4b7d30a6c.svg)
![$K_2 = 2 C_{yb} = \sqrt{2 - \varphi}$](https://habrastorage.org/getpro/habr/formulas/aff/d91/f97/affd91f978840a4c96d8cb62b01cc1b8.svg)
![$K_3 K_2 = \sqrt{5}$](https://habrastorage.org/getpro/habr/formulas/313/1ef/042/3131ef0429640965f43fdedec1dc63dc.svg)
![$K_3 \varphi = K_2$](https://habrastorage.org/getpro/habr/formulas/668/2ec/770/6682ec7701b1b93f54b6eebec6816deb.svg)
Преобразуя таблицу умножения
можно получить целочисленную трёхмерную матрицу для умножения векторов.
Используя эту координатную систему мы позиционируем точки с идеальной точностью целых чисел.
Еще немного теории о связи чисел: степень числа фи и последовательность фибоначчи.
При всей схожести формул
![$\varPhi^{n - 1} + \varPhi^{n} = \varPhi^{n + 1}$](https://habrastorage.org/getpro/habr/formulas/650/222/f35/650222f3590fa1c77a04c8393c527e66.svg)
![$\text{Ф}_{n - 1} + \text{Ф}_{n} = \text{Ф}_{n + 1}$](https://habrastorage.org/getpro/habr/formulas/2ac/4a5/61b/2ac4a561b4e05178aa048a4b9f6cfa6e.svg)
они, конечно, различаются.
Функция
это степенная функция, которая строго больше нуля. А в последовательности фибоначчи
присутствует ноль, и из-за этого все предшествующие ему числа чередуют знак.
Для решения уравнения
подходит не только
, но и
. Причем, они подходят одновременно.
. Если
и
, то коэффициенты
.
Так что, существует формула, которая связывает последовательность фибоначчи и степенную функцию числа фи (формула бине):
Для чисел с положительными номерами значение задается преимущественно левым слагаемым, и правое выравнивает до целого числа. Для чисел с отрицательным номером основной вклад идёт от правого слагаемого.
Обратная формула, получения степени числа
из последовательности фибоначчи:
![$\varPhi ^ {n} = \text{Ф}_{n-1} + \text{Ф}_{n}\varPhi$](https://habrastorage.org/getpro/habr/formulas/154/0b1/98d/1540b198dc71e8d7d8452f9a6c069abd.svg)
Для числа
, которое в высоких степенях приближается к нулю, используется различие знака чисел с отрицательными номерами и примерное равенство соотношения соседних чисел самому числу
.
![$\varphi ^ {n} = \text{Ф}_{-n+1} + \text{Ф}_{-n}\varphi$](https://habrastorage.org/getpro/habr/formulas/812/6ac/6b0/8126ac6b0bc1b069a9d4c966bb034ff2.svg)
В системе целых координат это находит выражение в том, что базовые вектора с множителем
выражаются в целых коэффициентах, взятых из последовательности фибоначчи:
![$\varphi^n\;V_0 = \{2\text{Ф}_{-n+1},2\text{Ф}_{-n}\;\;,0\;\;\;\;\;\;\;\;,0\;\;\;\;\;\;\}$](https://habrastorage.org/getpro/habr/formulas/6cb/580/6ae/6cb5806aeb41701fcb6f3e29b53abdbb.svg)
![$\varphi^n\;V_1 = \{\text{Ф}_{-n+2}\;\;,\text{Ф}_{-n+1},\text{Ф}_{-n}\;\;\;,\text{Ф}_{-n-1}\}$](https://habrastorage.org/getpro/habr/formulas/5ea/42f/9b9/5ea42f9b93ab5521ce6ed44791bed006.svg)
![$\varphi^n\;V_2 = \{\text{Ф}_{-n}\;\;\;\;\;,\text{Ф}_{-n-1},\text{Ф}_{-n+1},\text{Ф}_{-n}\;\;\;\}$](https://habrastorage.org/getpro/habr/formulas/2c4/015/93d/2c401593ddbf92b2eb7a2ab209478788.svg)
На такие вектора можно и делить.
Теперь можно попробовать составить треугольники.
Треугольников в котором углы кратны 36° не так много, всего два. Сумма углов в треугольнике 180°, в долях сумма углов должна быть равна пяти. Как 5 поделить на три целых числа? Единица должна быть, потому что без нее даже минимум, три двойки — это уже перебор. Оставшиеся 4 доли можно поделить только как 1 + 3 и 2 + 2. Оба треугольника равнобедренные, имеют в себе пару одинаковых углов.
Обозначим треугольники как T1 и T2, в соответствии размера в долях того угла который повторяется.
![](https://habrastorage.org/r/w1560/webt/tq/_y/fm/tq_yfmgseikxfzqdjtsy-przpii.png)
Теперь можно попробовать треугольники разбить.
Треугольник T1 можно поделить на два треугольника: Т1 и Т2.
![](https://habrastorage.org/r/w1560/webt/iw/pj/ii/iwpjiiry5w7rqlo0rszo7vphqpq.png)
Треугольник T2 можно поделить на два треугольника: T1 и T2.
![](https://habrastorage.org/r/w1560/webt/5z/wc/co/5zwccooknlvrmhyuyfmdab3prmk.png)
Треугольник T1 можно поделить еще и на три треугольника: T1, T2, T1. Причем, такого разбиения два: симметричное и антисимметричное.
![](https://habrastorage.org/r/w1560/webt/0v/dd/js/0vddjsk5zudhgt6bbvs94c5hcxq.png)
Эти разбиения уменьшают боковые стороны треугольников на один и тот же коэффициент
.
Из таких треугольников можно построить мозаику пенроуза.
Для построения можно исходить из следующих правил:
1. Для каждого уровня разбиения все треугольники имеют одинаковую длину боковых сторон, а основанием соединены с таким же треугольником, который на более детальный уровень разбивается симметрично исходному. Именно из-за этого правила мозаику пенроуза можно представлять равносторонними ромбами.
2. Для разбиения треугольника T1 используется только несимметричный вариант.
Именно поэтому мозаику пенроуза можно представлять разбиением на дельтоиды:
У получившегося T1 обязательно есть парный треугольник, который образует с исходным уголок, «дротик» (dart). А у получившегося T2 парный треугольник вместе с соседним образуют выпуклый дельтоид «воздушный змей» (kite).
![](https://habrastorage.org/r/w1560/webt/mq/lm/ae/mqlmaedyoysgulvp-g5zk4dpkxa.png)
Хотя сами по себе могут быть получены делением ромба, дельтоиды в мозаике расположены так, что никогда не соединяются в ромб. У сложной формы дельтоидов есть преимущество: ромб при одной форме может иметь два возможных направления, а у дельтоидов оно задано явно.
Если у нас соединены треугольники Т1 и Т2, то дальнейшее построение имеет два варианта: либо они вместе образуют Т2 и значит на общем уровне он имеет свое отражение. Либо Т1 и Т2 составляют вместе часть разбиения Т1, и тогда продолжение выглядит менее симметрично.
![](https://habrastorage.org/r/w1560/webt/pl/gb/wp/plgbwpwibkgesn2dkg47h5mtaim.png)
Поэтому мозаику пенрозуа удобнее строить не достраиванием во вне, а делением внутрь.
Относительно деления треугольники различаются не только по форме, но и по направлению симметрии. «Правый» и «Левый» треугольники режутся противоположным образом. Поэтому надо сразу выяснить, какого типа треугольники получаются при самом делении.
Мы получим правила
![](https://habrastorage.org/r/w1560/webt/jp/zt/4l/jpzt4lvjr-szkbjouyhqv1qiipg.png)
![$T_{1A} \rightarrow T_{1A}+T_{2A}+T_{1B}$](https://habrastorage.org/getpro/habr/formulas/b57/e5b/53c/b57e5b53cd28fedad704aef3edd4a376.svg)
![$T_{1B} \rightarrow T_{1B}+T_{2B}+T_{1A}$](https://habrastorage.org/getpro/habr/formulas/f3a/1ea/c66/f3a1eac669b3c12a6966ce88693e0371.svg)
![$T_{2B} \rightarrow T_{1A}+T_{2B}$](https://habrastorage.org/getpro/habr/formulas/98a/4b3/336/98a4b3336466096b7a7bd07f718f8d6c.svg)
![$T_{2A} \rightarrow T_{1B}+T_{2A}$](https://habrastorage.org/getpro/habr/formulas/eb4/c7c/b2d/eb4c7cb2d7bad70b4927cd091d252f7b.svg)
Получается, у ромбов четыре вида сторон: те которые при разбиении делятся — со сдвинутым местом разбиения вправо или влево, и те, которые не делятся, а образуют диагональ для Т1 — либо правого, либо левого. Зная какие стороны стыкуются можно раскрашивать плитки мозаики так, чтобы линии рисунка стыковались на границах, и тогда получаются симпатичные узоры.
У мозаики пенроуза несколько представлений, но как универсальный можно использовать вид из шести треугольников, четыре из которых повторяют показанное разбиение из треугольников, а два дополнительных, вида Т1, аналогичны по разбиению первому и второму, но отличаются тем, что на предыдущем уровне разбиения были частью Т1 точно такого же типа — левый из левого, правый из правого.
![](https://habrastorage.org/r/w1560/webt/ss/e1/l8/sse1l8-eu9h3wusye-dj93llude.png)
Такие треугольники можно объединять в различные виды мозаики: и в ромбы, и в дельтоиды, и в различающиеся стороной треугольники, и в фигуры HBS. А для представления Р1, для представления из прямых линий и для набора четырёхугольников нужно сопоставить двум базовым треугольникам линии разбиения.
Список представлений:
Чтобы изобразить на компьютере мозаику достаточно в текстовом редакторе написать html-страничку с кодом на javascript и открыть эту страничку в браузере.
Весь текст странички без кода:
Получаем «контекст», объектный интерфейс для рисования на плоскости:
Программа будет рассчитывать фигуры, а потом их отрисовывать в зависимости от заданных настроек.
Функции для упрощения команд рисования:
Мозаика будет состоять из фигур, они хранятся как массив данных: тип фигуры, от 0 до 5, координаты угла привязки, массив четырех целых чисел, и направление, 0 до 9. В функции отрисовки масштаб координат и размер шага рисования стороны фигуры задаются отдельно.
Сначала зададим базовую фигуру из шести треугольников.
Осталось задать режимы, как именно фигуры могут отображаться.
Код можно скопировать со статьи в файл фрагмент за фрагментом и он заработает.
Так как каждый треугольник имеет парный треугольник (кроме тех, которые на границе), то для лучшей прорисовки можно добавить режим, в котором один из пары треугольников отображается ромбом, а второй не отображается. Так же можно сделать и с дельтоидами и с треугольником объединенным из двух.
Для разбиения P1 нужно задать по две линии на треугольник.
![](https://habrastorage.org/r/w1560/webt/p1/ly/td/p1lytdtnwdzazxt4skivxybse9y.png)
![](https://habrastorage.org/r/w1560/webt/zu/5p/gs/zu5pgsfxwhfj0i_dhhxojlze5ka.png)
![](https://habrastorage.org/r/w1560/webt/zv/mn/mo/zvmnmoewvnndw6fmw2nkl5i4jdu.png)
![](https://habrastorage.org/r/w1560/webt/vu/wq/1-/vuwq1-n3ihcniorgpcl58zavf_m.png)
Для представления мозаики из линий нужно задать следующее разбиение:
![](https://habrastorage.org/r/w1560/webt/14/hf/ol/14hfolotdv-m0bcw5y9lyiemboy.png)
Через три уровня линии повторяются:
![](https://habrastorage.org/r/w1560/webt/nr/97/lc/nr97lcrwpzx1_hw8m9an_epfb74.png)
![](https://habrastorage.org/r/w1560/webt/qi/fy/fi/qifyfify2rkrulfupcyj8vtpnsy.png)
![](https://habrastorage.org/r/w1560/webt/ts/kb/fq/tskbfqqlpjqwctpkjxau7vz-zky.png)
Из этого можно вывести коэффициенты пропорций деления линией сторон в месте пересечения. После смещения на три уровня сторона треугольника становится
. Решая уравнение
получим
, и коэффициент будет равен
.
Получаются пропорции деления:
— деление стороны посередине.
— деление единичной диагонали.
— проекция диагонали на сторону.
— первая проекция на диагональ широкого ромба.
— вторая проекция на диагональ широкого ромба.
— проекция на диагональ узкого ромба
Оказывается, что треугольники, у которых совпадают углы привязки образуют фигуры HBS. И для отрисовки разбиения на HBS достаточно выводить дальнюю грань. Если треугольники масштабировать без изменения привязки, то вокруг фигур образуется пустое пространство. В этом пустом пространстве можно вывести фигуры предыдущего уровня, смасштабировав так, чтобы они касались фигур этого уровня. Именно так нарисовано первое изображение статьи.
![](https://habrastorage.org/r/w1560/webt/7w/5i/et/7w5ietl5ejxgcxxmmjklneu5s4c.png)
![](https://habrastorage.org/r/w1560/webt/nk/rh/vc/nkrhvcebjvoz9mrngledbg2bwza.png)
![](https://habrastorage.org/r/w1560/webt/1s/bd/ir/1sbdirofowie9hlqcn9tqzvfuyk.png)
![](https://habrastorage.org/r/w1560/webt/zj/wi/zm/zjwizmpetc4vkz0fpit5mc1zqiy.png)
![](https://habrastorage.org/r/w1560/webt/m9/kc/-9/m9kc-9wtlzocrjdkd_kqcbqgi3k.png)
Для вывода отображения из семи видов четырехугольников отрисовывается основание каждого треугольника и высота.
![](https://habrastorage.org/r/w1560/webt/l0/-b/jo/l0-bjojkqzo0gxb0-4uehd1gfts.png)
![](https://habrastorage.org/r/w1560/webt/kv/qx/b3/kvqxb3etury0ez2vheu3rs58k4c.png)
![](https://habrastorage.org/r/w1560/webt/n8/2u/e6/n82ue6-qp1hvbani3yvagfjwrmo.png)
![](https://habrastorage.org/r/w1560/webt/ms/t7/65/mst765wr7ccnnrofxxn6vaa6ntu.png)
По-моему, очень красиво.
В ней плоскость не делится на десятиугольники, вместо этого все линии расположены под углами кратными 36°. Координаты в этой системе можно записывать целыми числами, по два целых числа на горизонтальное и вертикальное направление.
![](https://habrastorage.org/webt/gf/xz/73/gfxz73dxe0-owjktjad1d-ohf3o.png)
Расскажу как это нарисовать.
Количество единичных векторов по всем направлениям в этой системе отсчета десять. Если учесть симметрию обратного направления, то пять. Если учесть горизонтальную симметрию, то три. Обозначим их как
Обычная формула расчета координат при повороте:
Двойной угол по этой формуле:
Значит
И исходя из того что существует разница координат
Получим квадратное уравнение
Которое решается
Это говорит о том, что существует как положительное так и отрицательное значение координаты
![](https://habrastorage.org/webt/94/ck/nl/94cknlncxymcwpihyqexxgplwxc.png)
У десятиугольника такая симметрия, что разница горизонтальной координаты между одинарным и двойным углом при увеличении углов в три раза, то есть, между тройным углом и шестикратным, сохраняет точно такую же величину. Абсолютные значения координат m и x в парном решении меняются местами и меняют свой знак, оставляя значение у разницы тем же самым. Так что, можно связать второе решение квадратного уравнения с тройным углом.
Используя что
Получим
И сразу получим остальные значения.
Число
Число
Их основные свойства:
Координатная система в которой координаты целые числа, и при этом можно делать повороты на 36° определяется так:
Используемые константы равны
Это позволяет представить три базовых вектора
![](https://habrastorage.org/webt/m9/50/xa/m950xaltid59a4cus0d6q-cukj8.png)
При комбинации единичных векторов групповая чётность координат сохраняется, и может быть только одного из следующих типов:
То есть, на координаты накладываются ограничения
где
Преобразуя таблицу умножения
* | 1 | φ | K3 | K2 |
---|---|---|---|---|
1 | 1 | φ | K3 | K2 |
φ | φ | 1-φ | K2 | K3-K2 |
K3 | K3 | K2 | 3+φ | 1+2φ |
K2 | K2 | K3-K2 | 1+2φ | 2-φ |
Используя эту координатную систему мы позиционируем точки с идеальной точностью целых чисел.
Еще немного теории о связи чисел: степень числа фи и последовательность фибоначчи.
При всей схожести формул
они, конечно, различаются.
Функция
Для решения уравнения
Так что, существует формула, которая связывает последовательность фибоначчи и степенную функцию числа фи (формула бине):
Для чисел с положительными номерами значение задается преимущественно левым слагаемым, и правое выравнивает до целого числа. Для чисел с отрицательным номером основной вклад идёт от правого слагаемого.
Обратная формула, получения степени числа
Для числа
В системе целых координат это находит выражение в том, что базовые вектора с множителем
На такие вектора можно и делить.
Теперь можно попробовать составить треугольники.
Треугольников в котором углы кратны 36° не так много, всего два. Сумма углов в треугольнике 180°, в долях сумма углов должна быть равна пяти. Как 5 поделить на три целых числа? Единица должна быть, потому что без нее даже минимум, три двойки — это уже перебор. Оставшиеся 4 доли можно поделить только как 1 + 3 и 2 + 2. Оба треугольника равнобедренные, имеют в себе пару одинаковых углов.
Обозначим треугольники как T1 и T2, в соответствии размера в долях того угла который повторяется.
![](https://habrastorage.org/webt/tq/_y/fm/tq_yfmgseikxfzqdjtsy-przpii.png)
Теперь можно попробовать треугольники разбить.
Треугольник T1 можно поделить на два треугольника: Т1 и Т2.
![](https://habrastorage.org/webt/iw/pj/ii/iwpjiiry5w7rqlo0rszo7vphqpq.png)
Треугольник T2 можно поделить на два треугольника: T1 и T2.
![](https://habrastorage.org/webt/5z/wc/co/5zwccooknlvrmhyuyfmdab3prmk.png)
Треугольник T1 можно поделить еще и на три треугольника: T1, T2, T1. Причем, такого разбиения два: симметричное и антисимметричное.
![](https://habrastorage.org/webt/0v/dd/js/0vddjsk5zudhgt6bbvs94c5hcxq.png)
Эти разбиения уменьшают боковые стороны треугольников на один и тот же коэффициент
Из таких треугольников можно построить мозаику пенроуза.
Для построения можно исходить из следующих правил:
1. Для каждого уровня разбиения все треугольники имеют одинаковую длину боковых сторон, а основанием соединены с таким же треугольником, который на более детальный уровень разбивается симметрично исходному. Именно из-за этого правила мозаику пенроуза можно представлять равносторонними ромбами.
2. Для разбиения треугольника T1 используется только несимметричный вариант.
Именно поэтому мозаику пенроуза можно представлять разбиением на дельтоиды:
У получившегося T1 обязательно есть парный треугольник, который образует с исходным уголок, «дротик» (dart). А у получившегося T2 парный треугольник вместе с соседним образуют выпуклый дельтоид «воздушный змей» (kite).
![](https://habrastorage.org/webt/mq/lm/ae/mqlmaedyoysgulvp-g5zk4dpkxa.png)
Хотя сами по себе могут быть получены делением ромба, дельтоиды в мозаике расположены так, что никогда не соединяются в ромб. У сложной формы дельтоидов есть преимущество: ромб при одной форме может иметь два возможных направления, а у дельтоидов оно задано явно.
Если у нас соединены треугольники Т1 и Т2, то дальнейшее построение имеет два варианта: либо они вместе образуют Т2 и значит на общем уровне он имеет свое отражение. Либо Т1 и Т2 составляют вместе часть разбиения Т1, и тогда продолжение выглядит менее симметрично.
![](https://habrastorage.org/webt/pl/gb/wp/plgbwpwibkgesn2dkg47h5mtaim.png)
Поэтому мозаику пенрозуа удобнее строить не достраиванием во вне, а делением внутрь.
Относительно деления треугольники различаются не только по форме, но и по направлению симметрии. «Правый» и «Левый» треугольники режутся противоположным образом. Поэтому надо сразу выяснить, какого типа треугольники получаются при самом делении.
Мы получим правила
![](https://habrastorage.org/webt/jp/zt/4l/jpzt4lvjr-szkbjouyhqv1qiipg.png)
Получается, у ромбов четыре вида сторон: те которые при разбиении делятся — со сдвинутым местом разбиения вправо или влево, и те, которые не делятся, а образуют диагональ для Т1 — либо правого, либо левого. Зная какие стороны стыкуются можно раскрашивать плитки мозаики так, чтобы линии рисунка стыковались на границах, и тогда получаются симпатичные узоры.
У мозаики пенроуза несколько представлений, но как универсальный можно использовать вид из шести треугольников, четыре из которых повторяют показанное разбиение из треугольников, а два дополнительных, вида Т1, аналогичны по разбиению первому и второму, но отличаются тем, что на предыдущем уровне разбиения были частью Т1 точно такого же типа — левый из левого, правый из правого.
![](https://habrastorage.org/webt/ss/e1/l8/sse1l8-eu9h3wusye-dj93llude.png)
Такие треугольники можно объединять в различные виды мозаики: и в ромбы, и в дельтоиды, и в различающиеся стороной треугольники, и в фигуры HBS. А для представления Р1, для представления из прямых линий и для набора четырёхугольников нужно сопоставить двум базовым треугольникам линии разбиения.
Универсальный вид![](https://habrastorage.org/r/w1560/webt/4o/my/l_/4omyl_gak0rqdephyxgsxio1l_8.png)
![](https://habrastorage.org/webt/4o/my/l_/4omyl_gak0rqdephyxgsxio1l_8.png)
Список представлений:
- Из двух треугольников (четырёх видов) с одинаковыми боковыми сторонами
- Из двух треугольников (четырёх видов) с различными боковыми сторонами
- Из пары ромбов, представление Р3
- Из ромба и уголка двух видов
- Из пары дельтоидов, представление Р2: дротик/воздушный змей
- Представление P1: пятиугольники, звезда, лодочка, ромб
- Представление «шестиугольник, лодка, звезда», HBS
- Особое представление: просто пересекающиеся прямые линии
- Семь четырёхугольников (Семь видов четырехугольников, количество форм только шесть)
Чтобы изобразить на компьютере мозаику достаточно в текстовом редакторе написать html-страничку с кодом на javascript и открыть эту страничку в браузере.
Весь текст странички без кода:
<html><canvas id="a" width="640" height="320"></canvas><script>
</script></html>
Получаем «контекст», объектный интерфейс для рисования на плоскости:
var canvas = document.getElementById("a");
var b = canvas.getContext("2d");
Программа будет рассчитывать фигуры, а потом их отрисовывать в зависимости от заданных настроек.
Функции для упрощения команд рисования:
// начать описание контура
function begin(){b.beginPath();}
// начать линию с точки
function from(p) {b.moveTo(s[8] + p[0], s[9] - p[1]);}
// привести линию к точке
function to(p){b.lineTo(s[8] + p[0], s[9] - p[1]);}
// привести линию к начальной точке, не требуется если выполняется только заливка
function close(){b.closePath();}
// заливка
function fill(color){b.fillStyle = color; b.fill();}
// обводка контура линией
function line(){b.strokeStyle = "#444"; b.lineWidth = 0.5; b.stroke();}
function line_white(){b.strokeStyle = "#fff"; b.lineWidth = 1; b.stroke();}
function line_black(){b.strokeStyle = "#444"; b.lineWidth = 1.5; b.stroke();}
Мозаика будет состоять из фигур, они хранятся как массив данных: тип фигуры, от 0 до 5, координаты угла привязки, массив четырех целых чисел, и направление, 0 до 9. В функции отрисовки масштаб координат и размер шага рисования стороны фигуры задаются отдельно.
Прежде всего нужно задать используемые константы.
var s;
function prepare()
{
var sqrt = Math.sqrt;
var fi = (sqrt(5) - 1) / 2;
var fb = (sqrt(5) + 1) / 2;
var f3 = sqrt(3 + fi);
var f2 = sqrt(2 - fi);
//координаты базовых векторов для всех десяти направлений
var vt = [[ 2, 0, 0, 0], [ 1, 1, 0, 1], [ 0, 1, 1, 0], [ 0,-1, 1, 0], [-1,-1, 0, 1],
[-2, 0, 0, 0], [-1,-1, 0,-1], [ 0,-1,-1, 0], [ 0, 1,-1, 0], [ 1, 1, 0,-1]];
// Константы множители координат
var c = [1/2, fi/2, f3/2, f2/2]
// Общий массив констант
// нулевой элемент для контекста
// седьмой для дополнительных данных рисования.
// восьмой и девятый для указания центра рисования
// десятый для размера шага.
var s = [0, vt, c, fi, f3, f2, 0, 0, 0, 0];
return s;
}
s = prepare();
s[0] = b;
s[7] = 1; // доля последнего уровня
s[8] = 500/2; // сдвих координаты x
s[9] = 320/2; // сдвиг координаты y
Сначала зададим базовую фигуру из шести треугольников.
Код заполнения первого уровня
var f = []; // слои разбиения.
f[0] = [];
f[0].push([0,[ 0, 0, 0, 0],0]);
f[0].push([1,[ 0, 0, 0, 0],0]);
f[0].push([2,[ 0, 0, 0, 0],3]);
f[0].push([3,[ 0, 0, 0, 0],3]);
f[0].push([2,[ 0, 0, 0, 0],7]);
f[0].push([3,[ 0, 0, 0, 0],7]);
Код расчета и отрисовки уровней
fi = s[3]; // берем из констант коэффициент
var levels = 3; // количество расчетных уровней
s[7] = 0.1 * 10; // степень проявления уровня
s[10] = 24 * 6 * fi * fi; // длина линии
//s[10] = 24 * 6 * fi
//s[10] = 24 * 6;
// для отображения полного поля нужно изменить размер шага, размер канвы, место центра и поменять местами p[0] и p[1] в функциях from() и to().
//s[10] = 24;
mode = 12; // режим рисования
// разбиение
var n = 0, m;
for(; n < levels; n++)
{ m = n + 1;
f[m] = [];
for(var k = 0; k < f[n].length; k++)
zd(f[n][k], s, f[m]);
}
// отображение
n = m - 1;
// предыдущий уровень
if(s[7] != 1)
for(var i = 0; i < f[n].length; i++) {paint(f[n][i], mode, 1);}
// последний уровень
for(var i = 0; i < f[m].length; i++) {paint(f[m][i], mode, 0);}
// Для 11 режима подчеркиваются линии
if(mode == 11) {d = 3; for(var i = 0; i < f[m-d].length; i++) {paint(f[m-d][i], mode, d);}}
Функция разбиения
function zd(a, s, f)
{
var t = a[0]; // тип фигуры
var vt = s[1]; // таблица векторов
if (t > 3) t = t - 4; // типы фигур 4 и 5 обрабатываются как 0 и 1
// направление первого шага в зависимости от типа фигуры, в виде смещения направления
sht = [ 1,-1, 2,-2];
var shift = sht[t];
if(t == 0) {t1 = 0; t2 = 3; t3 = 5;} // типы получившихся фигур
else if(t == 1) {t1 = 1; t2 = 2; t3 = 4;}
else if(t == 2) {t1 = 4; t2 = 2;}
else if(t == 3) {t1 = 5; t2 = 3;}
if (t < 2)
{
pos = a[1];
v1 = a[2]; // общее направление
v2 = (v1 + shift + 10) % 10; // направление первого шага
v3 = (v1 - shift + 10) % 10; // направление второго шага
v4 = (v2 + 5) % 10; // обратное направление первому
v5 = (v1 + 5) % 10; // обратное направление общему (не второму)
p1 = add(pos, vt[v2]); // позиция после первого шага
p2 = add(p1, vt[v3]); // позиция после второго шага
p3 = mul(p1,[2,2,0,0]); // масштабирование
p4 = mul(p2,[2,2,0,0]); // масштабирование
f.push([t1, p3, v4]);
f.push([t2, p3, v3]);
f.push([t3, p4, v5]);
}
else
{
pos = a[1];
v1 = a[2];
v2 = (v1 + shift + 10) % 10; // направление первого шага
v3 = (v1 - shift + 10) % 10; // направление второго шага
v4 = (v2 + 5) % 10; // обратное направление первому
v5 = (v3 + 5) % 10; // обратное направление второму
p1 = add(pos, vt[v2]); // позиция после первого шага
p2 = add(p1, vt[v3]); // позиция после второго шага
p3 = mul(p1,[2,2,0,0]); // масштабирование
p4 = mul(p2,[2,2,0,0]); // масштабирование
f.push([t1, p3, v4]);
f.push([t2, p4, v5]);
}
return f;
}
Используемые функции сложения и умножения векторов
function mul(v1, v2)
{
mt = [[[1, 0, 0, 0],[0, 1, 0, 0],[ 0, 0, 1, 0],[ 0, 0, 0, 1]],
[[0, 1, 0, 0],[1,-1, 0, 0],[ 0, 0, 0, 1],[ 0, 0, 1,-1]],
[[0, 0, 1, 0],[0, 0, 0, 1],[-3, 1, 0, 0],[-1,-2, 0, 0]],
[[0, 0, 0, 1],[0, 0, 1,-1],[-1,-2, 0, 0],[-2, 1, 0, 0]]]
var v3 = [0, 0, 0, 0];
for(var i = 0; i < 4; i++)
for(var j = 0; j < 4; j++)
for(var k = 0; k < 4; k++)
v3[k] = v3[k] + v1[i] * v2[j] * mt[i][j][k];
for(var i = 0; i < 4; i++) v3[i] = v3[i] / 2;
return v3;
}
function add(v1, v2)
{ // нельзя к первому к первому аргументу добавить второй и вернуть,
// потому что аргументы принимаются по ссылке и значит будут изменены.
var v3 = [0, 0, 0, 0];
for(var i = 0; i < 4; i++) v3[i] = v3[i] + v1[i];
for(var i = 0; i < 4; i++) v3[i] = v3[i] + v2[i];
return v3;
}
Функция нахождения точки между двумя, с заданным коэффициентом.
function mean(p1, p2, d)
{ var p3 = [(p2[0] - p1[0]) * d + p1[0],(p2[1] - p1[1]) * d + p1[1]];
return p3;
}
Осталось задать режимы, как именно фигуры могут отображаться.
Функция отрисовки фигуры
function paint(a, mode, level = 0)
{
vt = s[1]; // таблица векторов
c = s[2]; // массив координатных констант
fi = s[3]; // константа фи
pr = s[7]; // доля проявления уровня
b = s[0]; // контекст рисования
var st = s[10];
// шесть цветов на выбор
colors = ["#BCE","#BBE","#ECE","#EBE","#CEF","#EEF"];
type = a[0]; // оригинальный тип фигуры
tn = type; // тип свернутый до 4
if(tn > 3) tn = tn - 4;
// цвет
color = colors[type];
// направление первого шага, в виде сдвига направления
sht = [ 1,-1, 2,-2];
var shift = sht[tn];
p = a[1]; // точка привязки
v0 = a[2]; // направление
v0 = (10 + v0 % 10) % 10; // направление выравнено в пределы 0-10
v1 = (10 + (v0 + shift) % 10) % 10; // направление первого шага
v2 = (10 + (v0 - shift) % 10) % 10; // направление второго шага
// коэффициенты масштабирования для позциции и для сторон.
var kop = 0;
var koe = 0;
pr1 = 1 - pr; // доля предыдущего уровня.
if(level == 0) {kop = st; koe = pr;}
if(level == 1) {kop = st / fi; koe = pr1 / fi; } // проступание соседнего уровня
if(level == 3) {kop = st / fi / fi / fi; koe = pr / fi / fi / fi; } // линии на три уровня меньше
st = st * koe; // масштабирование фигур.
// координаты начала линии
p0 = [kop * (p[0] * c[0] + p[1] * c[1]), kop * (p[2] * c[2] + p[3] * c[3])]
// координаты конца первой линии
s1 = vt[v1]; p1 = [p0[0] + st * (s1[0] * c[0] + s1[1] * c[1]), p0[1] + st * (s1[2] * c[2] + s1[3] * c[3])];
// координаты конца второй линии
s2 = vt[v2]; p2 = [p1[0] + st * (s2[0] * c[0] + s2[1] * c[1]), p1[1] + st * (s2[2] * c[2] + s2[3] * c[3])];
// таблица рисовать ли фон
modes = [1, 1,1,1,1,1, 0,0,0,0,1, 1,1,1];
y = modes[mode];
// заливка, сразу можно грани рисовать
if(level < 3) // если сдвинутый на три уровень, то фон не отрисовывается, только линии 11 режима.
if(y || mode == 0)
{ begin(); from(p0); to(p1); to(p2); close();
if(y) {fill(color);}
if(mode == 0) line();
if(mode == 12) line_white();
}
// четырехугольники
if(mode == 1)
{ p3 = mean(p0, p2, 0.5);
begin(); from(p0); to(p2); from(p1); to(p3); line_black();
}
// дальняя сторона, фигуры HBS
if(mode == 2)
{ begin(); from(p1); to(p2); line();
}
if(mode == 6) // ромбы
{ begin();
if(tn == 0 || tn == 2)
{ color = colors[tn * 2];
// четвертый угол ромба
p3 = mean(p0, p2, 0.5);
p4 = mean(p1, p3, 2);
from(p0); to(p1); to(p2); to(p4); close(); fill(color);
}
line();
}
if(mode == 7) // дельтоиды
{ if(type == 0)
{ // расчет дополнительного угла уголка
p3 = mean(p0, p1, 1 + fi);
p4 = mean(p2, p3, 1 + fi);
begin(); from(p0); to(p1); to(p4); to(p2); close(); fill(colors[0]); line();
}
if(type == 2)
{ // расчет углов фигуры воздушный змей
p3 = mean(p0, p2, 2 + fi)
p4 = [p0[0] + (p2[0] - p1[0]), p0[1] + (p2[1] - p1[1])];
begin(); from(p0); to(p1); to(p3); to(p4); close(); fill(colors[4]); line();
}
}
if(mode == 8) // разные треугольники
{ if(type < 2)
{ begin(); from(p0); to(p1); to(p2); close(); fill(colors[0]); line();
}
if(type == 4 || type == 5)
{ p3 = mean(p0, p1, 1 + fi);
begin(); from(p0); to(p3); to(p2); close(); fill(colors[4]); line();
}
}
if(mode == 9) // ромб и уголки
{ if(type == 0)
{ p3 = mean(p0, p1, 1 + fi);
p4 = mean(p2, p3, 1 + fi);
begin(); from(p0); to(p1); to(p4); to(p2); close(); fill(colors[0]); line();
}
if(type == 2)
{ p3 = [p0[0] - p1[0] + p2[0], p0[1] - p1[1] + p2[1]];
begin(); from(p0); to(p1); to(p2); to(p3); close(); fill(colors[4]); line();
}
if(type == 4)
{ p3 = mean(p2, p1, 1 + fi);
p4 = mean(p0, p3, 1 + fi);
begin(); from(p0); to(p4); to(p1); to(p2); close(); fill(colors[0]); line();
}
}
if(mode == 10)
{
p4 = mean(p1, p0, fi);
p5 = mean(p0, p2, fi);
p6 = mean(p2, p0, 1 / 2 + fi / 2);
p7 = mean(p1, p2, 0.5);
begin(); if(tn < 2) {from(p4); to(p5);} else {from(p6); to(p4);} to(p7); line();
}
if(mode == 11)
{
k1 = 1 / 2;
k2 = (fi + 1) / 2;
k3 = (4 - fi) / 4;
k4 = (fi + 1) / 4;
k5 = (3 - 2 * fi) / 2;
k6 = 1 / 4;
if(tn < 2)
{
p3 = mean(p0, p2, k4);
p4 = mean(p0, p1, k2);
p5 = mean(p1, p2, k1);
p6 = mean(p0, p2, k5);
p7 = mean(p1, p2, k3);
begin(); from(p3); to(p4); to(p5); to(p6); to(p7);
}
else
{
p3 = mean(p2, p1, k3);
p4 = mean(p0, p1, k2);
p5 = mean(p1, p2, k1);
p6 = mean(p2, p0, k6);
begin(); from(p3); to(p4); to(p5); to(p6);
}
line();
}
}
Код можно скопировать со статьи в файл фрагмент за фрагментом и он заработает.
Так как каждый треугольник имеет парный треугольник (кроме тех, которые на границе), то для лучшей прорисовки можно добавить режим, в котором один из пары треугольников отображается ромбом, а второй не отображается. Так же можно сделать и с дельтоидами и с треугольником объединенным из двух.
Для разбиения P1 нужно задать по две линии на треугольник.
![](https://habrastorage.org/webt/p1/ly/td/p1lytdtnwdzazxt4skivxybse9y.png)
![](https://habrastorage.org/webt/zu/5p/gs/zu5pgsfxwhfj0i_dhhxojlze5ka.png)
![](https://habrastorage.org/webt/zv/mn/mo/zvmnmoewvnndw6fmw2nkl5i4jdu.png)
![](https://habrastorage.org/webt/vu/wq/1-/vuwq1-n3ihcniorgpcl58zavf_m.png)
Для представления мозаики из линий нужно задать следующее разбиение:
![](https://habrastorage.org/webt/14/hf/ol/14hfolotdv-m0bcw5y9lyiemboy.png)
Через три уровня линии повторяются:
![](https://habrastorage.org/webt/nr/97/lc/nr97lcrwpzx1_hw8m9an_epfb74.png)
![](https://habrastorage.org/webt/qi/fy/fi/qifyfify2rkrulfupcyj8vtpnsy.png)
![](https://habrastorage.org/webt/ts/kb/fq/tskbfqqlpjqwctpkjxau7vz-zky.png)
Из этого можно вывести коэффициенты пропорций деления линией сторон в месте пересечения. После смещения на три уровня сторона треугольника становится
Получаются пропорции деления:
Оказывается, что треугольники, у которых совпадают углы привязки образуют фигуры HBS. И для отрисовки разбиения на HBS достаточно выводить дальнюю грань. Если треугольники масштабировать без изменения привязки, то вокруг фигур образуется пустое пространство. В этом пустом пространстве можно вывести фигуры предыдущего уровня, смасштабировав так, чтобы они касались фигур этого уровня. Именно так нарисовано первое изображение статьи.
![](https://habrastorage.org/webt/7w/5i/et/7w5ietl5ejxgcxxmmjklneu5s4c.png)
![](https://habrastorage.org/webt/nk/rh/vc/nkrhvcebjvoz9mrngledbg2bwza.png)
![](https://habrastorage.org/webt/1s/bd/ir/1sbdirofowie9hlqcn9tqzvfuyk.png)
![](https://habrastorage.org/webt/zj/wi/zm/zjwizmpetc4vkz0fpit5mc1zqiy.png)
![](https://habrastorage.org/webt/m9/kc/-9/m9kc-9wtlzocrjdkd_kqcbqgi3k.png)
Для вывода отображения из семи видов четырехугольников отрисовывается основание каждого треугольника и высота.
![](https://habrastorage.org/webt/l0/-b/jo/l0-bjojkqzo0gxb0-4uehd1gfts.png)
![](https://habrastorage.org/webt/kv/qx/b3/kvqxb3etury0ez2vheu3rs58k4c.png)
![](https://habrastorage.org/webt/n8/2u/e6/n82ue6-qp1hvbani3yvagfjwrmo.png)
![](https://habrastorage.org/webt/ms/t7/65/mst765wr7ccnnrofxxn6vaa6ntu.png)
По-моему, очень красиво.