Creating procedural tools for city environment design workflow - experiments

Hello,

Recently I’ve been learning geometry nodes with the goal to create some tools for myself that would aid me in my work (environment design and concept art mostly). So I decided to put into practice what I learned so far and share the journey in hopes of learning something new and maybe helping other people by sharing the process with some insights that I have. So, I will describe the main idea and plan below.

Here are my experiments with the city generator built from scratch in geometry nodes.

Main goals and modules:

  1. Create a general proof of concept system:
    a) Create main layout from the provided base plane mesh:
    • edges are main roads (created by insetting the initial mesh faces by a specified offset)
    • set-up further divides the inset faces into smaller city blocks until they reach a specific size and
      parcel to building floor area ratio (I can specify those parametricly). I chose the repeat and for
      each element zone for this, it is not the fastest but allowed me to do the division recursively.
      Maybe there is a simpler and better way of doing it, but I couldn’t come up with anything other.
    • the new city blocks are inset so between them are created new, narrower roads, which adds a
      nice real world hierarchy to the road system I think
    • label the output city blocks (mesh islands) and put them into categories (medium, small, non
      built) - the medium blocks will have buildings on their borders and in interiors, small only on the
      borders. Non built - are edge cases that have weird shape or are too small and would lead to
      weird behavior later on.

b) Basic buildings outlines creation:
- Convert the created city blocks into curves, offset their start and end points so that they are not
on the corners of the curves
- Subdivide those curves into random length that is a multiplication of the module width (for
example 3m). It will allow me to later on extrude the base of the building to create proxy mesh
for instancing designed and modeled in 3d kitbash facade elements.
- For each curve create building ID for later - Duplicate and offset the curves by the specified
building depth distance with some random variance, label them respectively front and back by
adding named attributes (booleans)
- Create and set position of side curves - add labels

In this way I can have pretty well defined all sides of the buildings that will later on help me instancing different elements on them depending on their orientation. Easier and more reliable than using normal comparison in my opinion.

c) Inside buildings:
- by subdividing parcels labeled as “medium” I can later on use basic shortest edge path set up
to create selections for the interior buildings
- I mark those edges for later with a named attribute

d) Create proxy meshes with the random floor number per building and a procedural rooftop:
pretty straight forward I think, but again I used a repeat zones and for each element zones for the
extrusion. The problem is - now they are nested withing groups within groups and that is pretty
heavy and slows down the whole set up. Maybe there is a faster and less computation intensive
way of doing it, but for the life of me I couldn’t come up with anything that would work on per
building scenario. It was important, because in this way I can also create procedural rooftops for
each building and place it at a proper height. Also I can specify different heights for different
buildings (for example buildings on the main wide roads can be bigger, while the rest can have
different height etc.) and add ID for textures or kitbash models to instance on faces of proxy mesh.

So this is the current stage of the whole project. Next I think I will try and work on optimizing it and test in edge case scenarios. Then divide it into a couple of smaller geometry nodes set ups responsible for each stage in more detail. In that way I possibly will have more creative control over each city block and buildings in it. After all I do not want the tool to dictate my designs (I hope haha).

10 Likes

An update on the interior courtyards buildings.

So the problem that I faced is how to replicate this type of urban composition (Paris, haussmanian architecture):


While the divisions by the roads so far are kind of similar in my node set up, the interior divisions of courtyards and interior buildings were hard to do.

I made a couple of attempts:

  1. Using grids to divide the interior spaces into curves and use those to create building proxy meshes.

The division set up:

  1. Using a grid with random delete edges set up, the left edges are converted into curves for creating building proxies, a rather quick and decent result. More organic than just a grid:

  1. Similar to the previous one but with the shortest path selection to chose edges:

4.Last one, most complicated, incorporates a modified matrix set up, that I’ve learned from a tutorial BCITY by Mike Hodgetts, to create irregular grid that later on is used as a cutter in boolean operation with the base mesh to create a more organic and closed interior courtyards composition - more similar to what I had in the ref:

The last one combined with some random delete seems to create rather good results. Probably more tests are required to nail that look. Or maybe I will need to rethink the whole set up altogether. If anyone has any suggestions or ideas please feel free to share.

Thanks for reading.

7 Likes

I didn’t read all the details, but that looks pretty good so far ! I’ll keep an eye on the progress !

1 Like

Thank you! Yeah, it’s a wall of text hahaha. I’m new to the forum so I don’t know what format of presentation is best.

As for the project - I’ll try and update the topic as I work on the geonodes setup. I consider it a very early wip and more of a proof of concept thing at this stage so probably a lot will change as I experiment and learn, so all the feedback is welcomed :smile:

1 Like

I think walls of text are ok, forums are good for that ! and I tend to do that as well, it’s just that I wasn’t in the right mindset to really read carefully and thoroughly, especially when it comes to tech stuff I need a mind completely available hahaha !

Anyway, I subscribed to the thread so I’ll follow your progress and see if I have stuff to add. I’ve looked into similar stuff even if I didn’t tried city blocks yet :slight_smile: I live in paris also so it’s two reasons to look at that project :smiley:

Good luck with it and see you around !

Hey, thanks for the reply. Yeah I totally understand, I’m similar I need to have enough energy to read through technical stuff.

I’ve seen a couple of your projects and I must say that you are a magician. I remember reading through those topics with buildings and railways generators. Super interesting stuff for such a newbie like me. Thanks for sharing!

1 Like

Thanks a bunch ! At some point , studying hard and diligently pays off ! You’ll definitely get there if you keep practicing ! It’s great that you share stuff as well so we can inspire each others !

1 Like

Update.

So today I was working mostly on some minor stuff like very basic street markings and adding some guardrails to more prone to odd behavior parts of the building meshes generator.

I think that my initial idea of ground plane division into main and secondary streets and city parcels between them might have been not so optimal. I spent a day trying to create axis for roads and fix those curves directions or find ways of trimming them proportionaly from both sides. Another thing was breaking those curves at the intersections and crossroads.

The secondary streets are just main city block outlines copied and offset by the half of the street width and then trimmed at ends to create spaces for intersections and later on points for crosswalks. Since the curves were having random directions and random tangents I was initially trying to create a clever way of reorganizing them (making point index 0 near the main road, then the end of the curve the remaining index) but I realized that it was not needed and simple merge and split edges with trimming by the same amount from both ends does the job well.

The main roads were tricky tho. I used an “inset” geo node setup for initial offseting of the mesh faces and then subdivide those new faces into city parcels further.

It meant that while I had the initial big roads axis readily available I did not have them separated into segments that correspond with the secondary roads ends and intersections. I had to figure this out and use some geometry proximity with converting between mesh and curves with resampling and later on merging to get points at intersections of the roads. Then I used proximity to those points to delete parts of the road markings (that were made by turning curves into meshes)

Next was a gate logic to filter out the odd or problematic shapes or the interior courtyards parcels used for interior buildings.

It’s mostly filtering out faces with sharp angles, too small surface area for building plans or that fit characteristics of triangles or problematic simple geometry shapes. I made a utility helper node group for that:


A mini spaghetti monster.

So thats that for now. Thanks for reading! If you have any suggestions or if anything is presented in a way that is difficult to understand please let me know.

Update. THE OFFGRID SIDE QUEST (lol).

This one took a long while. Mainly due to me taking a detour and doing a big side quest. I wasn’t happy with how currently I was generating the interior spaces/buildings. I was mainly reusing the same logic and formula I use for the main city block division, which gives results like this:

Main city blocks:

After another division for internal buildings:

You can clearly see that it is only slightly irregular and in only X axis. So I started searching for other more irregular but not chaotic rectangular grids. Somewhere on the forum someone mentioned an Offgrid algorithm made by Chris Cox. With some help and trials and errors I managed to convert it to python and use it in Blender to generate random rectangular irregular grids:


I even went as far as to ask AI model to write parts of the code (I know, a filthy cheater, I admit) to turn it into something like a small add-on for Blender. It was useful to test the idea if this type of grid will work for my needs and yield better results.

In city I used this mesh as a boolean/cutter on the city blocks, and the result was as follows:

And with buildings:

I actually kind of liked the result. It still needs tweaking and finding good balance and proportion but it was more interesting looking. The problem was - it wasn’t procedural.

So I tried to recreate the Offgrid algorithm, or at least mimic it, by using geo nodes. Oh. My. God. It took so much time, but I think I learned a thing or two.

While I got the general concept right, and the figured out the underlying mechanisms and how to translate that to geometry nodes - I had really a hard time figuring out the proper data transfer, because I quickly realized that it needs a secondary or unrelated to the blender geometry indexing and even geometry workflow for it to work.

So the core idea is:
The grid’s vertices have neighboring faces which contain control points providing X or Y coordinate for the resulting generated rectangles. Because the underlying grid can be also seen as a checkerboard it is rather easy to determine that each rectangle has two faces governing X coordinates and two Y coordinates for its points. From that it is rather straight forward conceptually to see that we have everything to actually create those rectangles… but to actually transfer and store actual data by not relying on finicky “sample nearest” or “geometry proximity” was really hard for me. So, since I already have sinned and used the help of AI model for python coding, I actually tried it for helping me figure out how geometry nodes actually handle data and how can I transfer it in a more math based way. Long story short, AI sucked at providing a proper understandable solution, but as a helper on a more technical or math based problems it actually allowed me to recreate my logic in Blender as a procedural geometry node set up.

It is a work in progress, but it seems to be working fine for my needs. So I tested it inside the city gen, to see if it will yield more interesting results per city block.


As you can see, if used per city block as a proceduraly generated offgrid style mesh it produces destroyed geometry after inseting or offseting for actual interior buildings floor plans. Well… it failed successfully… Now I have to find other solution. But at least I have an interesting pattern generator that is procedural and geo nodes based. Maybe later I will tweak it to use noise texture as an input for the rectangles offset?

Anyway… I’m tempted to see if it is possible to create in geo nodes Stalbergs irregular grid used in Townscraper haha.

Any suggestions and help are welcomed! Thank you for reading this stream of consciousness.

1 Like

IRREGULAR RECTANGULAR GRID IN GN - inspired by Offgrid

Disclaimer - some parts were figured out with the use of LLM AI model (mainly the repositioning and scaling of final rectangles and data transfer/conversion).

The Blend file with the geo nodes setup is at the end of the post.

I thought I will share the gn setup, with proper explanation, that attempts to adapt the logic of Chris Cox’s Offgrid script into blender using geometry nodes. It’s pretty much a procedural rectangular irregular grid generator. It took me quite a lot of work and research to get it working and I learned a lot during the process. I think that it might be helpful to some more advanced GN wizards out there.

1. The basic logic:


The whole idea is to see the algorithm conceptually as two grids - one being the dimensions and boundary lattice (lets call it BASE GRID, or COORDINATE GRID) and the other being the resulting irregular rectangular grid.
The base grid has an alternating pattern on its faces - which can be visualized as checkerboard. The black squares are responsible for governing the X values of the vertices of the final rectangles and the white ones are responsible for the Y values of the vertices of the final rectangles.
I imagine those as a rulers in a drafting table. To understand why this results in a non overlapping final pattern that is still irregular it is good to imagine it as:


As you can see, each vertex has 4 neighboring faces, and every next vertex shares two of those faces with its neighbor vertices. That’s why the resulting rectangles do not overlap. They share boundaries.
In addition you can think of it visually like blue lines (responsible for X coordinates) being left-right boundaries and red lines being top-bottom boundaries for each rectangle that is being placed on a given vertex:

So as you can see each resulting rectangle has one of it’s corners always within the boundaries of only one square of the base grid. And given the checkerboard nature of the base grid we can now easily see which face is responsible for each coordinate of each of the resulting rectangles vertex.
If square
I = Top Left (TL)
II - Top Right (TR)
III - Bottom Left (BL)
IV - Bottom Right (BR)

Then

Ax = (TL value) and Ay = TR value, Bx = BR value, By = TR value. And so on and so forth.

The trick was to figure out a how to convert the Blender index system into a more controlled coordinates system and use it to get both vertex and faces IDs and coordinates.

2. INITIAL SET UP - the custom coordinate system:


It is a pretty common practice from what I’ve learned to do this like that. A note on the modulo and division values - I am giving here each vertex it’s own coordinate and in a grid the Vertex number = nr of Columns + 1 (same for vertices in the other direction = rows +1).

This coordinate system and the relation between vertex x,y and neighboring faces x,y was the most tricky thing to figure out for me:


TL = OG_X - 1, OG_Y - 1
TR = OG_X, OG_Y - 1
BL = OG_X - 1, OG_Y
BR = OG_X, OG_Y

And then:

Face index = Y*Grid Width + X

To illustrate:

Another way of thinking about it is that each faces that is a neighbor of given vertex has the same (X, Y) as this face top right corner vertex:

Those relationships took me a long time to figure out, but I hope it is at least somewhat understandable the way I explain it.

3.SETTING UP THE CHECKERBOARD:
That was pretty simple, it’s just adding a boolean value to each VERTEX in an alternating manner. The important note here is that those are going to be sampled per vertex ID derived from an earlier step, so the formula goes:

4. CREATING THE U/V values (the X, Y offset for the final rectangles corner vertices) AND SAMPLING THEM PER FACE ID:

The random U, V jitter from group 5:

The resulting Control_U (basically a X offset within base grid face boundries) and Control_V (the Y offset) are then being sampled per each neighboring face. Inside groups 6:

In group 7. I simply check which value to use where based on the checkerboard pattern.

5.THE TEMPORARY BOUNDARIES OF RECTANGLES:


Because I operate on the custom X,Y coordinate system and not on the Blenders physical coordinates of the vertices of the grid - the results that I get here are still not representing the final vertex position in the Blender geometry space. They are values governing offset per base grid vertex. What does that mean?

With the jitter U (so along X axis) set to 1 I get:

6.CLAMPING THE VALUES OUTSIDE OF THE BASE GRID.

This step is basically clamping any values generated in previous steps that go outside the base grid. For example vertices that are on the border of the base grid can have negative values:

and that would result in the rectangles to be moved along the X or Y axis (above X) and create the jagged or irregular boundaries in new mesh:

It is a little bit dirty trick as you can see, but it works, and it is the only solution that I found to this issue. The issue of rectangles being offset in regards to the base grid in either X or Y axis is the result of me using random value in generating U and V values. And trying to stick to the Offgrid principle: That each base grid square contains only one unique corner of each resulting rectangles.

Well, it just works.

7. GETTING FINAL POSITIONS FOR RECTANGLES AND THEIR DIMENSIONS (SCALE).


At the frame 14. I shift the custom coordinate system that I was using (going from 0… to nr Columns) to match the Blender physical one of the grid (for a grid placed at the physical world origin 0,0 the left most columns will have negative coordinates, similar for bottom most rows).

8. INSTANCING PLANES ON MOVED BASE GRID POINTS AND SCALING THEM.


I use calculated in previous step position vector to move base grid points to offset positions based on U and V values and then instance on them new rectangles. Lastly, I use the calculated physical dimension vector to scale the rectangles to proper sizes.

The end.

The blend file - saved in blender 4.5 lts. Works for me in 5.2lts.

GN_Offgrid_Blender4_5lts.blend (175.6 KB)

UPDATE - Roads, different pavements and random city parcel selection.

So I managed to fix one of the issues with the procedural pavements - I wanted the pavements to be wider if they are going on the sides of the main roads.

It is a bit hacky and often fail but for now that is the best solution I found that makes the pavements scale in width only when close to the main roads. Other approach, like with the geometry proximity and curve scale by a normal vector were very inconsistent:

Then there are some tests with how the set up works so far:

Other than that, I’m thinking on redesigning the set up to be more modular and the data flow to be more simple. Maybe even separate it into 3 different modules:

  1. Base city block curves
  2. Streets and pavements
  3. Buildings:
    3a. Separated into base floor module generation with maybe group ID.
    3b. Proxy meshes with rooftops and individual ID per building mesh
    3c. Final building generated - instance modules on proxy mesh based on both group ID and individual ID.

Maybe even the building module could be done from a curve drawn by the user? I’ve seen cool clip from the Assassins Creed Unity development showing how artist was just drawing a simple curve along the road and then the in house designed tool was building procedural buildings along it, then allowing the user to pick rooftops and height per building. It gives way more artistic freedom in my opinion and is pretty cool.

2 Likes

DIFFERENT APPROACH TO ROAD AND CITY PARCEL GENERATION

A little bit of a side quest, but I wanted to test if there are other viable options for creating procedurally the base street grid.

Here I use distribute points on faces to instance some curve lines that I later on deform procedurally and turn into volumes.


Then I go with mesh boolean with a simple plane to get roads:

Later I use this to create a cutter for the initial base geometry to get city blocks and roads. Whats nice with this approach is that roads are truly random and I get for free the main axis of each road as a curve. My previous set up only gave me this for the big roads. So the problem here with intersections and street markings are very easy to solve:

Sadly, this approach to street layout and city blocks generation has it’s own drawbacks - the city blocks are very irregular at edges:

And even after turning them into curves and cleaning and resampling them they still are rather problematic to offset evenly. Less reliable than my initial set up. Well, I learned something.

SIDE QUEST - TESTING GROWTH ALGORITHM TECHNIQUE FOR STREET GRID GENERATION

I found out about L Systems and growth procedural generation recently during my search for new ideas on how to generate a more believable and realistic city street grid. The approach here is from a breakdown/tutorial video on yt done by this person:

The space colonization algorithm in blender geometry nodes, for organic branching structures: part 1

I tweaked it a little to use repeat zone instead of simulation zone, and added some other small adjustments. Then I used geometry proximity and two spheres as a visual selectors for select shortest path workflow and store named attribute on them. Later on I used it to create two sets of road meshes with different widths. Then I used it to cut in the initial plane some new faces.

The workflow while provides a more realistic looking roads and parcels in urban city scale, is also very prone to crashing blender. Also - as a bonus - is very slow and it is difficult to further parcel the remaining faces into smaller city blocks.

My previous, original approach uses an input plane for initial subdivision. Each face is split either into 4 (cross pattern) or in two (either vertically or horizontally based on it’s X,Y dimensions). Then it is flagged based on its area size and compared to the future buildings floor to plot ratio. Then the process repeats until no big city blocks remain:

And the whole thing with proxy buildings (lots of tweaks and fixes since last iteration):

If anyone has an idea or suggestions how to create a more organic looking city block and roads layout please help me haha (no srsly, please help ;_; )

1 Like