Adapted from michaelfogleman.com/static/snowflakes/
Log in to post a comment.
// Forked from "Snowflakes" by llemarie // https://turtletoy.net/turtle/f89ca289be // Ported from Michael Fogleman's generative snowflakes web app: // https://www.michaelfogleman.com/static/snowflakes/ // https://twitter.com/FogleBird/status/1356082505699500034?s=20 var rows = 1; // will be calculated in setup var cols = rows; const show_seeds = 0; // min=0, max=1, step=1 const predefined_seeds = 0; // min=0, max=1000, step=1 const default_iterations = 19; // min=3, max=100, step=1 const default_minCells = 32; // min=10, max=100, step=1 const default_off_bias = 1.0; // min=0.1, max=2, step=0.1 const default_on_bias = 1.0; // min=0.1, max=2, step=0.1 var global_scale = 1; const saved_min_max_seeds2 = [ // iterations, cells, off, on, seed [6, 32, 1, 1, 385], ]; var off_bias = default_off_bias; var on_bias = default_on_bias; var line_count = 0; const lookup = [ 0, 1, 1, 2, 1, 3, 2, 4, 1, 5, 3, 6, 2, 6, 4, 7, 1, 3, 5, 6, 3, 8, 6, 9, 2, 6, 6, 10, 4, 9, 7, 11, 1, 2, 3, 4, 5, 6, 6, 7, 3, 6, 8, 9, 6, 10, 9, 11, 2, 4, 6, 7, 6, 9, 10, 11, 4, 7, 9, 11, 7, 11, 11, 12, ]; function pack(p) { return p[0] + "," + p[1]; } function unpack(p) { const s = p.split(","); const x = parseInt(s[0]); const y = parseInt(s[1]); return [x, y]; } function add(cells, p) { cells.add(pack(p)); } function has(cells, p) { return cells.has(pack(p)); } function mask(cells, p) { const x = p[0]; const y = p[1]; let result = 0; if (has(cells, [x + 1, y])) { result |= 1; } if (has(cells, [x + 1, y - 1])) { result |= 2; } if (has(cells, [x, y - 1])) { result |= 4; } if (has(cells, [x - 1, y])) { result |= 8; } if (has(cells, [x - 1, y + 1])) { result |= 16; } if (has(cells, [x, y + 1])) { result |= 32; } return result; } function neighbors(p) { const x = p[0]; const y = p[1]; let result = []; result.push([x + 1, y]); result.push([x + 1, y - 1]); result.push([x, y - 1]); result.push([x - 1, y]); result.push([x - 1, y + 1]); result.push([x, y + 1]); return result; } function neighborhood(cells) { let result = new Set(); for (let p of cells) { p = unpack(p); const x = p[0]; const y = p[1]; add(result, [x, y]); add(result, [x + 1, y]); add(result, [x + 1, y - 1]); add(result, [x, y - 1]); add(result, [x - 1, y]); add(result, [x - 1, y + 1]); add(result, [x, y + 1]); } return result; } function step(cells, off, on) { let offMask = 0; let onMask = 0; for (let i = 0; i < 13; i++) { if (random() < off[i]) { offMask |= 1 << i; } if (random() < on[i]) { onMask |= 1 << i; } } let result = new Set(); for (let p of neighborhood(cells)) { p = unpack(p); const bit = 1 << lookup[mask(cells, p)]; if (has(cells, p)) { if ((offMask & bit) == 0) { add(result, p); } } else { if ((onMask & bit) != 0) { add(result, p); } } } return result; } function generate(iterations, minCells) { while (true) { let off = []; let on = []; for (let i = 0; i < 13; i++) { off.push(random() * off_bias); on.push(random() * on_bias); } let cells = new Set(); add(cells, [0, 0]); let history = []; history.push(edges(cells)); for (let i = 0; i < iterations; i++) { cells = step(cells, off, on); history.push(edges(cells)); if (cells.size < 2) { break; } } if (hasIsolatedCells(cells)) { continue; } if (cells.size >= minCells) { return history; } } } function cartesian(p) { const size = 1; const q = parseInt(p[0]); const r = parseInt(p[1]); const s = Math.sqrt(3); const x = s * q + s / 2 * r; const y = 3.0 / 2 * r; return [x * size, y * size]; } function less(p, q) { if (p[0] === q[0]) { return p[1] < q[1]; } return p[0] < q[0]; } function hasIsolatedCells(cells) { for (let p of cells) { p = unpack(p); let ok = false; for (let q of neighbors(p)) { if (has(cells, q)) { ok = true; break; } } if (!ok) { return true; } } return false; } function edges(cells) { let result = []; for (let p of cells) { p = unpack(p); let ok = false; for (let q of neighbors(p)) { if (!has(cells, q)) { continue; } ok = true; if (!less(p, q)) { continue; } result.push([cartesian(p), cartesian(q)]); } if (!ok) { result.push([cartesian(p), cartesian(p)]); } } return mergeEdges(result); } function packEdge(edge) { const a = edge[0]; const b = edge[1]; return a[0] + "," + a[1] + "," + b[0] + "," + b[1]; } function nextEdge(lookup, edge) { const a = edge[0]; const b = edge[1]; const nx = b[0] + (b[0] - a[0]); const ny = b[1] + (b[1] - a[1]); for (let p of lookup.get(pack(b))) { const ex = Math.abs(nx - p[0]); const ey = Math.abs(ny - p[1]); if ((ex < 1e-6) && (ey < 1e-6)) { return [b, p]; } } return null; } function mergeEdges(edges) { let lookup = new Map(); for (let edge of edges) { lookup.set(pack(edge[0]), []); lookup.set(pack(edge[1]), []); } for (let edge of edges) { if (pack(edge[0]) === pack(edge[1])) { continue; } lookup.get(pack(edge[0])).push(edge[1]); lookup.get(pack(edge[1])).push(edge[0]); } let result = []; let seen = new Set(); for (let edge of edges) { const a = edge[0]; const b = edge[1]; if (seen.has(packEdge([a, b])) || seen.has(packEdge([b, a]))) { continue; } let p0 = a; let p1 = b; // forward let e = [a, b]; while (true) { seen.add(packEdge(e)); p1 = e[1]; e = nextEdge(lookup, e); if (!e) { break; } } // reverse e = [b, a]; while (true) { seen.add(packEdge(e)); p0 = e[1]; e = nextEdge(lookup, e); if (!e) { break; } } result.push([p0, p1]); } return result; } function distance2(x1, y1, x2, y2) { const dx = x1 - x2; const dy = y1 - y2; return dx*dx + dy*dy; } const cosA = Math.cos(Math.PI/6); const sinA = Math.sin(Math.PI/6); function drawEdges(edges, scale, offset_x, offset_y) { edges_to_sort = []; edges_sorted = []; for (let e of edges) { edges_to_sort.push(e); } var current_x = 0; var current_y = 0; while (edges_to_sort.length > 0) { var min_distance = -1; var best_index = -1; var invert = false; var index = 0; var epsilon = 0.1; for (let e of edges_to_sort) { var penalty = distance2(0, 0, e[0][0], e[0][1]) * 0.1; var dist2 = distance2(current_x, current_y, e[0][0], e[0][1]); if (dist2 > epsilon) dist2 += penalty; if (best_index < 0 || min_distance > dist2) { best_index = index; min_distance = dist2; invert = false; } penalty = distance2(0, 0, e[1][0], e[1][1]) * 0.1; dist2 = distance2(current_x, current_y, e[1][0], e[1][1]); if (dist2 > epsilon) dist2 += penalty; if (best_index < 0 || min_distance > dist2) { best_index = index; min_distance = dist2; invert = true; } index++; } if (best_index >= 0) { var edge = edges_to_sort[best_index]; edges_to_sort.splice(best_index, 1); if (invert) { for (let i = 0; i < 2; i++) { var tmp = edge[0][i]; edge[0][i] = edge[1][i]; edge[1][i] = tmp; } } current_x = edge[1][0]; current_y = edge[1][1]; edges_sorted.push(edge); } } for (let e of edges_sorted) { line_count++; if (e[0][0] === e[1][0] && e[0][1] === e[1][1]) { const p = 0.02; var x0 = e[0][0]-p; var y0 = e[0][1]-p; var x1 = e[1][0]+p; var y1 = e[1][1]+p; var x2 = e[0][0]-p; var y2 = e[0][1]+p; var x3 = e[1][0]+p; var y3 = e[1][1]-p; var rx0 = cosA * x0 - sinA * y0; var ry0 = sinA * x0 + cosA * y0; var rx1 = cosA * x1 - sinA * y1; var ry1 = sinA * x1 + cosA * y1; var rx2 = cosA * x2 - sinA * y2; var ry2 = sinA * x2 + cosA * y2; var rx3 = cosA * x3 - sinA * y3; var ry3 = sinA * x3 + cosA * y3; x0 = rx0 * scale * global_scale + offset_x * global_scale * 40; y0 = ry0 * scale * global_scale + offset_y * global_scale * 40; x1 = rx1 * scale * global_scale + offset_x * global_scale * 40; y1 = ry1 * scale * global_scale + offset_y * global_scale * 40; x2 = rx2 * scale * global_scale + offset_x * global_scale * 40; y2 = ry2 * scale * global_scale + offset_y * global_scale * 40; x3 = rx3 * scale * global_scale + offset_x * global_scale * 40; y3 = ry3 * scale * global_scale + offset_y * global_scale * 40; turtle.goto(x0, y0); turtle.pendown(); turtle.goto(x1, y1); turtle.goto(x2, y2); turtle.goto(x3, y3); turtle.penup(); } else { var x0 = e[0][0]; var y0 = e[0][1]; var x1 = e[1][0]; var y1 = e[1][1]; var rx0 = cosA * x0 - sinA * y0; var ry0 = sinA * x0 + cosA * y0; var rx1 = cosA * x1 - sinA * y1; var ry1 = sinA * x1 + cosA * y1; x0 = rx0 * scale * global_scale + offset_x * global_scale * 40; y0 = ry0 * scale * global_scale + offset_y * global_scale * 40; x1 = rx1 * scale * global_scale + offset_x * global_scale * 40; y1 = ry1 * scale * global_scale + offset_y * global_scale * 40; turtle.goto(x0, y0); turtle.pendown(); turtle.goto(x1, y1); turtle.penup(); } } } function Snowflake(id) { this.id = id; if (predefined_seeds > 0) this.seed = this.id + predefined_seeds * 1000; else this.seed = Math.floor(9999999999 * Math.random()); this.iterations = default_iterations; this.minCells = default_minCells; if (typeof saved_min_max_seeds !== 'undefined' && saved_min_max_seeds.length > this.id) { this.iterations = saved_min_max_seeds[this.id][0]; // skip 1 this.minCells = saved_min_max_seeds[this.id][2]; off_bias = saved_min_max_seeds[this.id][3]; on_bias = saved_min_max_seeds[this.id][4]; this.seed = saved_min_max_seeds[this.id][5]; } this.reset(); } Snowflake.prototype.reset = function() { randomSeed(this.seed); const iterations = this.iterations; //this.minIterations = iterations; this.maxIterations = iterations; this.history = generate(iterations, this.minCells); this.computeBounds(); } Snowflake.prototype.draw = function(index, offset_x, offset_y) { if (index < 0) return; if (index >= this.history.length) return; const edges = this.history[index]; var scale = 40 * this.computeScale(); if (show_seeds) { scale *= 0.7; offset_y *= 0.9; } drawEdges(edges, scale, offset_x, offset_y); if (show_seeds) { turtle.penup(); var string = `${this.minIterations} ${this.maxIterations} ${this.minCells} ${off_bias} ${on_bias} ${this.seed}`; var len = string.length; const text_size = 0.07; var t_offset_x = -len/(0.1 / text_size); var t_offset_y = 0.45; turtle.goto(offset_x * global_scale * 40 + t_offset_x, (offset_y + t_offset_y) * global_scale * 40); text.print(turtle, string, text_size); turtle.penup(); } } Snowflake.prototype.computeBounds = function() { let x0 = 0; let y0 = 0; let x1 = 0; let y1 = 0; for (let edges of this.history) { for (let e of edges) { for (let p of e) { x0 = Math.min(x0, p[0]); x1 = Math.max(x1, p[0]); y0 = Math.min(y0, p[1]); y1 = Math.max(y1, p[1]); } } } this.bounds = [x0, y0, x1, y1]; } Snowflake.prototype.computeScale = function() { const p = 0.9; let x0, y0, x1, y1; [x0, y0, x1, y1] = this.bounds; const dx = x1 - x0 + 2; const dy = y1 - y0 + 2; const sx = p * 1 / dx; const sy = p * 1 / dy; const s = Math.min(sx, sy); return s; } let flakes = []; function setup() { line_count = 0; } function draw(t_i) { for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const i = row * cols + col; if (i != t_i) continue; let flake = flakes[0]; const offset_x = col - cols * 0.5 + 0.5; var offset_y = row - rows * 0.5 + 0.5; if (show_seeds) offset_y *= 1.3; flake.draw(i, offset_x, offset_y); } } } Canvas.setpenopacity(0.9); const turtle = new Turtle(); const text = new Text(); turtle.penup(); // The walk function will be called until it returns false. function walk(i) { if (i==0) { setup(); flakes.push(new Snowflake(0)); rows = cols = 1; while (rows * cols < flakes[0].history.length) { if (cols > rows) rows++; else cols++; } global_scale = 5 / Math.max(rows, cols); turtle.penup(); turtle.goto(-30,97); //text.print(turtle, `History: ${flakes[0].history.length} - Rows: ${rows} Cols: ${cols}`, .1); turtle.penup(); } if (i >= flakes[0].history.length) { return false; } draw(i); //sleep(10); return true; } //////////////////////////////// function sleep(milliseconds) { const date = Date.now(); let currentDate = null; do { currentDate = Date.now(); } while (currentDate - date < milliseconds); } //////////////////////////////////////////////////////////////// // Text utility code. Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/1713ddbe99 //////////////////////////////////////////////////////////////// function Text() { class Text { print (t, str, scale = 1, italic = 0, kerning = 1) { t.radians(); let pos = [t.x(), t.y()], h = t.h(), o = pos; str.split('').map(c => { const i = c.charCodeAt(0) - 32; if (i < 0 ) { pos = o = this.rotAdd([0, 48*scale], o, h); } else if (i > 96 ) { pos = this.rotAdd([16*scale, 0], o, h); } else { const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2]; paths.map( p => { t.up(); p.map( s=> { t.goto(this.rotAdd([(s[0]-s[1]*italic)*scale - lt, s[1]*scale], pos, h)); t.down(); }); }); pos = this.rotAdd([(rt - lt)*kerning, 0], pos, h); } }); } rotAdd (a, b, h) { return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]]; } } const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefg'+ 'gmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfph'+ 'nmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>c'+ 'qfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti'+ '^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrk'+ 'shserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlping'+ 'kfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjd'+ 'hcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wag'+ 'sg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmh'+ 'n<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o'+ '_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnop'+ 'mrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<'+ 'q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c'+ '^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcnd'+ 'pfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqpo'+ 'rlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs'+ '<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkc'+ 'ieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfeh'+ 'dkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_j'+ 'bjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^'+ 'k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofp'+ 'ips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkd'+ 'mepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsks'+ 'mrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj['+ 'i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjci'+ 'eigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhill'+ 'nmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map( r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0) -106));} return ret; })]; }); return new Text(); } /////////////// var seed = 0; var r_m = 4294967296; // a - 1 should be divisible by m's prime factors var r_a = 1664525; // c and m should be co-prime var r_c = 1013904223; function randomSeed(val) { seed = val;//(val == null ? Math.random() * m : val) >>> 0; } function random() { seed = (r_a * seed + r_c) % r_m; var rand = seed / r_m; return rand; }