Planets

This will be mostly about terrestrial planets. Gas giants are also probably something to think about, but they're likely going to be much less interesting for the purposes of the game.

I'm going to start with the geographic qualities of planets. The overall description of the planet will be in terms of biomes, using something like Whittaker's biome model. I'll have to make adjustments to allow for more extreme, non-Earthlike biomes, but it will give us something to start with.

The biome of a point on a planet is a function of the temperature and the humidity. I'm going to add elevation as well. These three values are defined at each point on the planet. I'll generate them using poisson noise.

from noise import pnoise3
def posnoise(lat,lon,s=1,octaves=1):
    x = (1-cos(lat)*cos(lon))/2
    y = (1+sin(lat))/2
    z = (1+cos(lat)*sin(lon))/2
    return (pnoise3(x*s,y*s,z*s,base=0,octaves=octaves)+1)/2

Elevation and humidity are fairly straightforward. (I'm using hard-coded constants, but this will eventually be parameterized to produce different types of planets.)

def elevation(lat,lon):
    return (1+posnoise(lat,lon,octaves=20,s=5))/2

def humidity(lat,lon):
    return posnoise(lat,lon,s=3,octaves=2)

Temperature is, for these purposes, a weighted average of the noise and a function of the latitude. We also add a cooling effect for higher elevations. This is all motivated by making a neat looking picture, not necessarily because this is a good model of anything real.

def temp(lat,lon,ele,noisiness = .15):
    Tlat = 1-cos(lat)
    Tnoi = posnoise(lat,lon,s=8)
    T = 1 - Tlat*(1-noisiness) - Tnoi*noisiness

    cool_ele = .6
    if ele > cool_ele:
        T -= pow(1+ele-cool_ele,2)-1

    if T < 0: T = 0

    return T

Let's produce maps of these three values over the surface of a planet.

import numpy as np, Image

nsamples = (2048,1024)
lat = [pi*((float(x)/nsamples[1])-.5) for x in range(nsamples[1]+1)]
lon = [2*pi*((float(x)/nsamples[0])-.5) for x in range(nsamples[0]+1)]

def itersamples(lat,lon):
    for i,la in enumerate(lat):
        print i
        for o,lo in enumerate(lon):
            yield (i,o,la,lo)

E = np.zeros([len(lat), len(lon)])
for i,o,la,lo in itersamples(lat,lon):
    E[(i,o)] = elevation(la,lo)

E -= np.min(E)
E /= np.max(E)

Image.fromarray((E*255).astype('uint8')).save("elevation.png")

H = np.zeros([len(lat), len(lon)])
for i,o,la,lo in itersamples(lat,lon):
    H[(i,o)] = humidity(la,lo)

H -= np.min(H)
H /= np.max(H)

Image.fromarray((P*255).astype('uint8')).save("humidity.png")

T = np.zeros([len(lat), len(lon)])
for i,o,la,lo in itersamples(lat,lon):
    T[(i,o)] = temp(la,lo,E[(i,o)])

Image.fromarray((T*255).astype('uint8')).save("temp.png")

Fine.

Now to produce the biomes, which are (as you recall) a function of temperature and humidity.

def biome(t,h):
    if t < .1: return "POLAR"
    if t < .3: return "TUNDRA"
    if t < .5: return "BOREAL"

    if t < .7:
        if h < 1.0/3: return "C.DESERT"
        if h < 2.0/3: return "PRAIRIE"
        return "TEMP.DECIDUOUS"

    if h < .2: return "W.DESERT"
    if h < .4: return "GRASSLAND"
    if h < .6: return "SAVANNA"
    if h < .8: return "TROP.DECIDUOUS"
    return "RAINFOREST"

This is somewhat a simplification, and it's probably too simple in the long run, but it's a start. We also need some way of displaying them, so I'll just map these biome labels onto colors.

biome_colors = {
    "OCEAN": (0,0,128),
    "POLAR": (255,255,255),
    "TUNDRA": (215,215,255),
    "BOREAL": (196,196,255),
    "C.DESERT": (255, 127, 80),
    "PRAIRIE": (192, 192, 64),
    "TEMP.DECIDUOUS": (201, 160, 220),
    "W.DESERT": (215,196,0),
    "GRASSLAND": (128,128,96),
    "SAVANNA": (255, 215, 0),
    "TROP.DECIDUOUS": (128, 128, 0),
    "RAINFOREST": (0,196,0),
}

And map:

B = np.zeros([len(lat), len(lon), 3], 'uint8')
for i,o,la,lo in itersamples(lat,lon):
    b = biome(T[(i,o)], H[(i,o)])
    B[(i,o)] = biome_colors[b]

Image.fromarray(B).save("biome.png")

It would be good at this point to see the planet as a sphere. So I turn to POV-ray.

#include "functions.inc"
#include "shapes.inc"

camera { up y right x location <0,0,-3> look_at <0,0,0> }

light_source {
    <2, 0, -3>
    color rgb <1.0,1.0,1.0>
    parallel
    point_at <0,0,0>}

#declare p = function{pigment{image_map{png "elevation.png" map_type 1}}}

object{
    HF_Sphere(
        function{p(x,y,z).gray},
        0, 0, <512,256>, 0, "", <0,0,0>, 1, .15)
    texture{pigment{image_map{
                png "biome.png"
                map_type 1
                interpolate 2}}}
    rotate 360*clock*y
}

Run the povray command and use imagemagick to turn the frames into a GIF.

$ povray +H600 +W600 +KC +KFF36 planet.pov
$ convert planet*.png planet.gif

Neat. Let's try it with an ocean.

object{
    sphere{0,1.03}
    texture{
        pigment{rgb <.2,.2,.8>}
        finish{
            ambient 0.15
            diffuse 0.55
            brilliance 6.0 
            phong 0.8 
            phong_size 120 
            reflection 0.6 
        }   
    }   
    normal{
        bozo 1.75
        turbulence 0.9 
    }   
}

~adodge, 19 April 2016