Pendula 🕰️

Each 'doodle' is drawn by a set of pendula, one for each 'arm'. A pendulum consists of a number of 'segments' where each segment is a pendulum on its own with its own frequency.

Log in to post a comment.

const rndSeed = ''; //type=string Change me, empty is random each run
const gridSize = 5; //min=1 max=10 step=1
const margin = 5; //min=0 max=20 step=.1
const arms = 1; //min=1 max=10 step=1
const armR = 0; //min=0 max=1 step=.01
const segments = 3; //min=2 max=20 step=1
const iterationExp = 3.65; //min=1 max=8 step=.01
const roundExp = 3; //min=1 max=6 step=1

const canvasSize = 200;

// You can find the Turtle API reference here: https://turtletoy.net/syntax
turtlelib_init();
const useSeed = rndSeed == ''? (new Date()).getMilliseconds() + ('' + new Date()).substring(0, 24): rndSeed;
R.seed(useSeed);
Canvas.setpenopacity(.7);
const cellSize = (canvasSize - margin * (1 + gridSize)) / gridSize;
const origins = Array.from({length: arms}, (e, i) => [(armR * cellSize / 2) * Math.cos(i * 2 * Math.PI / arms), (armR * cellSize / 2) * Math.sin(i * 2 * Math.PI / arms)]);
const armLength = cellSize / 2 - origins[0][0];
const segmentLength = armLength / segments;

const grid = Array.from({length: gridSize}).flatMap((e, r) => Array.from({length: gridSize}, (e, c) => [c, r].map(e => margin + e * (cellSize + margin) + cellSize / 2 - canvasSize / 2))).map(pt => new Pendula(pt, origins, segments, segmentLength));

// The walk function will be called until it returns false.
function walk(i) {
    grid.filter(cell => cell.step());
    return i < 10**iterationExp;
}

function Pendula(pos, armOrigins, segments, segmentLength) {
    const add = (a, b) => [a[0]+b[0], a[1]+b[1]];

    class Pendula {
        constructor(pos, segments, segmentLength) {
            this.waveLengths = Array.from({length: segments}, (e, i) => ((Math.random() * 2 - 1) * 10**roundExp | 0) / 10**roundExp);
            this.waveLengthss = Array.from({length: segments}, (e, i) => ((Math.random() * 2 - 1) * 10**roundExp | 0) / 10**roundExp);
            this.offsets = Array.from({length: segments}, (e, i) => Math.random() * 2 * Math.PI);
            this.offsetss = Array.from({length: segments}, (e, i) => Math.random() * 2 * Math.PI);
            this.segmentR = segmentLength;
            this.phase = 0;
            this.origin = pos;
            this.turtle = new Turtle(this.value());
        }
        value() {
            let location = this.origin;
            for(let i = 0; i < this.waveLengths.length; i++) {
                location = add(location, [
                    this.segmentR * Math.cos(this.waveLengths[i] * this.phase + this.offsets[i]),
                    this.segmentR * Math.sin(this.waveLengths[i] * this.phase + this.offsets[i])
                ]);
            }
            return location;
        }
        step(inc = .1) {
            this.phase += inc;
            this.turtle.goto(this.value());
        }
    }
    
    class Pendulae {
        constructor(pos, armOrigins, segments, segmentLength) {
            this.pendulae = armOrigins.map(e => new Pendula(add(pos, e), segments, segmentLength));
        }
        step(inc = .1) {
            this.pendulae.forEach(p => p.step(inc));
        }
    }
    return new Pendulae(pos, armOrigins, segments, segmentLength);
}

// Below is automatically maintained by Turtlelib 1.0
// Changes below this comment might interfere with its correct functioning.
function turtlelib_init() {
	turtlelib_ns_13b81fd40e_Jurgen_Randomness();
}
// Turtlelib Jurgen Randomness v 2 - start - {"id":"13b81fd40e","package":"Jurgen","name":"Randomness","version":"2"}
function turtlelib_ns_13b81fd40e_Jurgen_Randomness() {
///////////////////////////////////////////////////////////////
// Pseudorandom functions - Created by Jurgen Westerhof 2024 //
///////////////////////////////////////////////////////////////
class Random {
    static #apply(seed) {
        // Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html
        !function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random");
        Math.seedrandom(seed);
    }
    static seedRandom() { this.#apply(new Date().getMilliseconds()); }
    static seedDaily() { this.#apply(new Date().toDateString()); }
    static seed(seed) { this.#apply(seed); }
    static getInt(min, max) { if(max == undefined) {max = min + 1; min = 0; } const [mi, ma] = [Math.min(min, max), Math.max(min, max)]; return (mi + Math.random() * (ma - mi)) | 0;}
    static get(min, max) {if(min == undefined) {return Math.random();}if(max == undefined) {max = min;min = 0;}const [mi, ma] = [Math.min(min, max), Math.max(min, max)];return mi + Math.random() * (ma - mi);}
    static fraction(whole) { return this.get(0, whole); }
    static getAngle(l = 1) { return l * this.get(0, 2*Math.PI); }
    // Standard Normal variate using Box-Muller transform.
    static getGaussian(mean=.5, stdev=.1) {const u = 1 - this.get(); /* Converting [0,1) to (0,1] */const v = this.get();const z = ( -2.0 * Math.log( u ) )**.5 * Math.cos( 2.0 * Math.PI * v );/* Transform to the desired mean and standard deviation: */return z * stdev + mean;}
    static skew(value, skew = 0) { /*skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed*/return (skew < 0)? value - (this.skew(1-value, -skew) - (1-value)): Math.pow(value, 1-Math.abs(skew));}
    static getNormalDistributed(skew = 0) { /*skew values (from 0 to 1) by a skew from -1 to 1, respectively right and left skewed (resp more values to left or to right), 0 is not skewed*/let v = -1;while(v < 0 || 1 <= v) { v = this.getGaussian(.5, .1) };return this.skew(v, skew);}
}
this.R = Random;
}
// Turtlelib Jurgen Randomness v 2 - end