A turtle to celebrate the birth of our daughter Esja.
Log in to post a comment.
// Esja. Created by Reinder Nijhoff 2020 // @reindernijhoff // // https://turtletoy.net/turtle/d4e8c7f64d // let mountainSeed = 54; // min=1, max=100, step=1 const waveSeed = 30; // min=1, max=100, step=1 const wrinkles = 14; // min=0, max=20, step=.1 const frequency = 0.3; //min=.1, max=1, step=.01 const waveSize = 24.; // min=0, max=60, step=.1 const xDisplacement = .24; // min=0, max=1, step=.01 const turtle = new Turtle(); const goldenRatio = 1.61803398875; const noise = new SimplexNoise(waveSeed); const mountainTops = []; esjaSea(); esjaMountains(); esjaWind(); //////////////////////////////////////////////////////////////// // Sea, Mountains, Wind //////////////////////////////////////////////////////////////// function esjaWind() { const turtle = new Tortoise().addTransform(simplexDistort); const curls = []; const lines = 70; for (let y=0; y<lines; y++) { const c = y * goldenRatio; const up = mountainRandom() > .5; curls.push( { x: (c - Math.floor(c)) * .75, y, up }); curls.push( { x: (c - Math.floor(c)) * .75 + .75, y, up }); } curls.sort( (a,b) => a.x-b.x ); const dither = new Dither(turtle, (p, l) => .5 * noise.noise2D([0.5,l*.3]) - .125 + Math.pow(Math.abs(.005 * p[1]), 1) - 7 / (.1+dist(p, [-45, -5])) ); for (let y=0; y<=lines; y++) { const ycor = -2 * y + 10; turtle.jump([-110, ycor]); const collisions = curls.filter(a => Math.abs(a.y-y) <= 3.5); collisions.forEach(curl => { const xcen = curl.x * 200 - 100; const ycen = -2 * curl.y + 10; if (curl.y === y + (curl.up ? 3 : -3)) { dither.goto([xcen - .5, ycor]); for (let a=0; a<20; a+=.1) { const cos = Math.cos(a) * (curl.up ? 1 : -1); const sin = Math.sin(a); dither.goto(add([xcen - .5, ycen], scale([sin, cos], 6 - a/4))); } } else { const xl = xcen - Math.sqrt(40 - (ycor - ycen)**2); dither.goto([xl, ycor]); } const xr = xcen + Math.sqrt(40 - (ycor - ycen)**2); turtle.jump([xr, ycor]); }); dither.goto([100, ycor]); } } function esjaMountains() { const numberMountains = 7; const allLines = []; const collisionLines = []; class MountainLine { constructor(p, dx) { this.start = this.end = p; this.dx = dx; } walkAndSplit() { const dy = 10 + 20 * mountainRandom() - this.start[1]; this.end = add(this.start, [this.dx*dy, dy]); collisionLines.forEach(line => { const int = segment_intersect(this.start, this.end, line.start, line.end); if (int !== false && !equal(this.start, int)) { this.end = int; } }); collisionLines.push(this); const len = dist(this.start, this.end); if (len > 4) { const f = 2-goldenRatio; allLines.push(new MountainLine(lerp(this.start, this.end, f), -this.dx)); } } } for (let i=0; i<numberMountains; i++) { const top =[(i+.5*mountainRandom()-.25)*200/(numberMountains-1)-100, -mountainRandom()*35]; allLines.push(new MountainLine(top, 1), new MountainLine(top,-1)); mountainTops.push(top); } const dither = new Dither(turtle, (p, l) => noise.noise2D([0.5,l*.3]) + 1 - .05 * p[1]); do { allLines.sort( (a,b) => 2 * mountainRandom() - 1); const line = allLines.pop(); line.walkAndSplit(); turtle.jump(line.start); dither.goto(line.end); } while (allLines.length > 0); } function esjaSea() { const turtle = new Tortoise().addTransform(simplexDistort); for (let z=0; z<65; z+=1.5) { const y = 20 / (1 + z) * 100; const dither = new Dither(turtle, (p, l) => noise.noise2D([y,l*.3]) + 1.2 - (z/37)); turtle.jump([-110,y]); dither.goto([110 ,y ]); } } //////////////////////////////////////////////////////////////// // Utility code //////////////////////////////////////////////////////////////// function simplexDistort(p) { const n = noise.noise2D([p[0]*frequency/200, p[1]*frequency/200]); const dist = waveSize * .2 * Math.sin(n * 3. * wrinkles) * ((.5 + .5 *n)**2); return [p[0] + dist * xDisplacement, p[1] + dist]; } function mountainRandom() { mountainSeed = 1103515245 * ((mountainSeed >> 1) ^ mountainSeed); mountainSeed = 1103515245 * (mountainSeed ^ (mountainSeed>>3)); mountainSeed = mountainSeed ^ (mountainSeed >> 16); return mountainSeed / 1103515245 % 1; } function Dither(turtle, densFunc) { class Dither { constructor(turtle, densFunc) { this.turtle = turtle; this.densFunc = densFunc; this.dist = 0; } goto(dest) { const p = this.turtle.pos(); const d = dist(p, dest); const startDist = this.dist, steps = Math.max(d*5, 1); for (let i=0; i<=steps; i++) { const c = lerp(p, dest, i/steps); if (this.densFunc(c, startDist + d * i/steps) <= 0) { this.turtle.up(); } else { this.turtle.down(); } this.turtle.goto(c); } this.dist += d; } } return new Dither(turtle, densFunc); } function equal(a,b) { return .001>dist_sqr(a,b); } function scale(a,b) { return [a[0]*b,a[1]*b]; } function add(a,b) { return [a[0]+b[0],a[1]+b[1]]; } function sub(a,b) { return [a[0]-b[0],a[1]-b[1]]; } function dot(a,b) { return a[0]*b[0]+a[1]*b[1]; } function dist_sqr(a,b) { return (a[0]-b[0])**2+(a[1]-b[1])**2; } function dist(a,b) { return Math.sqrt(dist_sqr(a,b)); } function length(a) { return Math.sqrt(dot(a,a)); } function lerp(a,b,t) { return [a[0]*(1-t)+b[0]*t,a[1]*(1-t)+b[1]*t]; } function segment_intersect(a,b,d,c) { const e=(c[1]-d[1])*(b[0]-a[0])-(c[0]-d[0])*(b[1]-a[1]); if(0==e)return false; c=((c[0]-d[0])*(a[1]-d[1])-(c[1]-d[1])*(a[0]-d[0]))/e; d=((b[0]-a[0])*(a[1]-d[1])-(b[1]-a[1])*(a[0]-d[0]))/e; return 0<=c&&1>=c&&0<=d&&1>=d?[a[0]+c*(b[0]-a[0]),a[1]+c*(b[1]-a[1])]:false; } function Tortoise(x, y) { class Tortoise extends Turtle { constructor(x, y) { super(x, y); this.ps = Array.isArray(x) ? [...x] : [x || 0, y || 0]; this.transforms = []; } addTransform(t) { this.transforms.push(t); this.jump(this.ps); return this; } applyTransforms(p) { if (!this.transforms) return p; let pt = [...p]; this.transforms.map(t => { pt = t(pt); }); return pt; } goto(x, y) { const p = Array.isArray(x) ? [...x] : [x, y]; const pt = this.applyTransforms(p); for (let i=0; i<mountainTops.length; i++) { const top = mountainTops[i]; if (Math.abs(top[0]-pt[0]) < pt[1]-top[1]) { this.up(); break; } } if (this.isdown() && (this.pt[0]-pt[0])**2 + (this.pt[1]-pt[1])**2 > 4) { this.goto((this.ps[0]+p[0])/2, (this.ps[1]+p[1])/2); this.goto(p); } else { super.goto(pt); this.ps = p; this.pt = pt; } } position() { return this.ps; } } return new Tortoise(x,y); } function SimplexNoise(seed = 1) { const grad = [ [1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0], [1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1], [0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1] ]; const perm = new Uint8Array(512); const F2 = (Math.sqrt(3) - 1) / 2; const G2 = (3 - Math.sqrt(3)) / 6; class SimplexNoise { constructor(seed = 1) { for (let i = 0; i < 512; i++) { perm[i] = i & 255; } for (let i = 0; i < 255; i++) { const r = (seed = this.hash(i+seed)) % (256 - i) + i; const swp = perm[i]; perm[i + 256] = perm[i] = perm[r]; perm[r + 256] = perm[r] = swp; } } noise2D(p) { const s = dot(p, [F2, F2]); const c = [Math.floor(p[0] + s), Math.floor(p[1] + s)]; const i = c[0] & 255, j = c[1] & 255; const t = dot(c, [G2, G2]); const p0 = sub(p, sub(c, [t, t])); const o = p0[0] > p0[1] ? [1, 0] : [0, 1]; const p1 = sub(sub(p0, o), [-G2, -G2]); const p2 = sub(p0, [1-2*G2, 1-2*G2]); let n = Math.max(0, 0.5-dot(p0, p0))**4 * dot(grad[perm[i+perm[j]] % 12], p0); n += Math.max(0, 0.5-dot(p1, p1))**4 * dot(grad[perm[i+o[0]+perm[j+o[1]]] % 12], p1); n += Math.max(0, 0.5-dot(p2, p2))**4 * dot(grad[perm[i+1+perm[j+1]] % 12], p2); return 70 * n; } hash(i) { i = 1103515245 * ((i >> 1) ^ i); const h32 = 1103515245 * (i ^ (i>>3)); return h32 ^ (h32 >> 16); } } return new SimplexNoise(seed); }