Bézier Grid 🌊

Known to Turtletoy is Bézier Grid 🌊 (variation) like in Interpolated random grid and Bézier Grid 🌊 (variation) like in Grid

This adds Bézier curves to the equation, making things like Bézier Grid 🌊 (variation) possible.

en.wikipedia.org/wiki/b%c3%a9zier_curve

Log in to post a comment.

const seed = 0; //min=0 max=99999 step=1
const type = 2; //min=0 max=2 step=1 (Linear, Fixed Bezier, Dynamic Bezier)
const rows = 25; //min=1 max=50 step=1
const columns = 25;//min=1 max=50 step=1
const padding = 10; //min=-50 max=50 step=1
const displace = .48; //min=0 max=2 step=.01
const pDisplace = .5; //min=0 max=1 step=.01
const horizontals = 25; //min=0 max=40 step=1
const verticals = 25; //min=0 max=40 step=1
const bezierLength = .3; //min=0 max=3 step=.1
const penOpacity = .3;//min=.1 max=1 step=.1

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(penOpacity);
const CanvasDimensions = [200, 200];
const CellDimensions = [(CanvasDimensions[0] - padding - padding) / columns, (CanvasDimensions[1] - padding - padding) / rows];


if(seed !== 0) {
    // 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('anything here' + seed);
    // Add a seed in seedrandom, then Math.random will use this seed
}

const add2 = (a, b) => [a[0]+b[0], a[1]+b[1]];
const sub2 = (a, b) => [a[0]-b[0], a[1]-b[1]];
const mul2 = (a, b) => [a[0]*b[0], a[1]*b[1]];
const scale2 = (a, s) => mul2(a, [s,s]);
const lerp2 = (a,b,t) => [a[0]*(1-t) + b[0]*t, a[1]*(1-t) + b[1]*t];
const lenSq2 = (a) => a[0]**2+a[1]**2;
const len2 = (a) => Math.sqrt(lenSq2(a));

const bezier2 = (origin, relativeOriginControl, target, relativeTargetControl, steps) => Array.from({length: steps + 1}, (v, idx) => {
    const t = idx/steps;
    
    const originControl = add2(origin, relativeOriginControl);
    const targetControl = add2(target, relativeTargetControl);
    
    const originDev = lerp2(origin, originControl, t);
    const secDev = lerp2(originControl, targetControl, t);
    const targetDev = lerp2(targetControl, target, t);
    
    const originPt = lerp2(originDev, secDev, t);
    const targetPt = lerp2(secDev, targetDev, t);

    return lerp2(originPt, targetPt, t);
});
const redistributeLinear = (path, steps) => {
    const segments = path.map((v, i, arr) => [v, arr[(i+1)%arr.length], len2(sub2(v, arr[(i+1)%arr.length]))]);
    const last = segments.pop();
    const length = segments.reduce((p, v) => p + v[2], 0);
    const stepLength = length/steps;

    let lengthLeft = stepLength;
    const result = [segments[0][0]];
    while(segments.length > 0) {
        const segment = segments.shift();
        const delta = lengthLeft - segment[2];
        if(delta > 0) {
            lengthLeft = delta;
            continue;
        }
        if(delta === 0) {
            result.push(segment[1]);
            lengthLeft = stepLength;
            continue;
        }
        const newPoint = lerp2(segment[0], segment[1], lengthLeft / segment[2]);
        result.push(newPoint);
        segments.unshift([newPoint, segment[1], segment[2] - lengthLeft]);
        lengthLeft = stepLength;
    }
    if(result.length < steps + 1) result.push(last[0]);
    return result;
}

// Global code will be evaluated once.
const turtle = new Turtle();

const randomDisplacement = (angle) => {
    const fullDisplacement = scale2(mul2(CellDimensions, [Math.cos(angle),  Math.sin(angle)]), displace);
    //0 <= pDisplace <= .5 then no to full random displacement
    //.5 <= pDisplace <= 1 then full random to full displacement
    const factor = (pDisplace <= .5)? pDisplace * 2 * Math.random(): 1 - ((1-pDisplace) * 2) * Math.random();
    return scale2(fullDisplacement, factor);
}

const crossings = Array.from({length: rows + 1}).map((row, y) => Array.from({length: columns + 1}).map((column, x) => { return {
    origin: [padding - CanvasDimensions[0] / 2 + x * CellDimensions[0], padding - CanvasDimensions[1] / 2 + y * CellDimensions[1]],
    location: add2(
        [padding - CanvasDimensions[0] / 2 + x * CellDimensions[0], padding - CanvasDimensions[1] / 2 + y * CellDimensions[1]],
        randomDisplacement(Math.random() * Math.PI * 2)
    )};
}));

const point = (...pts) => pts.forEach((p, i) => {
    turtle.jump(p[0], p[1] - .15);
    turtle.circle(.3);
});

// The walk function will be called until it returns false.
function walk(i) {
    const column = i % (columns + 1);
    const row = i / (columns + 1) | 0;

    if(row == rows) return false;
    if(column == columns) return true;
    
    const isRightEdge = (column == columns - 1);
    const isBottomEdge = (row == rows - 1);
    
    switch(type) {
        case 0:
            drawLines(crossings[row][column], crossings[row + 1][column], crossings[row][column + 1], crossings[row + 1][column + 1], horizontals, isBottomEdge);
            drawLines(crossings[row][column], crossings[row][column + 1], crossings[row + 1][column], crossings[row + 1][column + 1], verticals, isRightEdge);
            break;
        case 1:
        case 2:
            drawBezierLines(crossings[row][column], crossings[row + 1][column], crossings[row][column + 1], crossings[row + 1][column + 1], horizontals, isBottomEdge, type == 1);
            drawBezierLines(crossings[row][column], crossings[row][column + 1], crossings[row + 1][column], crossings[row + 1][column + 1], verticals, isRightEdge, type == 1);
            break
    }
    return true;
}

function drawLines(leftTop, leftBottom, rightTop, rightBottom, steps, drawLast) {
    for(let i = 0, max = steps - 1; i < steps - (drawLast? 0: 1); i++) {
        turtle.jump(lerp2(leftTop.location, leftBottom.location, i/max));
        turtle.goto(lerp2(rightTop.location, rightBottom.location, i/max));
    }
}

function drawBezierLines(leftTop, leftBottom, rightTop, rightBottom, steps, drawLast, fixed = true) {
    const fixedFromControl = scale2(sub2(rightTop.origin, leftTop.origin), .5);
    const fixedToControl = scale2(fixedFromControl, -1);

    const rightTopControl = scale2(sub2(rightTop.location, rightTop.origin), bezierLength);
    const leftTopControl = scale2(sub2(leftTop.location, leftTop.origin), bezierLength);
    
    const rightBottomControl = scale2(sub2(rightBottom.location, rightBottom.origin), bezierLength);
    const leftBottomControl = scale2(sub2(leftBottom.location, leftBottom.origin), bezierLength);
    
    
    const fromLine = redistributeLinear(
                        bezier2(leftTop.location, leftTopControl,
                             leftBottom.location, leftBottomControl, 40)
                        , steps);
    const toLine   = redistributeLinear(
                        bezier2(rightTop.location, rightTopControl,
                             rightBottom.location, rightBottomControl, 40)
                        , steps);
    
    const cellDim = Math.max(len2(sub2(leftTop.location, rightTop.location)), len2(sub2(leftBottom.location, rightBottom.location)));
    
    fromLine.forEach((v, i) => {
        if(i < fromLine.length - 1 || drawLast) {
            const p = i/(fromLine.length - 1);

            const fromControl = fixed? fixedFromControl: lerp2(leftTopControl, leftBottomControl, p);
            const toControl = fixed? fixedToControl: lerp2(rightTopControl, rightBottomControl, p);
            
            const pts = bezier2(v, fromControl, toLine[i], toControl, Math.ceil(cellDim));
            pts.forEach((p, i) => {
                turtle[i == 0? 'jump': 'goto'](p);
            });
        }
    });
}