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