Welcome, guest | Sign In | My Account | Store | Cart

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, 117 lines
  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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Image, ImageDraw, ImageChops, ImageOps, ImageFilter
import pygame.display, pygame.image
import random
import sys
from math import ceil

percentWater = .70
mapSize = 600 #in pixels
maxSize = 1.40 # 1 to 5 How big should the slices be cut, smaller slices create more islands, larger slices create less
shape = 1.40 # 1 mean roundish continents .5 is twice as tall as it is wide, 2.0 is twice as wide as tall
driftRate = .70 # As the world ages how much slower does it drift.  1 creates a more textured world but takes longer
roughness = 1 #High numbers make a world faster, with more "ridges", but also makes things less "smooth"
filename = 'heightmap.bmp'

xrand = lambda ms = mapSize*3: int(randType(0, ms))
yrand = lambda ms = mapSize*2: int(randType(0-(ms/2), ms))
randType =  random.uniform #change to alter variability



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

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

def drawPieSlices(oval, orig, action):
    fl = action[1]
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.pieslice([oval[0],oval[1],mapSize*4,oval[3]], 90, 270, fill=fl)
    del draw
    orig = action[0](orig, img)
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.pieslice([0-oval[0],oval[1],oval[2]-mapSize*2,oval[3]], 270, 90, fill=fl)
    del draw
    return action[0](orig, img)

def drawOval(oval, orig, action):
    img = Image.new('L', (mapSize*2,mapSize))
    draw = ImageDraw.Draw(img)
    draw.ellipse(oval, fill=action[1])
    del draw
    return action[0](orig, img)

def cutOval(orig, smallness=1):
    smallness = smallness ** driftRate
    landAction = lambda: (
        ImageChops.add, 
        ceil(randType(1,roughness*smallness*(percentWater)))
        )
    seaAction = lambda: (
        ImageChops.subtract, 
        ceil(randType(1,roughness*smallness*(1.0-percentWater)))
        )
    action = seaAction() if random.random() < percentWater else landAction()
    oval = [xrand(mapSize*2),yrand(mapSize),1,1] #x,y,x,y
    oval[2] = int(oval[0]+(mapSize*maxSize*shape)*smallness)
    oval[3] = int(oval[1]+(mapSize*maxSize)*smallness)
    if oval[2] > mapSize*2: #if x values cross our border, we needto wrap
        ret = drawPieSlices(oval, orig, action)
    else:
        ret = drawOval(oval, orig, action)
    return ret

imageToPygame = lambda i: pygame.image.fromstring(i.tostring(), i.size, i.mode)

def highestPointOnSphere(sphere):
    extremes = sphere.getextrema()
    return extremes[0]/255.0 if percentWater > .5 else 1-(extremes[1]/255.0)

def createSphere():
    pygame.init() #Need this to render the output
    sphere = Image.new('L', (mapSize*2,mapSize))
    img = ImageDraw.Draw(sphere)
    baseline = (256*(1.0-(percentWater)))
    img.rectangle([0-mapSize,0,mapSize*4,mapSize], fill=baseline)
    del img
    return sphere

def sphereToPicture(sphere):
    picture = normalize(sphere)
    picture = ImageOps.colorize(picture, (10,0,100), (0,256,0))
    picture = imageToPygame(picture)
    return picture

def generatePlanet(sphere, displayInterval = 50):
    extrema = highestPointOnSphere(sphere)
    i = 0
    picture = sphereToPicture(sphere)
    pygame.display.set_mode(picture.get_size())
    main_surface = pygame.display.get_surface()
    del picture
    while extrema > driftRate/(roughness*10*maxSize):
        sphere = cutOval(sphere, extrema)
        i = i+1
        if displayInterval > 0 and i%displayInterval == 0:
            picture = sphereToPicture(sphere)
            main_surface.blit(picture, (0, 0))
            pygame.display.update()
            del picture

        for event in pygame.event.get(): #When people close the window
            if event.type == pygame.QUIT:    
                return image
        extrema = highestPointOnSphere(sphere)
    return sphere
if __name__ == '__main__':
    finish(generatePlanet(createSphere(), 50))

Edit: Recently improved the readability, efficiency, and effectivness of this algorithm. Try adjusting the variables at the top.