ActiveState Code

Recipe 576929: Planet Terrain Heightmap Generator


The process is simply: 1. Take a plane 2. Cut out a shape 3. Make it a little taller 4. Repeat

  • Similar to the spherical landscape algorithm by Hugo Elias.
  • I found a combination of Ovals and Triangles to produce the best results.
Python
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Image, ImageDraw, ImageChops, ImageOps, ImageFilter
import pygame.display, pygame.image
import random
import sys


pygame.init() #Need this to render the output
mapSize = 600 #in pixels
maxSize = 200 #maximum number of pizels for elipses, Smaller makes more "Continents"
minSize = 100 #minimum number of pizels for elipses, Larger make fewer "Islands"

randType =  random.uniform #change to alter variability
roughness = 1 #High numbers make a world faster, with more "ridges", but also makes things less "smooth"

def normalize(image):
    picture = ImageOps.equalize(image.filter(ImageFilter.BLUR))
    return ImageChops.multiply(picture, picture)

def finish(image): #called when window is closed, or iterations stop
  picture = normalize(image)
  picture.save('heightmap.bmp')
  pygame.quit()
  sys.exit();

def makeTriangle(orig):
  action = random.choice([ImageChops.add,ImageChops.difference])
  xrand = lambda : int(randType(0, mapSize*3))
  yrand = lambda : int(randType(0-mapSize, mapSize*2))
  s1x = xrand()
  s1y = yrand()
  s2x = xrand()
  s2y = yrand()
  s3x = xrand()
  s3y = yrand()
  height = int(randType(1,roughness*2))
  img = Image.new('L', (mapSize*2,mapSize))
  draw = ImageDraw.Draw(img)
  draw.polygon([s1x,s1y,s2x,s2y,s3x,s3y], fill=height)
  del draw
  if s1x > mapSize*2 or s2x > mapSize*2 or s3x > mapSize*2: #if it crosses our border, we needto wrap
    orig = action(orig, img)
    r1x = s1x - mapSize*2
    r2x = s2x - mapSize*2
    r3x = s3x - mapSize*2
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.polygon([r1x,s1y,r2x,s2y,r3x,s3y], fill=height)
    del draw
  return action(orig, img)

def makeOval(orig):
  action = random.choice([ImageChops.add,ImageChops.difference])
  s1x = int(randType(0, mapSize*2))
  s1y = int(randType(0-mapSize, mapSize))
  s2x = int(randType(s1x+minSize, s1x+maxSize))
  s2y = int(randType(s1y+minSize, s1y+maxSize*2))
  height = int(randType(1,roughness))
  if s2x > mapSize*2: #if it crosses our border, we needto wrap
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.pieslice([s1x,s1y,mapSize*4,s2y], 90, 270, fill=height)
    del draw
    s2x=s2x-mapSize*2
    orig = action(orig, img)
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.pieslice([0-s2x,s1y,s2x,s2y], 270, 90, fill=height)
    del draw
  else:
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.ellipse([s1x,s1y,s2x,s2y], fill=height)
    del draw
  return action(orig, img)


#initialize blank image
image = Image.new('L', (mapSize*2,mapSize))
draw = ImageDraw.Draw(image)
draw.rectangle([0-mapSize,0,mapSize*4,mapSize], fill=128)
del draw

for i in range(10000):
  image = makeTriangle(image)
  image = makeOval(image)
  if i%50 == 1: #conver PIL to pygame display object every 50 iterations
    picture = normalize(image)
    picture = ImageOps.colorize(picture, 'blue', 'green')
    picture = pygame.image.fromstring(picture.tostring(), picture.size, picture.mode)
    pygame.display.set_mode(picture.get_size())
    main_surface = pygame.display.get_surface()
    main_surface.blit(picture, (0, 0))
    pygame.display.update()
  for event in pygame.event.get(): #When people close the window
    if event.type == pygame.QUIT:    
      finish(image)


finish(image)

Discussion

This algorithm can be used with a voxel or 3d modeling application to generate random terrains. Methodological Problems: 1. Mountains don't happen vertically enough 2. No canyons 3. etc...

Implementation Issues: I can't seem to get pygame to stop flickering. Ideally I'd like to update it more frequently, but it flickers even as is.

Sign in to comment