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);
}