Rounded whatever 👙

Please let me also get on the rounded corner hype train! I don't know what the fuzz is about but I want a piece!

Mouseover the variables to read what they do.

Triangle: Rounded whatever 👙 (variation)
Square: Rounded whatever 👙 (variation)
Pentagon: Rounded whatever 👙 (variation)
Etc...

Log in to post a comment.

const edges = 4; //min=3 max=10 step=1 Start with a regular polygon
const rotateFirstPI = .25; //min=0 max=2 step=.01 Then rotate that this amount of a full rotation
const width = 150; //min=10 max=200 step=1 Then strecht that result to fit in this width
const height = 75; //min=10 max=200 step=1 Then strecht that result to fit in this height
const cornerRadius = 10; //min=0 max=50 step=1 The radius of the first (or all) corner(s). Note that there's no protection from specifying a value that's too large
const repeatCornerRadius = 1; //min=0 max=4 step=1 (No; use values specified below, Yes; use cornerRadius for every corner and ignore variables below, Partailly; repeat cornerRadius and cr2 alternating, Partailly; repeat cornerRadius and cr2 and cr3 alternating, Partailly; repeat cornerRadius and cr2 and cr3 and cr4 alternating)
const cr2 = 0; //min=0 max=50 step=1
const cr3 = 0; //min=0 max=50 step=1
const cr4 = 0; //min=0 max=50 step=1
const cr5 = 0; //min=0 max=50 step=1
const cr6 = 0; //min=0 max=50 step=1
const cr7 = 0; //min=0 max=50 step=1
const cr8 = 0; //min=0 max=50 step=1
const cr9 = 0; //min=0 max=50 step=1
const cr10 = 0; //min=0 max=50 step=1

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(.7);

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

// The walk function will be called until it returns false.
function walk(i) {
    const radii = ((repeat) => {
        const all = [cornerRadius, cr2, cr3, cr4, cr5, cr6, cr7, cr8, cr9, cr10];
        switch(repeat) {
            case 0: return all;
            case 1: return [cornerRadius];
            default: return Array.from({length: 10}).map((v,i)=> all[i%repeat]);
        }
    })(repeatCornerRadius);
    
    const pts = getRoundedCornerPolygon(width, height, edges, rotateFirstPI, ...radii);
    drawPath(turtle, pts);
    return false;
}

function drawPath(turtle, pts) {
    [pts].map(v => v.concat([v[0]])).pop().forEach((pt, i) => turtle[(i == 0? 'jump': 'goto')](pt));
}

function getRoundedCornerPolygon(width, height, edges, rotation, ...cornerRadiuses) {
    const pointAngle = (pt) => Math.PI - Math.atan2(...pt);

    const cornerRadii = Array.from({length: edges}).map((v, i) => cornerRadiuses[i] === undefined? (i == 0? 0: cornerRadiuses[0]): cornerRadiuses[i]);

    const pts = getPolygon(width, height, edges, rotation);
    if(width == 0 || height == 0 || edges < 3) return pts;

    const vectorsFromPoints = pts.map(
        (v, i, a) => [
            [sub2(a[(i - 1 + a.length)%a.length], v)].map(v => [v, pointAngle(v)]).pop(),
            [sub2(a[(i + 1)%a.length], v)].map(v => [v, pointAngle(v)]).pop()
        ]
    ).map(vfp => [vfp[0][0], vfp[1][0], vfp[0][1] - vfp[1][1] + (vfp[1][1] > vfp[0][1]? 2*Math.PI: 0)]);
    
    const lengths = vectorsFromPoints.map(v => len2(v[0]));
    
    const cornerSplittingVectors = vectorsFromPoints.map(
        (vfp, i) => [
            //vector from corner to center circle
            scale2(add2(norm2(vfp[0]), norm2(vfp[1])), cornerRadii[i] / (Math.sin(vfp[2]))),
            //vector from corner to tangent of circle on way to previous point
            scale2(vfp[0], (cornerRadii[i] / Math.tan(vfp[2]/2)) / lengths[i]),
            //vector from corner to tangent of circle on way to next point
            scale2(vfp[1], (cornerRadii[i] / Math.tan(vfp[2]/2)) / lengths[(i+1)%lengths.length]),
        ]
    );

    const cornerCircleData = cornerSplittingVectors.map((csv, i) =>
        [add2(pts[i], csv[0])].map(circleCenter => [
            circleCenter,
            pointAngle(sub2(add2(pts[i], csv[1]), circleCenter)),
            pointAngle(sub2(add2(pts[i], csv[2]), circleCenter))
        ]).pop(),
        
    ).map(cd => [cd[0], cd[1] - (cd[2] < cd[1]? 2*Math.PI:0), cd[2]]);
    
    const lerp1 = (a, b, t) => a + (b-a)*t;

    const corners = cornerCircleData.map((ccd, j) => {
        return [Math.max(1, (cornerRadii[j] * 2 * (ccd[2] - ccd[1])) / Math.PI | 0)].map(steps => Array.from({length: steps + 1})
            .map((v, i, a, f = lerp1(ccd[1], ccd[2], i / steps)) => [
                cornerRadii[j] * Math.sin(f),
                cornerRadii[j] * -Math.cos(f)
            ])
        ).pop().map(pt => add2(ccd[0], pt))
    });

    return corners.flatMap(v => v)
}

function getPolygon(width, height, edges, rotation = 0) {
    const pts = getRegularPolygonPoints(Math.max(width, height), edges, rotation * 2 * Math.PI);
    const wh = pts.reduce((a, c) => [[Math.min(a[0][0], c[0]), Math.max(a[0][1], c[0])], [Math.min(a[1][0], c[1]), Math.max(a[1][1], c[1])]], [[0,0],[0,0]]);
    return pts.map(pt => mul2(pt, [width / (wh[0][1] - wh[0][0]), height / (wh[1][1] - wh[1][0])]));
}

////////////////////////////////////////////////////////////////
// 2D Vector Math utility code - Created by several Turtletoy users
////////////////////////////////////////////////////////////////
function norm2(a) { return scale2(a, 1/len2(a)); }
function add2(a, b) { return [a[0]+b[0], a[1]+b[1]]; }
function sub2(a, b) { return [a[0]-b[0], a[1]-b[1]]; }
function mul2(a, b) { return [a[0]*b[0], a[1]*b[1]]; }
function scale2(a, s) { return [a[0]*s,a[1]*s]; }
function lerp2(a,b,t) { return [a[0]*(1-t) + b[0]*t, a[1]*(1-t) + b[1]*t]; }
function lenSq2(a) { return a[0]**2+a[1]**2; }
function len2(a) { return Math.sqrt(lenSq2(a)); }
/*
function rot2(a) { return [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)]; }
function trans2(m, a) { return [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]]; } //Matrix(2x1) x Matrix(2x2)
function dist2(a,b) { return Math.hypot(...sub2(a,b)); }
function dot2(a,b) { return a[0]*b[0]+a[1]*b[1]; }
function cross2(a,b) { return a[0]*b[1] - a[1]*b[0]; }
function multiply2(a2x2, a) { return [(a[0]*a2x2[0])+(a[1]*a2x2[1]),(a[0]*a2x2[2])+(a[1]*a2x2[3])]; } //Matrix(2x2) x Matrix(1x2)
*/
function getRegularPolygonPoints(r, steps = null, rotatedStart = 0) {
    return getRegularPolygonPointsElipse(r, r, steps, rotatedStart);
}
function getRegularPolygonPointsElipse(rx, ry, steps = null, rotatedStart = 0) {
    return Array.from({length: steps === null? Math.max(12, 2 * Math.PI * Math.max(rx, ry)): steps})
    .map(
        (v, i, a, f = rotatedStart/2 + Math.PI * 2 * i / a.length) => [rx * Math.sin(f), ry * -Math.cos(f)]
    );
}