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