Perlin Noise Class
Monkey Forums/Monkey Code/Perlin Noise Class| 
 | ||
| basic perlin class 
'==========================================
'           CLASS                          
'   BASIC PERLIN CLASS WITHOUT BITMAP
'==========================================
Class TPerlinNoiseBasic
	Field sizeX = 640
	Field sizeY = 480
	'first random noise table	
	Field noiseArr:Float[]
	'output table
	Field terrainArr:Float[]
	
	'frequency, the lower the larger terrains of same level	
	Field frequency:Float = 1.0
	'starting amplitude
	Field amplitude:Float = 1.0
	'change of amplitude of next octave
	Field persistance:Float = 0.6
	'number of octaves
	Field octaves = 8
	
	'=========================================
	'	just make some data, using fake 2D arrays
	'===========================================
	Method New(_x, _y)
		
		Self.sizeX = _x
		Self.sizeY = _y	
		Self.noiseArr = new float[_x * _y]
		Self.terrainArr = new float[_x * _y]
	
	End
	
	'==========================================
	'    to init perlin noise parameters
	'==========================================
	Method ChangeParams(fre:Float, amp:Float, pers:Float, oct)
	
		frequency = fre
		amplitude = amp
		persistance = pers
		octaves = oct
	
	End 
	
	'==========================================
	'       single field noise generation
	'=========================================
	Method GetRandomNoise:Float(x:Float, y:Float)
		
		Local fre:Float = frequency
		Local amp:Float = amplitude
		Local finalValue:Float = 0.0
		
		For Local i = 0 To octaves
			finalValue = finalValue + LinearFilterNoise(x * fre, y * fre) * amp
			fre = fre * 2.0
			amp = amp * persistance
		Next
		
		If(finalValue < - 1.0) finalValue = -1.0
		If(finalValue > 1.0) finalValue = 1.0
		
		finalValue = finalValue * 0.5 + 0.5
		
		Return finalValue
	
	End 
	
	'==========================================
	'      create output terrain array
	'==========================================
	Method MakeTerrainMap()
		
		For Local x = 0 To sizeX - 1
			For Local y = 0 To sizeY - 1
				terrainArr[x + y * sizeX] = GetRandomNoise(x, y)
			Next
		Next
	
	End 
	'============================================
	'    perlin noise with linear interpolation
	'===========================================
	Method LinearFilterNoise:Float(x:Float, y:Float)
		
		Local fractionX:Float = x - Int(x)
		Local fractionY:Float = y - Int(y)
		
		Local x1 = (Int(x) + sizeX) Mod sizeX
		Local y1 = (Int(y) + sizeY) Mod sizeY
		Local x2 = (Int(x) + sizeX - 1) Mod sizeX
		Local y2 = (Int(y) + sizeY - 1) Mod sizeY
		
		If(x1 < 0) x1 = x1 + sizeX
		If(x2 < 0) x2 = x2 + sizeX
		If(y1 < 0) y1 = y1 + sizeY
		If(y2 < 0) y2 = y2 + sizeY
		
		Local finVal:Float = 0
		
		finVal = finVal + fractionX * fractionY * noiseArr[x1 + y1*sizeX ]
		finVal = finVal + fractionX * (1 - fractionY) * noiseArr[x1 + y2*sizeX]
		finVal = finVal + (1 - fractionX) * fractionY * noiseArr[x2 + y1 * sizeX]
		finVal = finVal + (1 - fractionX) * (1 - fractionY) * noiseArr[x2 + y2*sizeX ]
		
		Return finVal
	
	End 
	
	'===========================================
	'     to fill noise array with white noise
	'===========================================
	Method InitNoise()
		
		noiseArr = New Float[sizeX * sizeY]
		For Local x = 0 To sizeX - 1
			For Local y = 0 To sizeY - 1
				noiseArr[x + y * sizeX] = (Rnd() - 0.5) * 2.0
			Next
		Next
			
	End 
	
	'===========================================
        '   might be usefull, process whole array with sinus
        '============================================
	Method terrainSinus(p:Float)
		
		For Local x = 0 To sizeX - 1
			For Local y = 0 To sizeY - 1
		
				Local md:Float = Sin(y * 180 / sizeY) * 2 - 1
				terrainArr[x + sizeX * y] = md * p + terrainArr[x + sizeX * y] * (1.0 - p)
				
			Next
		Next
	
	End 
	
End 
extended class to draw data on screen and make some quantization due to fact we cannot draw to image / backbuffer so this has limited usage 
'==========================================
'           CLASS                          
'   EXTENDED PERLIN CLASS WITH BITMAP AND LEVELS
'==========================================
Class TPerlinNoise Extends TPerlinNoiseBasic
	
	'min and max colors
	Field colMin:TColor = new TColor(0, 0, 0)
	Field colMax:TColor = new TColor(200,200,200)
	
	Field levels[] = New Int[100]
	Field levelsColor:TColor[] = new TColor[20]
	
	'=========================================
	'		just make new data
	'===========================================
	Method New(_x, _y)
	
		Super.New(_x,_y)
	
		'init levels
		For Local i = 0 To 99
			Self.levels[i] = 0
		Next
		
	End
	
	'==========================================
	'   draw image
	'==========================================
	Method OnRender(scale, dx, dy)
		For Local x = 0 To sizeX - 1
			For Local y = 0 To sizeY - 1
			
				Local val# = terrainArr[x + sizeX * y]
				Local R = colMax.R * val + colMin.R * (1 - val)
				Local G = colMax.G * val + colMin.G * (1 - val)
				Local B = colMax.B * val + colMin.B * (1 - val)
								
				SetColor(R, G, B)
				DrawRect(x * scale + dx, y * scale + dy, scale, scale)
				
			Next
		Next
	End
	
	'==========================================
	'   draw image according to levels, like izolines
	'==========================================
	Method OnRenderLevel(scale, dx, dy)
		For Local x = 0 To sizeX - 1
			For Local y = 0 To sizeY - 1
			
				Local val:TColor = levelsColor[levels[terrainArr[x + sizeX * y]*99]]
				if(val = null) Continue 
				
				SetColor(val.R, val.G, val.B)
				DrawRect(x * scale + dx, y * scale + dy, scale, scale)
				
			Next
		Next
	End
	
	
	'============================================
	'      start process
	'===========================================
	Method OnCreate()
				
		InitNoise()
		MakeTerrainMap()
		
	End 
	
	'============================================
	' setup terrain N from lvl min to lvl max
        ' this is quantization of terrain to one color in range
        ' this creates area of same value = izo lines
	'===========================================	
	Method SetupLevel(levelMin, levelMax, val, R,G,B)
		levelsColor[val] = New TColor(R,G,B)
		For Local i = levelMin To levelMax - 1
			levels[i] = val
		Next
	End 
	
End 
example how to use Local Perlinek:TPerlinNoise Perlinek = New TPerlinNoise(64,48) 'to fill screen Perlinek.ChangeParams(0.1, 0.99, 0.65, 6) 'experiment !! Perlinek.colMin = New TColor(0, 50, 0) 'dark green Perlinek.colMax = New TColor(150, 200, 150) 'light green Perlinek.SetupLevel(0,40,0,0,40,80) 'low level water Perlinek.SetupLevel(40,80,1,40,240,40) 'medium grass Perlinek.SetupLevel(80,100,2,140,140,0) 'high rocks Perlinek.OnCreate() '=========================================================== 'draw levels quantization or draw just all '10 is scale 64 * 10 = 640 if(SpaceKeyIsHit = 1) Perlinek.OnRenderLevel(10,0,0) if(SpaceKeyIsHit = 0) Perlinek.OnRender(10,0,0) output    | 
| 
 | ||
| Nice one, thanks. | 
| 
 | ||
| if any one can help me how to create such image on the fly in monkey (grab image from backbuffer) | 
| 
 | ||
| There's no way to access the framebuffer in the standard Monkey libraries. If you want to be able to do that you will have to write the target code and Monkey abstraction layer yourself. | 
| 
 | ||
| I'd like to test this out, but the TColor class is undefined.  Can you add your code for TColor please? | 
| 
 | ||
| Class TColor Field R,G,B '**************************** '* '**************************** Method New(R,G,B) Self.R = R Self.G = G Self.B = B End '**************************** '* '**************************** Method New(col) Self.R = col & $00FF0000 Self.R = Self.R / $10000 Self.G = col & $0000FF00 Self.G = Self.G / $100 Self.B = col & $000000FF End '**************************** '* '**************************** Method GenerateColor:Int() Return $FF000000 + (R * $10000 + G * $100 + B) End End | 
| 
 | ||
| use it with temperatur / altitude / humidity layers and pick if low temp and high hum then its tundra etc... | 
| 
 | ||
|  if any one can help me how to create such image on the fly in monkey (grab image from backbuffer)  Forget rendering and then grabbing an image. You have all the data you need in your TPerlinNoiseBasic class arrays. :) Just calculate the pixel color values from your floats and put these in a new array, which is then put into an image: Strict 'Come on.. Don't forget this.. 'Create an image Local img:= CreateImage(width, height) 'And an array Local pixels:= New Int[width * height] '...calculate pixels's color values ' and then img.WritePixels(pixels, x, y, width, height) It is also possible to create terrain images bigger than the screen, thanks to WritePixels parameters! I use this code to get my tilemap-layers into images, and tilemaps usually are bigger than the screen:     Method SetupChunks:Void()
        chunkWidth = fullWidth
        chunkHeight = fullHeight
        
        While chunkWidth > DeviceWidth()
            chunkWidth /= 2
        Wend
        While chunkHeight > DeviceHeight()
            chunkHeight /= 2
        Wend
        
        chunkCols = fullWidth / chunkWidth
        chunkRows = fullHeight / chunkHeight
    End
    
    Method PreRender:Void()
        Local x:Int, y:Int
        Local w:Int = data.width * tileWidth 'Layer's width * Tile width
        Local h:Int = data.height * tileHeight 'Layer's height * Tile height
        
        graphic = CreateImage(w, h)
        
        Local pixels:= New Int[chunkWidth * chunkHeight] ' We only need an array for a chunk, not whole image...
    
        For Local chunkX:Int = 0 Until chunkCols
            For Local chunkY:Int = 0 Until chunkRows
                Cls(0, 0, 0)
                
                x = chunkX * chunkWidth
                y = chunkY * chunkHeight
        
                Select renderingType
                    Case ORTHOGONAL
                        RenderOrtho(x, y, chunkWidth, chunkHeight)
                    Case ISOMETRIC
                        RenderIsometric() ' This doesn't even work yet, so don't ask... :)
                End
                
                ReadPixels(pixels, 0, 0, chunkWidth, chunkHeight) 'Read where we rendered
                MaskPixels(pixels, 0, 0, 0)
                graphic.WritePixels(pixels, x, y, chunkWidth, chunkHeight) '...but put it in right place in our image.
            Next
        Next
    EndEDIT: Sorry.. Didn't notice this was so old... T__T | 
| 
 | ||
| Okay, so I played around a little and got it working with images. It "renders" the pixels straight into the image. Should there be no need for any pre-rendering. Also the terrain can be any size, even bigger than the rendering screen. I will still be optimizing the code since there's some left-overs from the earlier rendering.. :) [DONE] Also will be adding colormap, that isn't yet implemented. Will be using gradients which you are able to set key-colors to. [DONE] EDIT: How it looks with 2 gradient colors (red and lighter blue):  Usage:  'Use these, in for example OnCreate()
Local Perlinek = New TPerlinNoise(640, 480)
Perlinek.SetParams(0.05, 0.99, 0.65, 8) ' Experiment with these.. ;)
Perlinek.MakeTerrainMap()
        
Perlinek.SetLevelColor(0.0, New Color(255, 0, 0))
Perlinek.SetLevelColor(1.1, New Color(0, 128, 255)) ' Have to fix this, doesn't work as it should with key 1.0...
Perlinek.CreateTerrain()
'....
'And simply draw the image in OnRender():
DrawImage(Perlinek.image, 0, 0)
Code (updated with gradient): | 
| 
 | ||
| Here is another with Temperate-like colors:  Perlinek.SetLevelColor(1.1, New Color(2, 43, 68)) ' Water Perlinek.SetLevelColor(0.55, New Color(9, 62, 92)) Perlinek.SetLevelColor(0.51, New Color(17, 82, 112)) Perlinek.SetLevelColor(0.5, New Color(69, 108, 118)) ' Shore Perlinek.SetLevelColor(0.49, New Color(42, 102, 41)) ' Land Perlinek.SetLevelColor(0.25, New Color(115, 128, 77)) Perlinek.SetLevelColor(0.15, New Color(153, 143, 92)) Perlinek.SetLevelColor(0.05, New Color(179, 160, 120)) Perlinek.SetLevelColor(0.0, New Color(220, 200, 180)) ' Mountains |