Mutate your path!
Log in to post a comment.
// Path mutation. Created by Reinder Nijhoff 2024 - @reindernijhoff // // https://turtletoy.net/turtle/ead5b35378 // const mutationRate = .01; // min=0.0, max=0.5, step=0.001 const mutationDefect = 45; // min=0, max=100, step=0.1 const grid = 13; // min=1, max=15, step=1 const pathInput = `M0,-37 C-10,-37 -26,-22 -30,-14 C-32,-10 -38,-3 -35,2 C-28,17 -7,39 13,29 C14,28 17,30 18,29 C59,9 37,-36 -3,-36`; // type=path, bbox=-40,-40,80,80 Click here to redraw the path let seed = 1; // min=1, max=1000, step=1 let tokens = pathInput.match(/([0-9.-]+|[MLC])/g); function Translate(x,y) { return p => [p[0]+x, p[1]+y]; } function Scale(s) { return p => [p[0]*s, p[1]*s]; } function mutation(tokens) { return tokens.map(token => { if (isNumber(token)) { return random() < mutationRate ? token - (random()-.5)*mutationDefect : token; } else return token; }); } function drawPath(i, tokens) { const y = i/grid|0, x = i%grid; const path = Path(tokens); const steps = path.length() | 0; const turtle = new Tortoise(path.p(0)); turtle.addTransform(Scale(1.65 / grid)); turtle.addTransform(Translate((x+.5)*180/grid-90, (y+.5)*180/grid-90)); for (let i=0; i<steps; i++) { turtle.goto(path.p( i/steps )); } } function walk(i) { drawPath(i, tokens); tokens = mutation(tokens); return i < grid*grid-1; } function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } function random() { let r = 1103515245 * (((seed+=12345) >> 1) ^ (seed)); r = 1103515245 * (r ^ (r >> 3)); r = r ^ (r >> 16); const mod = 1 << 20; return (r % mod) / mod; } //////////////////////////////////////////////////////////////// // Modified path utility code. Created by Reinder Nijhoff 2023 // Parses a single SVG path (only M, C and L statements are // supported). The p-method will return // [...position, ...derivative] for a normalized point t. // // https://turtletoy.net/turtle/46adb0ad70 //////////////////////////////////////////////////////////////// function Path(tokens) { class MoveTo { constructor(p) { this.p0 = p; } p(t, s) { return [...this.p0, 1, 0]; } length() { return 0; } } class LineTo { constructor(p0, p1) { this.p0 = p0, this.p1 = p1; } p(t, s = 1) { const nt = 1 - t, p0 = this.p0, p1 = this.p1; return [ nt*p0[0] + t*p1[0], nt*p0[1] + t*p1[1], (p1[0] - p0[0]) * s, (p1[1] - p0[1]) * s, ]; } length() { const p0 = this.p0, p1 = this.p1; return Math.hypot(p0[0]-p1[0], p0[1]-p1[1]); } } class BezierTo { constructor(p0, c0, c1, p1) { this.p0 = p0, this.c0 = c0, this.c1 = c1, this.p1 = p1; } p(t, s = 1) { const nt = 1 - t, p0 = this.p0, c0 = this.c0, c1 = this.c1, p1 = this.p1; return [ nt*nt*nt*p0[0] + 3*t*nt*nt*c0[0] + 3*t*t*nt*c1[0] + t*t*t*p1[0], nt*nt*nt*p0[1] + 3*t*nt*nt*c0[1] + 3*t*t*nt*c1[1] + t*t*t*p1[1], (3*nt*nt*(c0[0]-p0[0]) + 6*t*nt*(c1[0]-c0[0]) + 3*t*t*(p1[0]-c1[0])) * s, (3*nt*nt*(c0[1]-p0[1]) + 6*t*nt*(c1[1]-c0[1]) + 3*t*t*(p1[1]-c1[1])) * s, ]; } length() { return this._length || ( this._length = Array.from({length:25}, (x, i) => this.p(i/25)).reduce( (a,c,i,v) => i > 0 ? a + Math.hypot(c[0]-v[i-1][0], c[1]-v[i-1][1]) : a, 0)); } } class Path { constructor(tokens) { this.segments = []; this.parsePath(tokens); } parsePath(t) { for (let s, i=0; i<t.length;) { switch (t[i++]) { case 'M': this.add(new MoveTo(s=[t[i++],t[i++]])); break; case 'L': this.add(new LineTo(s, s=[t[i++],t[i++]])); break; case 'C': this.add(new BezierTo(s, [t[i++],t[i++]], [t[i++],t[i++]], s=[t[i++],t[i++]])); break; default: i++; } } } add(segment) { this.segments.push(segment); this._length = 0; } length() { return this._length || (this._length = this.segments.reduce((a,c) => a + c.length(), 0)); } p(t) { t = Math.max(Math.min(t, 1), 0) * this.length(); for (let l=0, i=0, sl=0; i<this.segments.length; i++, l+=sl) { sl = this.segments[i].length(); if (t > l && t <= l + sl) { return this.segments[i].p((t-l)/sl, sl/this.length()); } } return this.segments[Math.min(1, this.segments.length-1)].p(0); } } return new Path(tokens); } //////////////////////////////////////////////////////////////// // Tortoise utility code. Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/102cbd7c4d //////////////////////////////////////////////////////////////// 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); 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); }