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