Da Fish in Sea

These are the voyages of Captain Observant

Perlin Noise - 1 Dimensional

| Comments

So I’ve been delving into Perlin Noise, looking for a way to generate lifelike textures procedurally. After researching online I hacked up a class to generate 1D Perlin noise… the 2D & 3D stuff will come soon hopefully. Here’s a demo of it, followed by the code.

Here’s the Perlin noise class at this point:

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
package  {

  public class PerlinNoise {

      public function PerlinNoise() {
          /*call getPerlinNoise_1D*/
      }

      public function getPerlinNoise_1D(len:int=200, octaves:int=8, decay:Number=1):Array {
          var graph:Array;
          //totalGraph holds accumulated values
          var totalGraph:Array = getNoiseFunctionResults(len, 1, 1);
          var totalAmplitude:Number= 1;//used to normalize amplitude
          for(var o:int = 1; o <= octaves; o++){
              if(Math.pow(2,o) < len) {//cannot have frequency > len
                  graph = getNoiseFunctionResults(len, Math.pow(decay,o)/*amplitude*/, Math.pow(2,o)/*frequency*/);
                  totalAmplitude+=Math.pow(decay,o);
                  for(var g:int = 0; g < graph.length; g++){
                      //add the graphs together by shifting them so they are centred on y=0 (subtract their amplitude/2)
                      totalGraph[g] += graph[g];
                  }
              }
          }
          //normalize values in totalGraph to be between -0.5 & 0.5 by dividing by accumulated amplitude
          for(var i:int = 0; i < totalGraph.length; i++){
              totalGraph[i] = totalGraph[i]/totalAmplitude;
          }
          return totalGraph;
      }

      private function getNoiseFunctionResults(len:int=200, amplitude:Number=1, freq:int=1):Array {
          //trace("getNoise(amplitude="+amp+",frequency"+freq+")");
          var result:Number = 0;//between -0.5 & 0.5
          var wavelength:int = len/freq;//divide imgWidth by frequency to get wavelength
          var results:Array = [];
          //start with random seed
          var seed:uint = Math.round(Math.random()*0x7FFFFFFF); //seed for prng... can be any uint (except 0)

          //get next seed  - used for interpolation  
          var nextseed:uint = prng(seed);  

          //for each x value 
          for(var x:int = 0; x < len; x++){

              //if we are on a factor of wavelength ... get psuedorandom y value 
              if(x % wavelength == 0){
                  //store nextseed as seed
                  seed  = nextseed;
                  //get next nextseed
                  nextseed = prng(seed); 
                  //get y value from pseudorandom number
                  result = (seed/0x7FFFFFFF)*amplitude - amplitude/2;
              } else {
                  //interpolate value between seed & nextseed    
                  result = (interpolate(seed, nextseed, (x % wavelength)/wavelength)/0x7FFFFFFF)*amplitude - amplitude/2;
              }
              results.push(result);
          }
          return results;   
      }

      private function prng(seed:uint):uint {
          //to get a full period sequence you should feed back the seed
          return seed * 16807 % 0x7FFFFFFF;   
          //to get a value between 0 & 1, divide result / 0x7FFFFFFF
      }

      private function interpolate(a:int,b:int,i:Number):Number {
          //cosine interpolation
          var ft:Number = i*Math.PI;
          var f:Number = (1 - Math.cos(ft)) * .5;
          return a*(1-f) + b*f;
      }
      
  }
}      
// Copyright (c) 2008 David Wilhelm
// MIT license: http://www.opensource.org/licenses/mit-license.phpwww.opensource.org/licenses/mit-license.php

The code for the above demo of the class is here: Note the use of Keith Peters’s excellent minimal comps

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
package  {
        import flash.display.Sprite;
        import flash.display.Bitmap;
        import flash.display.StageScaleMode;
        import flash.display.StageAlign;
        import flash.display.BitmapData;
        import flash.text.TextField;
        import flash.events.Event;
        import com.bit101.components.*;
        import flash.geom.Rectangle;</p>

    [SWF(backgroundColor="0xFFFFFF", width="600", height="400", frameRate="30")]
    public class PerlinNoise1D extends Sprite {

        private const imgWidth:Number = 400;
        private const imgHeight:Number = 400;
        private var octaves:int = 8;
        private var decay:Number= 1;
        private var bm:Bitmap;
        private var bmd:BitmapData;

        public function PerlinNoise1D() {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            //create a bitmap data 
            bmd = new BitmapData(imgWidth, imgHeight, false, 0x00000000);
            //create a bitmao using the data object
            bm = new Bitmap(bmd);
            addChild(bm);
            drawGraph();
            drawUI();
        }
        private function drawUI():void {
            var ui:Sprite = new Sprite();
            //octaves
            var octaves_sldr:HSlider = new HSlider(ui, 50, 20, function(e:Event):void {
                    octaves = Math.round(e.target.value);
                    octaves_val.text = ""+octaves;
                    drawGraph();
                });
            octaves_sldr.setSliderParams(1,10,1);
            var octaves_lbl:Label = new Label(ui, 0, 15, "octaves");
            var octaves_val:Label = new Label(ui, 150, 15, "8");

            //decay - between 0 &amp; 1
            var decay_sldr:HSlider = new HSlider(ui, 50, 40, function(e:Event):void {
                    decay = e.target.value;
                    decay_val.text = ""+decay;
                    drawGraph();
                });
            decay_sldr.setSliderParams(0,1,1);
            var decay_lbl:Label = new Label(ui, 0, 35, "decay");
            var decay_val:Label = new Label(ui, 150, 35, "1");
            ui.x = 100;
            ui.y = 5;
            ui.alpha= 0.5;
            addChild(ui);
        }

        private function drawGraph():void {

            var y:Number = 0;

            var perlin:PerlinNoise = new PerlinNoise();
            bmd.fillRect(new Rectangle(0, 0, imgWidth, imgHeight), 0x00000000);

            //get 1D Perlin noise
            var pNoise:Array = perlin.getPerlinNoise_1D(imgWidth,octaves,decay);
            trace(pNoise);
            //loop through each point and draw
            for(var i:int = 0; i &lt; pNoise.length; i++){
                y = pNoise[i]*imgHeight + imgHeight/2;//make y positive value between 0 &amp; imgHeight
                bmd.setPixel(i,Math.round(y), 0xFFFFFFFF);
            }

            //bm.bitmapData = bmd;  
        }

    }
}
// Copyright (c) 2008 David Wilhelm
// MIT license: http://www.opensource.org/licenses/mit-license.php