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 End EDIT: 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 |