xxxxxxxxxx
371
const ns = [
{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 },
{ x: 1, y: -1 },
];
const tiles = [
[],
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[0, 5],
[0, 2],
[1, 3],
[2, 4],
[3, 5],
[0, 4],
[1, 5],
[0, 1, 3, 4],
[1, 2, 4, 5],
[0, 2, 3, 5],
];
let dbg = false;
let rad = 50;
let w;
let wv;
let visit_order;
let all_edges;
let edge_marks;
let dim;
function pt(x, y) {
return { x: x, y: y };
}
function hexNeighbours(P) {
const ret = [];
for (let Q of ns) {
const x = P.x + Q.x;
const y = P.y + Q.y;
if (
x >= -dim &&
x <= dim &&
y >= -dim &&
y <= dim &&
x + y >= -dim &&
x + y <= dim
) {
ret.push(pt(x, y));
}
}
return ret;
}
function less(P, Q) {
return P.y < Q.y || (P.y === Q.y && P.x < Q.x);
}
function edge(P, Q) {
if (less(P, Q)) {
return `${P.x},${P.y},${Q.x},${Q.y}`;
} else {
return `${Q.x},${Q.y},${P.x},${P.y}`;
}
}
function createGrid(n) {
dim = n;
visit_order = [pt(0, 0)];
for (let ring = 1; ring <= dim; ++ring) {
let cur = pt(0, -ring);
for (let dir of ns) {
for (let idx = 0; idx < ring; ++idx) {
cur = pt(cur.x + dir.x, cur.y + dir.y);
visit_order.push(cur);
}
}
}
all_edges = new Set();
for (let P of visit_order) {
for (let Q of hexNeighbours(P)) {
all_edges.add(edge(P, Q));
}
}
}
function buildPatternFrom(idx) {
if (idx === visit_order.length) {
return true;
}
const P = visit_order[idx];
const my_tiles = shuffle(tiles);
// print(`Visiting ${P.x},${P.y}`);
// for( let e of Object.keys( edge_marks ) ) {
// print( ` => ${e} ${edge_marks[e]}`);
// }
for (let t of my_tiles) {
// Is the pattern in t compatible with any edges that are
// already in place?
let tile_ok = true;
// print(` Considering ${t}`);
for (let ed = 0; ed < 6; ++ed) {
const dir = ns[ed];
const Q = pt(P.x + dir.x, P.y + dir.y);
const e = edge(P, Q);
const b = t.includes(ed);
if (b && !all_edges.has(e)) {
// Want to use this edge, but it's not legal
tile_ok = false;
break;
}
if (e in edge_marks && b !== edge_marks[e]) {
tile_ok = false;
break;
}
}
if (tile_ok) {
// print( ` Recursion on [${t}]`);
const added = [];
for (let ed = 0; ed < 6; ++ed) {
const dir = ns[ed];
// print( `${ed} -> ${ns[ed].x},${ns[ed].y} -> ${ed in t}`);
const Q = pt(P.x + dir.x, P.y + dir.y);
const e = edge(P, Q);
if (!(e in edge_marks)) {
added.push(e);
edge_marks[e] = t.includes(ed);
//print( ` setting ${e} to ${ed in t}`);
}
}
if (buildPatternFrom(idx + 1)) {
return true;
}
for (let e of added) {
delete edge_marks[e];
}
}
}
return false;
}
function makePattern() {
edge_marks = {};
buildPatternFrom(0);
}
function screen(P) {
const v = pt(2 * rad, 0);
const w = pt(rad, rad * sqrt(3));
return pt(P.x * v.x + P.y * w.x, -P.x * v.y - P.y * w.y);
}
function eq(A, B) {
if (A.length !== B.length) {
return false;
}
for (let idx = 0; idx < A.length; ++idx) {
if (A[idx] !== B[idx]) {
return false;
}
}
return true;
}
function bar(c1, c2) {
fill(c1);
quad(-w, -wv, 0, -2 * wv, 0, rad, -w, rad);
fill(c2);
quad(0, -2 * wv, w, -wv, w, rad, 0, rad);
}
function type1(rot, flip, c1, c2, c3) {
noStroke();
push();
rotate(rot);
if (flip) {
scale(-1, 1);
rotate(PI / 6);
}
rotate(-HALF_PI);
push();
rotate(-PI / 3);
bar(c1, c3);
pop();
bar(c2, c3);
pop();
}
function type2(rot, c1, c2, c3, c4) {
push();
rotate(rot + (7 * PI) / 6);
push();
rotate(-PI / 3);
fill(c1);
quad(w, -wv, 0, 0, 0, rad, w, rad);
fill(c2);
quad(0, 0, -w, wv, -w, rad, 0, rad);
pop();
push();
rotate(PI / 3);
fill(c3);
quad(-w, -wv, 0, 0, 0, rad, -w, rad);
fill(c4);
quad(0, 0, w, wv, w, rad, 0, rad);
pop();
pop();
}
function type3(rot, c1, c2, c3, c4) {
push();
if (random(1) < 0.5) {
rotate(PI);
let tmp = c1;
c1 = c2;
c2 = tmp;
tmp = c3;
c3 = c4;
c4 = tmp;
}
rotate(rot);
fill(c1);
rect(-w, -rad, w, 2 * rad);
fill(c2);
rect(0, -rad, w, 2 * rad);
rotate(PI / 3);
fill(c3);
rect(-w, -rad, w, 2 * rad);
fill(c4);
rect(0, -rad, w, 2 * rad);
pop();
}
function setup() {
createCanvas(800, 800);
createGrid(3);
makePattern();
rad = 55;
w = 28;
wv = w / sqrt(3);
}
function draw() {
background(200, 160, 160);
push();
translate(width / 2, height / 2);
rotate(PI / 6);
noStroke();
if (dbg) {
for (let P of visit_order) {
const C = screen(P);
fill(255, 200, 200, 50);
stroke(0, 50);
strokeWeight(0.5);
circle(C.x, C.y, 2 * rad);
}
}
for (let P of visit_order) {
const C = screen(P);
push();
translate(C.x, C.y);
let ang = 0;
const t = [];
for (let ed = 0; ed < 6; ++ed) {
const dir = ns[ed];
const Q = pt(P.x + dir.x, P.y + dir.y);
const e = edge(P, Q);
if (edge_marks[e]) {
t.push(ed);
}
}
const cs = [color(100), color(180), color(255)];
const rr = color(255, 0, 0);
if (eq(t, [0, 1])) {
type1(0, false, cs[0], cs[1], cs[2]);
} else if (eq(t, [1, 2])) {
type1(PI / 2, true, cs[2], cs[1], cs[0]);
} else if (eq(t, [2, 3])) {
type1((4 * PI) / 3, false, cs[2], cs[0], cs[1]);
} else if (eq(t, [3, 4])) {
type1(-PI / 6, true, cs[1], cs[0], cs[2]);
} else if (eq(t, [4, 5])) {
type1(TWO_PI / 3, false, cs[1], cs[2], cs[0]);
} else if (eq(t, [0, 5])) {
type1((-5 * PI) / 6, true, cs[0], cs[2], cs[1]);
} else if (eq(t, [0, 2])) {
type2(0, cs[1], cs[0], cs[1], cs[2]);
} else if (eq(t, [1, 3])) {
type2(-PI / 3, cs[1], cs[2], cs[0], cs[2]);
} else if (eq(t, [2, 4])) {
type2((-2 * PI) / 3, cs[0], cs[2], cs[0], cs[1]);
} else if (eq(t, [3, 5])) {
type2(-PI, cs[0], cs[1], cs[2], cs[1]);
} else if (eq(t, [0, 4])) {
type2((-4 * PI) / 3, cs[2], cs[1], cs[2], cs[0]);
} else if (eq(t, [1, 5])) {
type2((-5 * PI) / 3, cs[2], cs[0], cs[1], cs[0]);
} else if (eq(t, [0, 1, 3, 4])) {
type3(PI / 6, cs[2], cs[0], cs[2], cs[1]);
} else if (eq(t, [1, 2, 4, 5])) {
type3(-PI / 6, cs[1], cs[0], cs[2], cs[0]);
} else if (eq(t, [0, 2, 3, 5])) {
type3(-HALF_PI, cs[1], cs[2], cs[1], cs[0]);
}
if (dbg) {
for (let n of ns) {
const Q = pt(P.x + n.x, P.y + n.y);
const e = edge(P, Q);
if (e in edge_marks) {
if (edge_marks[e]) {
const M = screen(pt(0.5 * (Q.x - P.x), 0.5 * (Q.y - P.y)));
stroke(0);
strokeWeight(1);
line(0, 0, M.x, M.y);
}
}
ang = ang + 1;
}
}
pop();
}
pop();
noLoop();
}
function keyPressed() {
if (key === " ") {
createGrid(3);
makePattern();
loop();
} else if (key === "s") {
save("output.png");
} else if (key === "d") {
dbg = !dbg;
loop();
}
}