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
// ----------------------
const scale = 1;        // overall scaling
const spacing = 2;      // distance between offset curves
const copies = 20;      // number of offset curves
const offsetX = -95;    // shift entire curve to the left
const L = 18;           // length of drawn segment and gap (dash length)
const offsetY_mirror = -10; // vertical shift for mirrored copy

// ----------------------
// DEFINE EXTRA FUNCTION
// ----------------------
// extra can now vary with curve index k
function extra_length(k) {
    // Example: constant
    //return 3;    
    
    // Example: increase linearly
    return 2 + k * 0.1;

    // Example: decrease linearly
    //return 5 - k * 0.2;

    // Example: sinusoidal variation
    //return 2 + 1 * Math.sin(k / 3);

}

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

//S-curve using scaled tanh
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 >= L) {
                acc -= L;
                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 + offsetX, scale * ya);
        turtle.pendown();
        turtle.goto(scale * xb + offsetX, scale * yb);
        
        // 2. both axes (with Y offset)
        turtle.penup();
        turtle.goto(-(scale * xa + offsetX), -scale * ya + offsetY_mirror);
        turtle.pendown();
        turtle.goto(-(scale * xb + offsetX), -scale * yb + offsetY_mirror);
    }
}

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

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

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

    return false;
}