#
# Chapter 16. Procedural Content
#
# terrain.py
#

import random
import math

INVROOT2 = 1.0 / math.sqrt(2.0)

class TerrainGenerator:
    def __init__(self, seed, size):
        self.seed = seed
        self.regionSize = size
        self.regionMask = self.regionSize - 1        
        self.rand = random.Random(self.seed)
        self.tiles = []
        
    def getHeightMasked(self, x, y):
        bx = x & self.regionMask
        by = y & self.regionMask
        return self.tiles[ self.regionSize * by + bx ]        

    def setHeight(self, x, y, value):
        self.tiles[ self.regionSize * y + x ] = value

    def displacePointSquare(self, x, y, step, displacement):
        averageHeight =(self.getHeightMasked(x-step,y-step) +
                        self.getHeightMasked(x+step,y-step) +
                        self.getHeightMasked(x-step,y+step) +
                        self.getHeightMasked(x+step,y+step)) * 0.25
        self.setHeight(x, y, averageHeight + displacement)

    def displacePointDiamond(self, x, y, step, displacement):
        averageHeight =(self.getHeightMasked(x,y-step) +
                        self.getHeightMasked(x+step,y) +
                        self.getHeightMasked(x-step,y) +
                        self.getHeightMasked(x,y+step)) * 0.25
        self.setHeight(x, y, averageHeight + displacement)
        
    def generateTerrain(self, minVal, maxVal):
        """Generate the terrain heightmap for this region.
        """
        self.tiles = [(minVal+maxVal)/2] * (self.regionSize * self.regionSize)
        fDisplacement = 100.0
        step = self.regionSize / 2

        while step > 0:
            # displacement on centers
            for y in xrange(step, self.regionSize, step * 2):
                for x in xrange(step, self.regionSize, step * 2):
                    displacement = self.rand.uniform(-fDisplacement, +fDisplacement)
                    self.displacePointSquare(x, y, step, displacement)
		
            # additional displacement on corners
            for y in xrange(0, self.regionSize, step * 2):
                for x in xrange(0, self.regionSize, step * 2):
                    displacement = self.rand.uniform(-fDisplacement, +fDisplacement)
                    self.displacePointSquare(x, y, step, displacement)                    

            fDisplacement = fDisplacement * INVROOT2

            # displacement on diamonds
            y = 0
            while y < self.regionSize:
                for x in xrange(step, self.regionSize, step * 2):
                    displacement = self.rand.uniform(-fDisplacement, +fDisplacement)
                    self.displacePointDiamond(x, y, step, displacement)                                        
                y = y + step
                                
                for x in xrange(0, self.regionSize, step * 2):
                    displacement = self.rand.uniform(-fDisplacement, +fDisplacement)
                    self.displacePointDiamond(x, y, step, displacement)                                                            
                y = y + step

            fDisplacement = fDisplacement * INVROOT2
            step = step >> 1

        # scale height values to specified min and max
        currentMin = min(self.tiles)
        currentMax = max(self.tiles)
        scale = (maxVal - minVal) / (currentMax - currentMin)
        offset = minVal - currentMin
        self.tiles = map(lambda t: int((t+offset)*scale), self.tiles)
        
    def __repr__(self):
        s = ""
        o = 0
        for y in range(0,self.regionSize):
            for x in range(0,self.regionSize):
                s += chr(self.tiles[o])
                o += 1
            s += "\n"
        return s

"""
t = TerrainGenerator(44,1024)
t.generateTerrain(65,90)
print t
"""
