256 lines
No EOL
6.7 KiB
JavaScript
256 lines
No EOL
6.7 KiB
JavaScript
function element(name, attr, content, children) {
|
|
const e = document.createElementNS('http://www.w3.org/2000/svg', name);
|
|
if(attr) {
|
|
Object.entries(attr).forEach(([k,v]) => e.setAttribute(k,v));
|
|
}
|
|
if(content !== undefined) {
|
|
e.innerHTML = content;
|
|
}
|
|
if(children) {
|
|
children.forEach(c => e.append(c));
|
|
}
|
|
return e;
|
|
}
|
|
|
|
function interpolator(fn) {
|
|
return function(strs, ...subs) {
|
|
let o = '';
|
|
strs.forEach((s,i) => {
|
|
o += s;
|
|
if(i<subs.length) {
|
|
o += fn(subs[i]);
|
|
}
|
|
});
|
|
return o;
|
|
}
|
|
}
|
|
|
|
const dp = interpolator(x => x.toFixed(4))
|
|
|
|
const coords = interpolator(([x,y]) => dp`${x} ${y}`);
|
|
|
|
function midpoint([x1,y1],[x2,y2]) {
|
|
return [(x1+x2)/2, (y1+y2)/2];
|
|
}
|
|
|
|
const {cos, sin, PI, sqrt} = Math;
|
|
const hex_points = [0,1,2,3,4,5].map(i => [cos(i*PI/3)/sqrt(3), sin(i*PI/3)/sqrt(3)]);
|
|
const midpoints = [0,1,2,3,4,5].map(i => midpoint(hex_points[i], hex_points[(i+1)%6]));
|
|
|
|
const svg = document.getElementById('board');
|
|
svg.append()
|
|
|
|
const dr = [cos(PI/6),sin(PI/6)];
|
|
const [drx,dry] = dr;
|
|
const dc = [cos(PI/6),-sin(PI/6)];
|
|
const [dcx,dcy] = dc;
|
|
|
|
function hex_project(r,c) {
|
|
return [r*drx + c*dcx, r*dry + c*dcy];
|
|
}
|
|
|
|
function lerp(a,b,t) {
|
|
return (1-t)*a + t*b;
|
|
}
|
|
|
|
function clamp(a,b,t) {
|
|
return Math.max(a,Math.min(b,t));
|
|
}
|
|
|
|
function rlerp(a,b) {
|
|
return lerp(a,b,Math.random());
|
|
}
|
|
|
|
function rint(n) {
|
|
return Math.floor(Math.random()*n);
|
|
}
|
|
|
|
function draw_hex([x,y], sqd, hue) {
|
|
const hex_d = coords`M ${hex_points[0]} `+(hex_points.slice(1).map(p=>coords`L ${p}`)).join(' ');
|
|
const s = clamp(0,1, 2*sqd**0.3)*rlerp(0.8,1);
|
|
return element('path',{d:hex_d,fill: dp`hsl(${hue},50%,${rlerp(90,95)}%)`, transform: dp`translate(${x} ${y}) scale(${s})`})
|
|
}
|
|
|
|
function fill_hex_with_tris([x,y], sqd, hue) {
|
|
const g = element('g');
|
|
|
|
const density = clamp(0,1, 1.5*sqd**0.3);
|
|
|
|
for(let i=0;i<6;i++) {
|
|
if(Math.random() < density) {
|
|
g.append(draw_tri([x,y],i, hue));
|
|
}
|
|
}
|
|
return g;
|
|
}
|
|
|
|
function draw_tri([x,y], o, hue) {
|
|
o = o || 0;
|
|
const tri_d = coords`M ${hex_points[o%6]} L ${hex_points[(o+1)%6]} L ${[0,0]}`;
|
|
return element('path',{d:tri_d,fill: dp`hsl(${hue},50%,${rlerp(90,95)}%)`, transform: dp`translate(${x} ${y})`})
|
|
}
|
|
|
|
function draw_circle([x,y], sqd, hue) {
|
|
const l = clamp(90,95,1000*sqrt(sqd));
|
|
const radius = clamp(0, 1, 3*sqd**0.3) * rlerp(0.8,1) * 0.5;
|
|
return element('circle', {cx: x, cy: y, r: radius, fill: `hsl(${hue},50%,${rlerp(0.95,1)*l}%)`})
|
|
}
|
|
|
|
function draw_branches([x,y], sqd, hue) {
|
|
const w = clamp(0, 0.3, 0.3 * sqrt(sqd));
|
|
let o = Math.random() < 0.5 ? 0 : 1;
|
|
const branch_d = coords`M 0 0 L ${midpoints[o]} M 0 0 L ${midpoints[o+2]} M 0 0 L ${midpoints[o+4]}`;
|
|
return element('path',{d: branch_d, 'stroke-width': w, stroke: dp`hsl(${hue},50%,${rlerp(85,95)}%)`, transform: dp`translate(${x} ${y})`})
|
|
}
|
|
|
|
function draw_hex_outline([x,y], sqd, hue) {
|
|
let o = rint(6);
|
|
let n = Math.floor(clamp(1,6,80*sqd**1));
|
|
const w = 1 - clamp(0,0.7,lerp(0.2,0.7,1*sqd**0.5));
|
|
let outer = coords`M ${hex_points[o]}`;
|
|
let inner = '';
|
|
for(let i=0;i<=n;i++) {
|
|
const [px,py] = hex_points[(o+i)%6];
|
|
outer += coords`L ${[px,py]}`;
|
|
inner = coords` L ${[w*px,w*py]}` + inner;
|
|
}
|
|
const hex_d = outer + inner;
|
|
return element('path',{d:hex_d, fill: dp`hsl(${hue},50%,${rlerp(90,95)}%)`, transform: dp`translate(${x} ${y})`})
|
|
}
|
|
|
|
function draw_arcs([x,y], sqd, hue) {
|
|
const g = element('g');
|
|
const density = clamp(0, 1, 1.5*sqd**0.3);
|
|
|
|
for(let i=0;i<6;i++) {
|
|
if(Math.random() < density) {
|
|
const r = 1/sqrt(3)/2/density;
|
|
const arc = element('path', {fill: dp`hsl(${hue},50%,${rlerp(90,95)}%)`, d: dp`M ${hex_points[i][0]} ${hex_points[i][1]} L ${midpoints[i][0]} ${midpoints[i][1]} A ${r} ${r} 0 0 1 ${midpoints[(i+5)%6][0]} ${midpoints[(i+5)%6][1]}`, transform: dp`translate(${x} ${y})`})
|
|
g.append(arc);
|
|
}
|
|
}
|
|
g.append(element('circle', {cx: x, cy: y, fill: dp`hsl(${hue},50%,${rlerp(90,95)}%)`, r: 1/sqrt(3)/2*density}));
|
|
return g;
|
|
}
|
|
|
|
function choice(l) {
|
|
const i = Math.floor(Math.random() * l.length);
|
|
return l[i];
|
|
}
|
|
|
|
const fns = [
|
|
draw_branches,
|
|
draw_hex,
|
|
draw_hex_outline,
|
|
fill_hex_with_tris,
|
|
//draw_circle,
|
|
draw_arcs,
|
|
];
|
|
|
|
const spots = [];
|
|
|
|
function hue_for(x,y) {
|
|
const rhue = 40;
|
|
const hue = ((x+y)/80 + 0.5) * 360 + rlerp(-rhue, rhue);
|
|
return hue;
|
|
}
|
|
|
|
for(let i=0;i<20;i++) {
|
|
const fn = choice(fns);
|
|
const an = rlerp(0,2*PI);
|
|
const r = sqrt(rlerp(0,1))*30;
|
|
const pos = [r*cos(an), r*sin(an)];
|
|
const [x,y] = pos;
|
|
const size = rlerp(1, 5);
|
|
const hue = hue_for(x,y);
|
|
spots.push({fn,pos, hue, size});
|
|
const c = element('circle',{cx: pos[0], cy: pos[1], r: 1, fill: dp`hsl(${hue},100%,50%)`});
|
|
//svg.append(c);
|
|
}
|
|
|
|
function sqdist([x1,y1], [x2,y2]) {
|
|
return (x1-x2)**2 + (y1-y2)**2;
|
|
}
|
|
|
|
function weighted_choice(weights,things) {
|
|
let t = 0;
|
|
weights.forEach(x => t += x);
|
|
const x = Math.random() * t;
|
|
let z = 0;
|
|
for(let i=0;i<weights.length;i++) {
|
|
z += weights[i];
|
|
if(z >= x) {
|
|
return things[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
function layer_hexes() {
|
|
for(let r=-40;r<40;r++) {
|
|
for(let c=-40;c<40;c++) {
|
|
const p = hex_project(r,c);
|
|
const weights = spots.map(({pos}) => {
|
|
return 1/sqdist(p, pos);
|
|
})
|
|
const {fn, pos, hue, size} = weighted_choice(weights, spots)
|
|
const el = fn(p, size/(1+sqdist(p,pos)), hue);
|
|
svg.append(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
function layer_lines() {
|
|
const occupied = {};
|
|
function hash(x,y) {
|
|
return `${x},${y}`;
|
|
}
|
|
function is_occupied(x,y) {
|
|
return hash(x,y) in occupied;
|
|
}
|
|
for(let r=-30;r<30;r++) {
|
|
for(let c=-30;c<30;c+=1) {
|
|
if(is_occupied(r,c)) {
|
|
continue;
|
|
}
|
|
let [x,y] = [r,c];
|
|
const hue = hue_for(...hex_project(r,c));
|
|
let trace = '';
|
|
let steps = 10;
|
|
for(let i=0;i<1000;i++) {
|
|
const directions = [
|
|
[1,0],
|
|
[0,1],
|
|
[-1,0],
|
|
[0,-1],
|
|
[1,-1],
|
|
[-1,1]
|
|
];
|
|
const available = directions.filter(([dx,dy]) => !is_occupied(x+dx,y+dy));
|
|
console.log(available);
|
|
if(!available.length) {
|
|
break;
|
|
}
|
|
const [dx,dy] = choice(available);
|
|
console.log(dx,dy);
|
|
for(let n=0;n<steps && !is_occupied(x+dx,y+dy);n++) {
|
|
x = x+dx;
|
|
y = y+dy;
|
|
occupied[hash(x,y)] = true;
|
|
trace += (trace ? ' L' : 'M') + coords` ${hex_project(x,y)}`;
|
|
|
|
}
|
|
}
|
|
console.log(trace);
|
|
svg.append(element('path',{d:trace,fill:'none',stroke: `hsl(${hue},50%, ${rlerp(95,99)}%)`, 'stroke-width': rlerp(0.1,0.2), 'stroke-linejoin': 'round'}))
|
|
}
|
|
}
|
|
}
|
|
|
|
function layer_parquet() {
|
|
const h = cos(PI/3);
|
|
}
|
|
|
|
layer_lines();
|
|
layer_hexes();
|
|
const viewport = 15;
|
|
svg.setAttribute('viewBox', `${-viewport} ${-viewport} ${2*viewport} ${2*viewport}`) |