Brute force using Perlin noise, a spiral, cultivated randomness or some 'noise' which I made myself.
Starry Night Flow 🌃 (variation)
Starry Night Flow 🌃 (variation)
Starry Night Flow 🌃 (variation)
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(1); const iterations = 4000; //min=100 max=5000 step=100 const maxLength = 20; //min=1 max=200 step=1 const stepLength = .2; const vectorGrid = 500; //min=20 max=1000 step=10 const visibleGrid = 400; //min=20 max=800 step=10 const stopMargin = 1; //min=0 max=10 step=.1 const lineThickness = 5; //min=1 max=10 step=1 const gridMode = 3; //min=0 max=3 step=1 (Perlin noise, Spiral noise, Random chaos, Jurgen noise) const mode_perlinZoom = 1; //min=.1 max=10 step=.1 const mode_spiralFactor = .5; //min=0 max=1 step=.1 const mode_randomBias = .4; //min=0 max=2 step=.1 const mode_randomOffset = -.2; //min=-1 max=1 step=.1 const mode_jurgenPoints = 8; //min=1 max=10 step=1 const mode_jurgenNegatives = 0; //min=0 max=1 step=1 (No, Yes) const mode_jurgenAmplitude = 20; //min=1 max=40 step=1 // Global code will be evaluated once. const turtle = new Turtle(); const fatTurtle = new Slowbro(); fatTurtle.thickness = lineThickness; const perl = new Perlin(mode_perlinZoom); const vpGridRatio = 200 / visibleGrid; const margin = stopMargin; const newDimensionalArray = (dim, fn, args = []) => Array.apply(null,{length: dim[0]}).map((v,k) => dim.length == 1? fn(...args, k): newDimensionalArray(dim.filter((v,k) => k != 0), fn, [...args, k])); const jurgenGrid = Array.apply(null,{length: mode_jurgenPoints}).map((v,k) => [(Math.random() * vectorGrid | 0) - vectorGrid/2, (Math.random() * vectorGrid | 0) - vectorGrid/2, Math.random() * mode_jurgenAmplitude - (mode_jurgenNegatives==1?mode_jurgenAmplitude/2:0)]); const vectorsGrid = gridMode == 0? newDimensionalArray([vectorGrid,vectorGrid], (col,row) => perl.get(col/(vectorGrid-1), row/(vectorGrid-1)) * Math.PI * 2) : gridMode == 1? newDimensionalArray([vectorGrid,vectorGrid], function(col,row) { const x = col - vectorGrid/2, y = row - vectorGrid/2; return (x === 0? Math.PI * (0<y? -.5: .5): Math.atan(y/x) + (x > 0? Math.PI:0)) + Math.PI/2 - mode_spiralFactor; }) : gridMode == 2? newDimensionalArray([vectorGrid,vectorGrid], (col,row) => ((Math.random() * mode_randomBias) + mode_randomOffset) * Math.PI * 2) : //default newDimensionalArray([vectorGrid,vectorGrid], function(col,row) { const xx = col - vectorGrid/2, yy = row - vectorGrid/2; let angles = []; let sumWeight = 0; for(let jg = 0; jg < jurgenGrid.length; jg++) { let x = xx - jurgenGrid[jg][0]; let y = yy - jurgenGrid[jg][1]; let weight = Math.abs(jurgenGrid[jg][2])/(x**2+y**2); sumWeight += weight; angles.push([((x === 0? Math.PI * (0<y? -.5: .5): Math.atan(y/x) + (x > 0? Math.PI:0)) + Math.PI/2) + (jurgenGrid[jg][2] < 0? Math.PI:0), weight]); } return angles.reduce((prev, cur) => prev + (cur[0] * cur[1]) /sumWeight, 0); }); ; const tr = (a) => [((a[0] - vectorGrid / 2) * vpGridRatio),((a[1] - vectorGrid / 2) * vpGridRatio)]; const add2 = (a, b) => [a[0] + b[0], a[1] + b[1]]; let visited = []; const rMargin = stopMargin / vpGridRatio; // The walk function will be called until it returns false. function walk(i) { let ix = 0; const visits = []; for(let pt of drawCurve( [Math.random() * vectorGrid, Math.random() * vectorGrid], maxLength / vpGridRatio, stepLength / vpGridRatio )) { const trpt = tr(pt); if(stopMargin > 0 && visited.filter((k) => k[0]-rMargin < trpt[0] && trpt[0] < k[0]+rMargin && k[1]-rMargin < trpt[1] && trpt[1] < k[1]+rMargin ).length > 0) break; ix++ == 0? fatTurtle.jump(trpt): fatTurtle.goto(trpt); visits.push(trpt); } visited = [...visited, ...visits]; return i < iterations; } function *drawCurve(pt, max_length, step_length = .2, ) { let num_steps = max_length / step_length; yield pt; for (let n = 0; n < num_steps; n++) { const col = pt[0] | 0, row = pt[1] | 0; if(col < 0 || row < 0 || vectorGrid <= col || vectorGrid <= row) return; let grid_angle = vectorsGrid[col][row]; pt[0] += step_length * Math.cos(grid_angle) pt[1] += step_length * Math.sin(grid_angle) yield pt; } } //////////////////////////////////////////////////////////////// // Slowbro utility code. Created by Lionel Lemarie 2021 // Based on Slowpoke by Reinder, which removes most duplicate // lines Slowbro adds optional thickness to the lines //////////////////////////////////////////////////////////////// function Slowbro(x, y) { const linesDrawn = {}; class Slowbro extends Turtle { constructor(x, y) { super(x, y); this.thickness = 1; this.offset = 0.2; this.slowpoke_skip = this.slowpoke_draw = 0; } goto(x, y) { if (Array.isArray(x)) { y = x[1]; x = x[0]; } const ox = this.x(), oy = this.y(); if (this.isdown()) { const p = [x, y], o = [ox, oy]; const h1 = o[0].toFixed(2) + '_' + p[0].toFixed(2) + o[1].toFixed(2) + '_' + p[1].toFixed(2); const h2 = p[0].toFixed(2) + '_' + o[0].toFixed(2) + p[1].toFixed(2) + '_' + o[1].toFixed(2); if (linesDrawn[h1] || linesDrawn[h2]) { super.up(); super.goto(p); super.down(); this.slowpoke_skip++; return; } linesDrawn[h1] = linesDrawn[h2] = true; this.slowpoke_draw++; for (var dx = this.thickness-1; dx >=0 ; dx--) { for (var dy = this.thickness-1; dy >= 0; dy--) { if (!dx && !dy) continue; super.goto( x + dx * this.offset, y + dy * this.offset); super.goto(ox + dx * this.offset, oy + dy * this.offset); } } } super.goto(x, y); } } return new Slowbro(x, y); } // Adapted from https://github.com/joeiddon/perlin function Perlin(scale) { class Perlin { constructor(s) { this.gradients = {}; this.memory = {}; this.scale = s; } rand_vect() { let theta = Math.random() * 2 * Math.PI; return [Math.cos(theta), Math.sin(theta)]; } dot_prod_grid(x, y, vx, vy) { let g_vect; let d_vect = [x - vx, y - vy]; if (this.gradients[[vx,vy]]){ g_vect = this.gradients[[vx,vy]]; } else { g_vect = this.rand_vect(); this.gradients[[vx, vy]] = g_vect; } return d_vect[0] * g_vect[0] + d_vect[1] * g_vect[1]; } smootherstep(x) { return 6*x**5 - 15*x**4 + 10*x**3; } interp(x, a, b){ return a + this.smootherstep(x) * (b-a); } get(x, y) { x *= this.scale; y *= this.scale; if (this.memory.hasOwnProperty([x,y])) return this.memory[[x,y]]; let xf = Math.floor(x); let yf = Math.floor(y); //interpolate let tl = this.dot_prod_grid(x, y, xf, yf); let tr = this.dot_prod_grid(x, y, xf+1, yf); let bl = this.dot_prod_grid(x, y, xf, yf+1); let br = this.dot_prod_grid(x, y, xf+1, yf+1); let xt = this.interp(x-xf, tl, tr); let xb = this.interp(x-xf, bl, br); let v = this.interp(y-yf, xt, xb); this.memory[[x,y]] = v; return v; } } return new Perlin(scale); }