Created
September 2, 2018 18:44
-
-
Save escaroda/4256b61c32402de2cb7dcd33b5b6003a to your computer and use it in GitHub Desktop.
Penrose Tile Visualiser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
This is a quick ui that wraps yurixi's code from https://habr.com/post/359244/ | |
It can save PNG and SVG. | |
All credit goes to him for doing pretty much all the work here. | |
--> | |
<!DOCTYPE html> | |
<html lang="en"> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>Penrose Explorer</title> | |
<script src="https://rawgit.com/gliffy/canvas2svg/master/canvas2svg.js"></script> | |
<!-- | |
<script src="https://raw.githubusercontent.com/gliffy/canvas2svg/master/canvas2svg.js"></script> | |
--> | |
<style> | |
* { | |
margin:0; | |
padding:0; | |
} | |
input[type="color"]{ | |
max-width:50px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="control"> | |
<input type="color" id="clrbg" value="#FFFFFF" onchange="redraw()" >bg</input> | |
<input type="color" id="clrln" value="#000000" onchange="redraw()" >line</input> | |
<input type="color" id="clr1" value="#BBCCEE" onchange="redraw()" >1</input> | |
<input type="color" id="clr2" value="#BBBBEE" onchange="redraw()" >2</input> | |
<input type="color" id="clr3" value="#EECCEE" onchange="redraw()" >3</input> | |
<input type="color" id="clr4" value="#EEBBEE" onchange="redraw()" >4</input> | |
<input type="color" id="clr5" value="#CCEEFF" onchange="redraw()" >5</input> | |
<input type="color" id="clr6" value="#EEEEFF" onchange="redraw()" >6</input> | |
<input id="linewidth" type="number" min=0 max=20 step=0.1 value=1 onchange="redraw()">line width px</input> | |
<input id="levels" type="number" min=0 max=100 step=1 value=7 onchange="redraw()">levels</input> | |
<label for="zoom">zoom</label> | |
<input type="range" min="1" max="150" value="30" id="zoom" step="1" | |
oninput="document.getElementById('zoomval').value = document.getElementById('zoom').value; redraw()"> | |
<output for="zoom" id="zoomval">30</output> | |
<label for="extent">extent</label> | |
<input type="range" min="0" max="1" value="1" id="extent" step="0.01" | |
oninput="document.getElementById('extentval').value = document.getElementById('extent').value; redraw()"> | |
<output for="extent" id="extentval">1</output> | |
<label for="drawmode">drawmode</label> | |
<input type="range" min="0" max="13" value="12" id="drawmode" step="1" | |
oninput="document.getElementById('drawmodeval').value = document.getElementById('drawmode').value; redraw()"> | |
<output for="drawmode" id="drawmodeval">12</output> | |
<button onclick="doPng()">PNG</button> | |
<button onclick="doSvg()">SVG</button> | |
time | |
<div id="timer" style="display:inline-block"></div> | |
ms | |
</div> | |
<canvas id="canvas" /> | |
<script type="application/javascript"> | |
/* https://habr.com/post/359244 */ | |
/* Получаем «контекст», объектный интерфейс для рисования на плоскости: */ | |
var canvas = document.getElementById("canvas"); | |
var context = canvas.getContext("2d"); | |
var svgcontext; | |
var svg = false; | |
var colors; | |
var linecol; | |
var bgcol; | |
var linewidth; | |
var start_ms = 0; | |
var finish_ms = 0; | |
// resize the canvas to fill browser window dynamically | |
window.addEventListener('resize', resizeCanvas, false); | |
function resizeCanvas() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
redraw(); | |
} | |
resizeCanvas(); | |
function doPng() { | |
var c=document.getElementById("canvas"); | |
window.open(c.toDataURL('image/png')); | |
} | |
function doSvg() { | |
if(typeof C2S === "undefined"){ | |
alert("unable to load canvas2svg"); | |
return; | |
} | |
svg=true; | |
redraw(); | |
svg=false; | |
alert("you will need to right click, save page as, then manually type svg filename extension") | |
var svg_win = window.open("", "svg_win"); | |
var svg_txt=svgcontext.getSerializedSvg(); | |
var parser = new DOMParser(); | |
var svg_doc = parser.parseFromString(svg_txt, "image/svg+xml"); | |
var svg_node = svg_doc.getElementsByTagName('svg')[0]; | |
var transplanted_svg = svg_win.document.importNode(svg_node, true); | |
var blank_root = svg_win.document.documentElement; | |
svg_win.document.removeChild(blank_root); | |
svg_win.document.appendChild(transplanted_svg); | |
} | |
function redraw() { | |
start_ms = Date.now(); | |
bgcol = document.getElementById('clrbg').value | |
context.lineJoin = 'bevel'; | |
context.beginPath(); | |
context.rect(0,0,canvas.width,canvas.height); | |
context.fillStyle=bgcol; | |
context.fill(); | |
if(svg){ | |
svgcontext = new C2S(canvas.width,canvas.height); | |
svgcontext.lineJoin = 'bevel'; | |
svgcontext.beginPath(); | |
svgcontext.rect(0,0,canvas.width,canvas.height); | |
svgcontext.fillStyle=(bgcol); | |
svgcontext.fill(); | |
} | |
s = prepare(); | |
s[0] = context; | |
s[7] = 1; // доля последнего уровня | |
s[8] = canvas.width/2.5; // сдвих координаты x | |
s[9] = canvas.height/2; // сдвиг координаты y | |
colors = [ | |
document.getElementById("clr1").value, | |
document.getElementById("clr2").value, | |
document.getElementById("clr3").value, | |
document.getElementById("clr4").value, | |
document.getElementById("clr5").value, | |
document.getElementById("clr6").value | |
] | |
linecol = document.getElementById("clrln").value | |
linewidth = document.getElementById("linewidth").value | |
/* Код заполнения первого уровня */ | |
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[4]; // берем из констант коэффициент | |
var levels = document.getElementById('levels').value; // количество расчетных уровней | |
s[7] = document.getElementById('extent').value; // степень проявления уровня | |
//s[10] = 24 * 6 * fi * fi; // длина линии | |
//s[10] = 24 * 6 * fi | |
//s[10] = 24 * 6; | |
// для отображения полного поля нужно изменить размер шага, размер канвы, место центра и поменять местами p[0] и p[1] в функциях from() и to(). | |
//s[10] = 24; | |
s[10]=document.getElementById('zoom').value; | |
mode = document.getElementById('drawmode').value; // режим рисования | |
// разбиение | |
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 begin(){ | |
context.beginPath(); | |
if(svg) | |
svgcontext.beginPath(); | |
} | |
// начать линию с точки | |
function from(p) { | |
context.moveTo(s[8] + p[0], s[9] - p[1]); | |
if(svg) | |
svgcontext.moveTo(s[8] + p[0], s[9] - p[1]); | |
} | |
// привести линию к точке | |
function to(p){ | |
context.lineTo(s[8] + p[0], s[9] - p[1]); | |
if(svg) | |
svgcontext.lineTo(s[8] + p[0], s[9] - p[1]); | |
} | |
// привести линию к начальной точке, не требуется если выполняется только заливка | |
function close(){ | |
context.closePath(); | |
if(svg) | |
svgcontext.closePath(); | |
} | |
// заливка | |
function fill(color){ | |
context.fillStyle = color; context.fill(); | |
if(svg){ | |
svgcontext.fillStyle = color; svgcontext.fill(); | |
} | |
} | |
// обводка контура линией | |
function line(){ | |
context.strokeStyle = linecol; | |
context.lineWidth = linewidth; | |
context.stroke(); | |
if(svg){ | |
svgcontext.strokeStyle = linecol; | |
svgcontext.lineWidth = linewidth; | |
svgcontext.stroke(); | |
} | |
} | |
function line_white(){ | |
context.strokeStyle = bgcol; context.lineWidth = linewidth; context.stroke(); | |
if(svg){ | |
svgcontext.strokeStyle = bgcol; svgcontext.lineWidth = linewidth; svgcontext.stroke(); | |
} | |
} | |
function line_black(){ | |
context.strokeStyle = "#444"; context.lineWidth = linewidth; context.stroke(); | |
if(svg){ | |
svgcontext.strokeStyle = "#444"; svgcontext.lineWidth = linewidth; svgcontext.stroke(); | |
} | |
} | |
/* Прежде всего нужно задать используемые константы. */ | |
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; | |
} | |
/* Функция разбиения */ | |
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 == 3 || mode == 4 || mode == 5) { | |
p5 = mean(p1, p2, 0.5); | |
p7 = mean(p0, p1, 0.5); | |
begin(); from(p5); to(p7); | |
if(mode > 3) { | |
from(p0); to(p2); | |
if(mode == 5) { | |
from(mean(p2, p5, 0.5)); to(mean(p0, p7, 0.5)); | |
from(mean(p1, p5, 0.5)); to(mean(p1, p7, 0.5)); | |
} | |
} | |
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(); | |
} | |
finish_ms = Date.now(); | |
document.getElementById("timer").innerHTML=finish_ms-start_ms; | |
} | |
</script> | |
<!-- | |
<canvas id="a" width="640" height="640"></canvas> | |
--> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment