Looking to generate some randoms grids of rectangles in a bigger rectangle.
This looks like a fairly easy problem, but it's not, seeking for advises here.
This is not a packing problem, since the inner rectangles can have any width and height.
But the amount of rectangles aren't always the same.
I have some results already with different kinds of loops, but none are really efficient.
For example, with 15 rectangles, a possible way to represent them could be:
O 10 50 60
+----------+---------------------------------------------+----------+-------+
| | | | |
| | | | |
| | | | |
5 +----------+---------------------------------------------+----------+-------+
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
15+----------+---------------------------------------------+----------+-------+
| | | | |
| | | | |
| | | | |
+----------+---------------------------------------------+----------+-------+
| | | | |
| | | | |
| | ↓ | | |
+----------+---------------------------------------------+----------+-------+
The coordinates would then be something like an array of [x,y] point of the top left corner (+) of each inner squares:
[[0,0],[10,0],[50,0],[60,0],[5,0],[5,10],[5,50], ...]
Or even better an array of [x,y,w,h] values (Top left x, top left y, width, height)
[[0,0,10,5],[10,0,40,5],[50,0,10,5],[60,0,10,5],[5,0,10,10],[5,10,40,10],[5,50,20,10], ...]
But the goal is to make a function that generate coordinates for any amount of inner squares:
For example, with 14 rectangles, a possible way to represent them could be:
+----------+----------------------------------+---------------------+-------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+----------+----------------------------------+----------+----------+-------+
| | | | |
| | | | |
+----------+--------+------------------------------------+----------+-------+
| | | | |
| | | | |
+-------------------+-----------+------------------------+----------+-------+
| | |
| | |
| | |
| | |
+-------------------------------+-----------------------------------+-------+
Related links:
Your problem has two aspects: You want to create a regular grid with n rectangles that may span several cells and you want to distribute the coordinates of the cell borders randomly.
So I propose the foolowing algorithm:
This algorithm uses two representations of the rectangles: First, it creates "cells", which have information about their row and column indices and spans. The actual output are rectangles with left, top, width and height information.
I'm not familar with PHP, so here is an implementaion in Javascript. I think you can see how it works:
function tile(big, n) {
// big: outer rectangle
// n: number of subrectangles to create
// determine number of rows and cols
let l = Math.sqrt(big.height * big.width / n);
let ncol = (big.width / l + 1) | 0;
let nrow = (big.height / l + 1) | 0;
let cells = [];
// create grid of m*m cells
for (let j = 0; j < nrow; j++) {
for (let i = 0; i < ncol; i++) {
cells.push(new Cell(i, j, 1, 1));
}
}
// conflate rectangles until target number is reached
while (cells.length > n) {
let k = (cells.length * Math.random()) | 0;
let c = cells[k];
if (c.col + c.colspan < ncol) {
let cc = cells[k + 1];
c.colspan += cc.colspan;
cells.splice(k + 1, 1);
}
}
// generate increasing lists of random numbers
let xx = [0];
let yy = [0];
for (let i = 0; i < ncol; i++) {
xx.push(xx[xx.length - 1] + 0.5 + Math.random());
}
for (let i = 0; i < nrow; i++) {
yy.push(yy[yy.length - 1] + 0.5 + Math.random());
}
// fit numbers to outer rectangle
for (let i = 0; i < ncol; i++) {
xx[i + 1] = (big.width * xx[i + 1] / xx[ncol]) | 0;
}
for (let i = 0; i < nrow; i++) {
yy[i + 1] = (big.height * yy[i + 1] / yy[nrow]) | 0;
}
// create actual rectangles
let res = [];
for (let cell of cells) {
let x = xx[cell.col];
let w = xx[cell.col + cell.colspan] - x;
let y = yy[cell.row];
let h = yy[cell.row + cell.rowspan] - y;
res.push(new Rect(x, y, w, h));
}
return res;
}
Notes:
x | 0
is a cheap way to turn floating-point numbers into integers. I've used it to snap the final coordinates to integer values, but you can also snap them to any grid size s
with s * ((x / s) | 0)
or s * intval(x / s)
in PHP.The code doesn't care much about aesthetics. It picks the cell sizes and the cells to conflate randomly, so that you might get cross joints, which don't look nice. You can influence the regularity of the result a bit, though:
Math.random()
returns a random number between 0 and 1. When creating the axes, I've added 0.5 to the result, so that you don't get very narrow cells. If you add less, the coordinates will be more irregular, if you add more, they will be more evenly distributed.You've mentioned good-looking properties in a comment. Perhaps it is easier to create a good-looking structure when you create the grid and placing the rectangles with constraints instead of first creating a grid and then remoing joints.