Expanding Rects

Rectangles expand, one side at a time, until they hit each other.

Log in to post a comment.

let NUM_RECTS = 1000; // min=0, max=1000, step=1
let CENTER_RAD = 1;
let SHRINK = 1; // min=-10, max=10, step=1
let HIT_SIDES_CHANCE = 0.2; // min=0, max=1, step=0.1
let OUTER_PADDING = 10; // min=0, max=50, step=1
let SEED_PADDING = 30; // min=0, max=50, step=1
let MIN_RAD = 2; // min=0, max=20, step=1
let MAX_RAD = 10; // min=0, max=20, step=1
let VERTICAL_CHANCE = 0.66; // min=0, max=1, step=0.1
// leave this 1 for square
let THIN_STRETCH_FACTOR = 2;  // min=1, max=10, step=1

// booleans
let DO_EXPAND = 1; // min=0, max=1, step=1
let DRAW_CENTERS = 0; // min=0, max=1, step=1
let DRAW_FRAME = 1; // min=0, max=1, step=1
let DRAW_X = 0; // min=0, max=1, step=1

let HATCH_CHANCE = 0.8; // min=0, max=1, step=0.1
let HATCH_SPACING = 1.5; // min=1, max=5, step=0.5
let SPARSE_HATCH_CHANCE = 0.15; // min=0, max=1, step=0.1

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

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

let rand = (min, max) =>
    Math.random() * (max - min) + min;

let randInt = (min, max) =>
    Math.floor(rand(min, max));

let rangeOverlaps = (a0, a1, b0, b1) =>
    (a0 < b1 && a1 > b0) || (b0 < a1 && b1 > a0);

let Line = (x0, y0, x1, y1) => {
    let line = {
        x0: x0,
        x1: x1,
        y0: y0,
        y1: y1,
    };
    line.draw = () => {
        turtle.jump(line.x0, line.y0);
        turtle.goto(line.x1, line.y1);
    };
    return line;
};

let Rect = (x0, x1, y0, y1) => {
    let r = {
        xc: (x0 + x1) / 2,
        yc: (y0 + y1) / 2,
        x0: x0,
        x1: x1,
        y0: y0,
        y1: y1,
    };
    r.draw = () => {
        if (DRAW_FRAME) {
            turtle.jump(r.x0, r.y0);
            turtle.goto(r.x1, r.y0);
            turtle.goto(r.x1, r.y1);
            turtle.goto(r.x0, r.y1);
            turtle.goto(r.x0, r.y0);
        }
        if (DRAW_X) {
            turtle.jump(r.x0, r.y0);
            turtle.goto(r.x1, r.y1);
            turtle.jump(r.x1, r.y0);
            turtle.goto(r.x0, r.y1);
        }
        if (DRAW_CENTERS) {
            turtle.jump(r.xc, r.yc - CENTER_RAD);
            turtle.circle(CENTER_RAD);
        }
        if (Math.random() < HATCH_CHANCE) {
            let h = r.y1 - r.y0;
            let spacing = HATCH_SPACING;
            if (Math.random() < SPARSE_HATCH_CHANCE) { spacing *= 2; }
            for (let xx = r.x0; xx <= r.x1 + h; xx += spacing) {
                let line = Line(xx, r.y0, xx - h, r.y1);
                if (line.x1 < r.x0) {
                    line.x1 = r.x0;
                    line.y1 = xx - r.x0 + r.y0;
                }
                if (line.x0 > r.x1) {
                    line.x0 = r.x1;
                    line.y0 = xx - r.x1 + r.y0;
                }
                line.draw();
            }
        }
    };
    r.intersects = (other) => (
        rangeOverlaps(r.x0, r.x1, other.x0, other.x1)
     && rangeOverlaps(r.y0, r.y1, other.y0, other.y1)
    );
    r.expand = (amt) => {
        r.x0 -= amt;
        r.y0 -= amt;
        r.x1 += amt;
        r.y1 += amt;
    };
    r.isValid = () => 
        r.x0 < r.x1 && r.y0 < r.y1;
    return r;
};

let rects = [];

// place initial rects and delete overlapping ones
for (let ii = 0; ii < NUM_RECTS; ii++) {
    let limit = 100 - SEED_PADDING;
    let x = randInt(-limit, limit);
    let y = randInt(-limit, limit);
    let xr = randInt(MIN_RAD, MAX_RAD);
    let yr = randInt(MIN_RAD, MAX_RAD);
    if (Math.random() < VERTICAL_CHANCE) {
        xr *= THIN_STRETCH_FACTOR;
    } else {
        yr *= THIN_STRETCH_FACTOR;
    }
    let rect = Rect(x-xr, x+xr, y-yr, y+yr);
    let intersects = false;
    for (let jj = 0; jj < rects.length; jj++) {
        if (rects[jj].intersects(rect)) {
            intersects = true;
            break;
        }
    }
    if (!intersects) {
        rects.push(rect);
    }
}
console.log('' + rects.length + ' rects');

// expand rects to hit neighbors
if (DO_EXPAND) {
for (let ii = 0; ii < rects.length; ii++) {
    let rect = rects[ii];
    let newVal = 0;
    
    let border = 100 - OUTER_PADDING;
    let hitBorder = Math.random() < HIT_SIDES_CHANCE;

    // expand to the right
    newVal = border;
    for (let other of rects) {
        if (rect === other) { continue; }
        if (rect.x1 <= other.x0 && rangeOverlaps(rect.y0, rect.y1, other.y0, other.y1)) {
            newVal = Math.min(newVal, other.x0);
        }
    }
    rect.x1 = Math.min(rect.x1, border);
    if (hitBorder || newVal !== border) { rect.x1 = newVal; }

    // expand to the left
    newVal = -border;
    for (let other of rects) {
        if (rect === other) { continue; }
        if (rect.x0 >= other.x1 && rangeOverlaps(rect.y0, rect.y1, other.y0, other.y1)) {
            newVal = Math.max(newVal, other.x1);
        }
    }
    rect.x0 = Math.max(rect.x0, -border);
    if (hitBorder || newVal !== -border) { rect.x0 = newVal; }

    // expand down
    newVal = border;
    for (let other of rects) {
        if (rect === other) { continue; }
        if (rect.y1 <= other.y0 && rangeOverlaps(rect.x0, rect.x1, other.x0, other.x1)) {
            newVal = Math.min(newVal, other.y0);
        }
    }
    rect.y1 = Math.min(rect.y1, border);
    if (hitBorder || newVal !== border) { rect.y1 = newVal; }

    
    // expand up
    newVal = -border;
    for (let other of rects) {
        if (rect === other) { continue; }
        if (rect.y0 >= other.y1 && rangeOverlaps(rect.x0, rect.x1, other.x0, other.x1)) {
            newVal = Math.max(newVal, other.y1);
        }
    }
    rect.y0 = Math.max(rect.y0, -border);
    if (hitBorder || newVal !== -border) { rect.y0 = newVal; }
}
}

for (let r of rects) {
    r.expand(-SHRINK);
}
rects = rects.filter(r => r.isValid());

// The walk function will be called until it returns false.
function walk(ii) {
    rects[ii].draw();
    return ii < rects.length-1;
}