Not logged inGosu Forums
Forum back to Help Search Register Login
Up Topic Gosu / Gosu Exchange / Isometric City Generator
- - By redcodefinal Date 2015-01-18 20:43

Hi guys,

New to the gosu forums, so I'm hoping this is the right place to post  this
I just got into gosu a week ago, and I've been working on porting an old piece of code called the Isometric City Generator

First some links:
ICGV2 - Written in C# XNA
ICGV3 - Also in XNA
isometric-city-generator-ruby - Current version

Some pictures of the old version can be found here
After I ported to Mono
Rotating a city

Current pictures (of the ruby version)
Using perlin gem
Using perlin_noise gem (Disgustingly slow although I liked the way it looked better)

I'm looking for a little advice on performance enhancement since I can barely use it right now to make anything bigger than a 40^3 render of the blocks but, I'll take any suggestions on anything, I'm really looking to improve anything.

So I'll start with talking about the end goal
My plan is to make a program that generates cities at an isometric perspective. My old versions would use a 3d array. I think this is gross. I wanted to make it dynamically generate it so I could do infinite size cities (or at least close to infinite). The ruby version is going to use perlin noise to generate building heights. Buildings will have features like windows, roofs, billboards, signs, doors. The ground will have plants, grass, roads. There should be special patches, like open stadiums, parks, airports. I'd love to add freeways. There will also be specially generated buildings, either by a single texture or building one out of smaller ones. If I can make that happen I'd really like to add people, cars, helocopters, all with pathing. All of these should be generated with perlin noise and not saved to memory (except for the objects like people, cars, copters, etc)

The Mono version was the most complete by far, adding things like rotation, special buildings like clock towers and radio towers, influence generation (making the city have a heart and suburbs).

Making buildings with a single texture where x or y (z is height in this case) was greater than one. I made a neat "city hall" sprite that I was going to plop down but, the drawing order would get messed up as a result. To see how I was drawing check IsometricFactory.cs, go to the DrawBlocks method @ line 466. This draw order issue was specifically frustrating. Now I could solve it by breaking up large buildings into single blocks and rendering them that way, but I figure there should be a better way.

Performance is a huge issue. In C# I was barely scraping by @ 30 FPS with a city size of 30*30*7. In ruby I'm barely getting 10 fps on a 20*20*20 block. I had an ok solution of not drawing blocks where there was a block obstructing it. Basically it would just check if a block was at (x+1, y+1, z+1). The hackish solution was to screenshot the window and start rendering that image instead. This sucks because you can't make a city bigger than the screen size, moving the camera will show an edge where the block fell off the screen. Of course this does boost FPS to 60. If possible I'd like to do all generation and drawing dynamically, so no arrays storing what block is where and what tile is what however, the more I use gosu the more I'm getting discouraged from the idea.

Here's my method_profiler report on isometric_factor.rb

I'd like to generate features, colors, roads, etc using perlin noise. My idea was to grab the noise at the current x, y, turn it into a string, then grab some digits after the floating point. Is there any issue with this? Is there a better way?
Roads would be the biggest issue I can see with the pure dynamic method, I'm not sure how to go about making them consistent. How would I figure out where a road should be placed? I want to make them more realistic (before I used straight lines and spaced them out a bit, I feel like there is a better way.

I would love to hear your thoughts on the project, if you have any questions I'd be happy to clarify my post.
Parent - - By jlnr (dev) Date 2015-01-18 21:35 Edited 2015-01-18 21:41
Hey! Definitely the right place to post this and ask questions.

Performance: Instead of storing things in an array, you can group "slices" of the city into a macro by using Window#record { lots_of_rendering_here }. This will return an image that captures all rendering code in the block into OpenGL vertex array for efficient reusing. (Efficient enough that you shouldn't have to worry about skipping obscured blocks.)
Of course, this is yet another way of saving things to memory - but Ruby just isn't efficient enough to do all the calculations at 60 FPS. And record is definitely cleaner than taking a screenshot of the window.

Z-order issues: Assuming that no block can contain two things at once, can't you just draw the whole box with the Z-position of the block that is the furthest away from the camera? Pretty hard (but fun) to reason about it without playing with the code :)
Parent - - By redcodefinal Date 2015-01-18 22:38 Edited 2015-01-18 23:11
I actually used the Window#record method in my commit before I posted this. However, I had no idea that it would record things off screen! Crazy nifty! Thanks for the tip on that. Question about that though. When I start the record the width and height I supply doesnt seem to matter at all, as long as it is > 0. Even weirder when I draw the image at 0,0 the top of the image is off the top of the screen, which doesn't really make any sense. I'm not to familiar with vertex buffers and the gosu rdocs say that Window#record returns something that is "not a true image". Is this position issue an expected behaviour? How do I figure out what the true coords of the image are?

I'll try to explain the issue better. If you look at the Draw method in IsometricFactory.cs @ line 466 here (turns out you can link directly to the line in github!), you can see I basically just use three for loops for x, y, z. However, once you add a building whose texture is one file where width > 1 || height > 1 it starts to overlap and be overlapped by draws before and after. Also I predict that using slices would screw this up if a large sized building was made across a slice.

EDIT: Realized you are a dev! Thanks for your work on gosu! I love it so far. :)
Parent - By jlnr (dev) Date 2015-01-18 23:10

> When I start the record the width and height I supply doesnt seem to matter at all, as long as it is > 0.

It matters if you want to query image.width or image.height, and when you rotate the image around its centre or use Image#draw_quad. If you don't plan on doing either of these things, you can just pass 1, 1.

> Even weirder when I draw the image at 0,0 the top of the image is off the top of the screen, which doesn't really make any sense.

I agree that it's not intuitive, but there is no elegant 'fix', and it might actually have creative uses. :)

EDIT: It's a pleasure, thanks for playing with Gosu!
Parent - - By Spooner Date 2015-01-20 19:16
How about trying the simplex noise generator (regular Perlin noise sucks) with my 100% C gem: (you say you've tried "perlin" and "perlin_noise" gems and found the latter slow, but "prettier"). Should be fast...

Alternatively, use a noise shader and read the pixels to get the heights (this can be done with Ashton and would be even faster, but a little more fiddly).
Parent - - By redcodefinal Date 2015-01-21 04:56
For 'perlin_noise', the issue is that it does not generate the random values on the fly, rather it stores 256 random values and interpolates between them. Here are my results of a simple benchmark running [x, y] on perlin's Perlin::Generator#[] and perlin_noise's Perlin::Noise#[] (gist). perlin's Perlin::Generator#[] was 266 times more efficient. As it turns out, I was using yours the whole time (your gem is already on! Thanks for writing such a great implementation! I was wondering how I could go about making more hills, especially in the emptier parts of the map. Would get_perlin_noise(x,y).abs do the trick? It seems to but maybe there is something else I can do.
Parent - - By Spooner Date 2015-01-21 12:35
Using abs isn't a great plan, since it will mean a steep angular change when the sign changes. Better to add 1 and divide by 2 so you get a range of 0..1 rather than -1..1. I'd recommend using some harmonics & multiple generators at once, adding the result, in order to get something that looks less like noise and more like terrain.
Parent - - By redcodefinal Date 2015-01-21 16:20
Sounds good! I'm interested in learning more about harmonics and multiple generators. Do you have any resources to point me in the right direction?
Parent - By Spooner Date 2015-01-21 16:22
Nope, sorry, I just played with the numbers until I got something nice ;) You could try some googling and hope...

Best I can do is to suggest you  look at Ashton, which uses glsl shaders to make more interesting patterns with multiple generators. Obviously not directly useful to you.
Parent - By shawn42 Date 2015-01-23 15:56
Nice progress!

I had a thought for roads: what if you randomly generate attractive points using noise as a height map.  The more attractive a place, the more traffic needs to get to it. (generate more roads from this point) and have your road generator focus on connecting the attractive points. This would mimic how roads / highways get built in real life. Just my $0.02.

Good luck.
Up Topic Gosu / Gosu Exchange / Isometric City Generator

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill