ID:983192
 
(See the best response by Zaoshi.)
Umm, a somewhat ambiguous question, bear with me.

I'm working on a framework for city-builders (similar in appearance to SimCity2000, Tropico, Populous). I'm not actually making a game, it's more just to learn some techniques. I'll probably release it as a library if I get anywhere with it.

Has anyone here ever worked on anything similar who could give any nifty tricks for generating landscapes?

At the moment I'm using a matrix of 'elevation values' to dictate the heights of the vertices where turfs corners meet. I'm essentially just placing circles of elevation=1 at random within a bounding box of that matrix. Which seems like a pretty brain-dead way of doing it. What better alternatives are there?

Then I'm transferring that matrix to turfs to render them in pseudo-3D using isometric sprites and pixel-offsets). Here's how it looks so far: http://filesmelt.com/dl/island6.png

Then there's the issue of mountains. I was thinking of placing a few random 'peaks' and then using a blur algorithm on my matrix to smooth it out into mountains and hills and stuff. I'd then pass back over and add a few random lumps and bumps. What alternatives are there? Drawing concentric circles adding 1 to the elevation value each time?

The grand plan is to have a semi-realistic weather system. Wind will change direction over time creating a vector-field over the map. Clouds will pass overhead, pick up moisture from the sea and release it according to the elevation of turfs below it. This would all influence fertility of farmland, soil-erosion, flooding etc.

So, lots of questions. :S
Any links to examples/other-games/guides that could help would be very much appreciated. Even if it's games that look similar that I could go to for inspiration.
Thanks for your time.

Here's what the bulk of the code looks like so far, it's yet to be optimised or anything.
MapGen Code:
var/datum/map_generator/mapgen = new /datum/map_generator()

/datum/map_generator
var/list/matrix
var/max_elevation = 10
var/water
var/water_max = 10
var/island_size = 30

var/min_radius = 2
var/max_radius = 5
var/min_edge
var/max_edge

proc/make_new_island(placement, seed, new_island_size, new_water_max)
set background = 1

//Using a pre-defined PRNG seed
if(seed) rand_seed(seed)

//Initializing variables:
//Island
if(new_island_size > 30)
island_size = round(new_island_size)
var/island_area = island_size*island_size //Number of elements in the matrix
//Water
if(new_water_max > 0.1 && new_water_max < 0.9)
water_max = round(new_water_max * island_area)
water = island_area //Island starts as a sea

//Initializing the island's Matrix
matrix = new /list(island_size,island_size)

min_edge = max_radius + 1
max_edge = island_size - min_edge

switch(placement)
if(1)
place_lumps_on_square()
else
place_lumps_randomly()

proc/place_lumps_randomly()

var/iter = 1
while(1)
if(iter++ > 999) break
if(water_max > water) break

var/x = rand(min_edge,max_edge)
var/y = rand(min_edge,max_edge)

var/radius = rand(min_radius,max_radius)

place_lump(x,y,radius)

proc/place_lumps_on_square()
var/iter = 1
while(1)
if(iter++ > 999) break
if(water_max > water) break

var/center = round(island_size/2)
var/ring_radius = round(center/2)

var/hits = 0
for(var/i=-ring_radius,i<=ring_radius,i++)
for(var/j=-ring_radius,j<=ring_radius,j++)
matrix[center+i][center+j] = 1

if(abs(i) == ring_radius && abs(j) == ring_radius)
if((hits++)%8 == 0)
place_lump(center+j,center+i,max_radius)

proc/place_lump(x,y,radius)
if(matrix[y][x]!=null) return

matrix[y][x] = 0

for(var/dx=1,dx<=radius,dx++)
for(var/dy=1,dy<=radius,dy++)
var/dist = cheap_pyth_hyp(dx, dy)
if(dist <= radius)
matrix[y+dy][x+dx] = 1
matrix[y-dy][x+dx] = 1
matrix[y+dy][x-dx] = 1
matrix[y-dy][x-dx] = 1

matrix[y+dx][x] = 1
matrix[y-dx][x] = 1
matrix[y][x+dx] = 1
matrix[y][x-dx] = 1

proc/commit_map()
elevationmatrix = matrix
matrix2map()

Matrix2Map
proc/matrix2map()
var/i_max = length(elevationmatrix)
var/j_max = length(elevationmatrix[1])

world.maxx = j_max - 1
world.maxy = i_max - 1
world.maxz = 1

var/list/icon_states = icon_states('wireframe2.dmi',1)

for(var/x=1,x<=world.maxx,x++)
for(var/y=1,y<=world.maxy,y++)
var/turf/T = locate(x,y,1)
if(!T) continue

T.gradient[1] = elevationmatrix[x][y+1]
T.gradient[2] = elevationmatrix[x+1][y+1]
T.gradient[3] = elevationmatrix[x+1][y]
T.gradient[4] = elevationmatrix[x][y]

var/lowest = 50
for(var/grad in T.gradient)
if(grad <= lowest)
if(grad == null)
grad = 0
lowest = grad

T.elevation = lowest

var/new_icon_state = ""
for(var/val in T.gradient)
if(val > T.elevation)
new_icon_state += "1"
else
new_icon_state += "0"

if(!(new_icon_state in icon_states))
world.log << "x=[x], y=[y]"
new_icon_state = "0000"

if(T.elevation == 0 && new_icon_state == "0000")
T.icon_state = "water"
else
T.icon_state = new_icon_state
T.pixel_y = 32*T.elevation
T.pixel_x = -T.pixel_y

Math:
//A very crude linear approximatiaon of pythagoras theorem.
/proc/cheap_pyth_hyp(dx, dy)
dx = abs(dx)
dy = abs(dy)
if(dx>=dy) return dx + (0.5*dy) //The longest side add half the shortest side approximates the hypotenuse
else return dy + (0.5*dx)

//we take EAST as theta = 0 and rotate anti-clockwise
/proc/polar2cartesian(r, theta)
return list(r * cos(theta), r * sin(theta))


//rotate a matrix counter-clockwise by rotation lots of 90degrees
/proc/rotate_matrix(list/matrix, rotation)
rotation %= 4
if(rotation < 0) rotation += 4 //ugh! BYOND! returning a value outside of the ring of positive integers.

var/i_max = length(matrix)
var/j_max = length(matrix[1])

switch(rotation)
if(1) //anticlockwise
. = new /list(j_max++,i_max++)
for(var/i=1,i<i_max,i++)
for(var/j=1,j<j_max,j++)
.[j_max-j][i] = matrix[i][j]
if(3) //clockwise
. = new /list(j_max++,i_max++)
for(var/i=1,i<i_max,i++)
for(var/j=1,j<j_max,j++)
.[j][i_max-i] = matrix[i][j]
if(2) //180 degrees
. = new /list(i_max++,j_max++)
for(var/i=1,i<i_max,i++)
for(var/j=1,j<j_max,j++)
.[i_max-i][j_max-j] = matrix[i][j]
else
. = new /list(i_max++,j_max++)
for(var/i=1,i<i_max,i++)
for(var/j=1,j<j_max,j++)
.[i][j] = matrix[i][j]


//Transpose a matrix
/proc/transpose_matrix(list/matrix)
var/i_max = matrix.len
var/j_max = length(matrix[1])

. = new /list(j_max++,i_max++)
for(var/i=1,i<i_max,i++)
for(var/j=1,j<j_max,j++)
.[j][i] = matrix[i][j]

Best response
Matrix is mathematical object. In your case it's simple array/list, not matrix. This will make less confusion if you rename it so something else, ex. heightArray.

Perlin Noise is quite a good algorithm to generate random terrain with random elevation, while keeping it smooth at the same time. Here's article and Python source: http://breinygames.blogspot.com/2012/06/ generating-terrain-using-perlin-noise.html
I don't really code so I'm more comfortable using the term matrix. For one BYOND's indexes start at 1 (not 0) and secondly, I will be recycling the various functions when I finally get to doing the the weather simulations, and I don't want to be feeding HeightArray into procs with matrix in their name and such.

Anyway, thanks a bunch. That looks ideal. I'll give it a read.
You could take a look at:
http://pcg.wikidot.com/pcg-algorithm:map-generation

It's a little sparse sometimes, but may give you some ideas that you can research further.
Oh, nice site, thanks.