Tunnel Kirigami

Cutting pattern that deploys into a tunnel when a load is applied perpendicularly to the sheet along the center path.

Log in to post a comment.

Canvas.setpenopacity(1);

const turtle = new Turtle();

// ----------------------
// PARAMETERS
// ----------------------
// overall scaling
let scale = 1; //min = 0, max = 10, step = 0.1  

// distance between offset curves
let spacing = 2; //min = 1, max = 50, step = 1

// number of offset curves
let number_line = 20; //min = 1, max = 50, step = 1      

// length of drawn segment and gap (dash length)
let cut_length = 18; //min = 1, max = 50, step = 1  

// shift entire curve to the left
let X = -95; //min = -100, max = 0, step = 1   

// vertical shift for mirrored copy
let Y = -10; //min = -50, max = 0, step = 1 


// ----------------------
// DEFINE EXTRA FUNCTION
// ----------------------
// extra can now vary with curve index k
let overlap_mode = 1; // min=1, max=4, step=1

if (overlap_mode == 1) {
    // constant slope
    function extra_length(k) { return 5; }
} else if (overlap_mode == 2) {
    // linear increase
    function extra_length(k) { return 2 + k * 0.1; }
} else if (overlap_mode == 3) {
    // linear decrease
    function extra_length(k) { return 5 - k * 0.2; }
} else {
    // sinusoidal variation
    function extra_length(k) { return 2 + 1 * Math.sin(k / 3); }
}

// ----------------------
// DEFINE BASE CURVE
// ----------------------
//Straight line
//function curve(x) {return 0;} // completely flat, straight line along y = 0

let curve_mode = 1; // min=1, max=2, step=1

if (curve_mode == 1) {
    // constant slope
    function curve(x)  { return 3; }
} else if (curve_mode == 2) {
    // linear increase
    function curve(x)  {return 20 * Math.tanh((x - 95) / 30);}
}

// ----------------------
// SAMPLE BASE CURVE
// ----------------------
function sample_curve() {
    let pts = [];
    let step = 0.1;

    for (let x = 0; x <= 190; x += step) {
        pts.push([x, curve(x)]);
    }

    return pts;
}

// ----------------------
// OFFSET A CURVE
// ----------------------
function offset_curve(base_pts, d) {
    let result = [];

    for (let i = 0; i < base_pts.length - 1; i++) {
        let [x1, y1] = base_pts[i];
        let [x2, y2] = base_pts[i + 1];

        let dx = x2 - x1;
        let dy = y2 - y1;
        let len = Math.sqrt(dx*dx + dy*dy);

        let nx = -dy / len;
        let ny = dx / len;

        result.push([x1 + d * nx, y1 + d * ny]);
    }

    let last = base_pts[base_pts.length - 1];
    let [xL, yL] = last;
    let prev = base_pts[base_pts.length - 2];
    let dx = xL - prev[0];
    let dy = yL - prev[1];
    let len = Math.sqrt(dx*dx + dy*dy);
    let nx = -dy / len;
    let ny = dx / len;
    result.push([xL + d * nx, yL + d * ny]);

    return result;
}

// ----------------------
// COMPUTE DASH PATTERN
// ----------------------
function compute_pattern(points, L) {
    let pattern = [];

    let acc = 0;
    let drawing = true;

    for (let i = 0; i < points.length - 1; i++) {
        let [x1, y1] = points[i];
        let [x2, y2] = points[i + 1];

        let dx = x2 - x1;
        let dy = y2 - y1;
        let seg_len = Math.sqrt(dx*dx + dy*dy);

        let traveled = 0;

        while (traveled < seg_len) {
            let remaining = seg_len - traveled;
            let needed = L - acc;
            let step = Math.min(remaining, needed);

            let t1 = traveled / seg_len;
            let t2 = (traveled + step) / seg_len;

            pattern.push({
                i: i,
                t1: t1,
                t2: t2,
                draw: drawing
            });

            traveled += step;
            acc += step;

            if (acc >= cut_length) {
                acc -= cut_length;
                drawing = !drawing;
            }
        }
    }

    return pattern;
}

// ----------------------
// DRAW CURVE WITH VARIABLE EXTRA
// ----------------------
function draw_curve(points, pattern, k) {
    const extra = extra_length(k); // get extra for this curve

    for (let p of pattern) {
        let draw = (k % 2 === 0) ? p.draw : !p.draw;
        if (!draw) continue;

        let [x1, y1] = points[p.i];
        let [x2, y2] = points[p.i + 1];

        let dx = x2 - x1;
        let dy = y2 - y1;
        let seg_len = Math.sqrt(dx*dx + dy*dy);

        let xa = x1 + dx * p.t1 - extra * dx / seg_len;
        let ya = y1 + dy * p.t1 - extra * dy / seg_len;
        let xb = x1 + dx * p.t2 + extra * dx / seg_len;
        let yb = y1 + dy * p.t2 + extra * dy / seg_len;

        // 1. original
        turtle.penup();
        turtle.goto(scale * xa + X, scale * ya);
        turtle.pendown();
        turtle.goto(scale * xb + X, scale * yb);
        
        // 2. both axes (with Y offset)
        turtle.penup();
        turtle.goto(-(scale * xa + X), -scale * ya + Y);
        turtle.pendown();
        turtle.goto(-(scale * xb + X), -scale * yb + Y);
    }
}

// ----------------------
// MAIN WALK FUNCTION
// ----------------------
function walk(i) {
    if (i > 0) return false;

    let base = sample_curve();
    let pattern = compute_pattern(base, cut_length);

    for (let k = 0; k < number_line; k++) {
        let d = k * spacing;
        let curve_k = offset_curve(base, d);
        draw_curve(curve_k, pattern, k);
    }

    return false;
}