I tried to make the node tree as readable as possible with frame-commentary:
It's nothing hard, really, spawn a grid of points, to spawn blocks. But to support blocks of varying (aligned with the grid) size, go through the points and for each check what kind of blocks can be spawned at that place - then pick randomly one of them. Once the checks are done, the chance for each block is the same, so because the small block always fits, it will appear more often (though big blocks still take more space, that's why the red color dominates). If you want to affect the chances, see here:
Geometry Nodes: How To Place Bias on Random Instance?
If you want to get statistically equal chance for each block type, either nerd-snipe Stef or just adjust values while previewing a big wall (you can actually disable instancing and just plug some attribute stat nodes to display the numbers of blocks), though wall dimensions will affect probability slightly, because wide blocks won't fit on the right border, and big blocks also won't fit on the top.
So you decide which block to spawn, not only save that information on currently evaluated point, but also "reserve" those neighboring points, which are now consumed by this block. For this purpose I reuse the type
attribute, giving it a -1
value. Checking which neighboring points have this attribute is of course part of figuring out which blocks can be spawned in a given place.
This process has to be done sequentially, because randomizing a block in one place will affect possibilities next to it. This means you need a repeat zone/simulation zone to do it, or a lot of chained custom groups in previous Blender (using switches and nested grouping you can reproduce a Repeat Zone for any realistic number of iterations even before Blender 4.0, but it's annoying to do) or without it you can get only an approximation of this technique, something similar to what I do here:
Is it possible to make a geometry node which can randomly place bricks in a defined pattern
Also while you can resign from modifying the state
forward, and just reading already modified points, which would produce a simpler logic here (check if 3 points below are taken by a big block, check if previous point is taken by a wide block), the complexity of that approach would grow much faster as more block types are added.
Consider how many nodes with 🟢 Geometry output are loops themselves. This means, that "Store Named Attribute" is a third-level loop:
for y in range(rows):
for x in range(columns):
for point in geometry.points:
if selectionField(point):
point['type'] = -1
This is the reason two indices are calculated separately, because I don't want to iterate over all points in this inner-most loop above, I just want to assign -1
to 0 (small block) / 1 (wide block) / 3 (big block) other points and the randomized block type to just the current point:
- Index node provides the
point.index
- loop flattened index is
y*columns + x
. Instead of it, I could have a single loop that iterates over vertices, but then I would need to calculate the x
and y
(to compare with columns
and rows
to check against the right/top boundary), using divmod.
Now I can compare the two indices to only select the currently evaluated xy
, or the next point + the above point + the next to above point.
Due to iterating from left to right, from bottom to top, Columns aren't stable for more than 1 row, because this changes the order of blocks just like resizing a notepad horizontally. However the only unstable aspect of modifying Rows is that it alternates the top big blocks fitting or not - so I added a switch to ignore the top check and only enable it at the last row:
(just now I see this gif doesn't showcase it well, but the first animation does)
You could achieve more stability while modifying Columns if you used position as the seed, but still the fact a big block [noun] can block [verb] a future block from spawning even though it's to the left of it, poses a challenge in stabilizing this axis… This can of course be true also for the vertical axis if you add more block types.
I was considering using raycasting / geometry proximity to test for room in the blocks, which would make it much easier to add more block types but would also be significantly slower (because realized geometry would have to added at least temporarily).