LoginLogin
Nintendo shutting down 3DS + Wii U online services, see our post

Diamond Square Noise and Plasma Effect

Root / Submissions / [.]

haloopdyCreated:
This tutorial will not go into much depth about the code. An implementation of the plasma (with messier code than what's shown here) is available for testing in the RNDLIBTEST program of Random's Big Dumb Library. You can use it to test out various color and parameter combinations without having to reprogram them yourself.

Plasma effect:

https://www.youtube.com/watch?v=JDlII65rCZM

Random Noise

Random noise is a block of seemingly random data which actually has a semblance of a pattern. It can be used to generate terrain, clouds, AI, textures, and basically anything else that requires "controlled" randomness. Purely random noise looks like TV static. It's not very useful: But controlled random noise looks much nicer: We're going to use the Diamond Square algorithm (a controlled random noise generator) to make the plasma effect you see in the video above.

Diamond Square

Diamond Square is a noise generator algorithm which uses 4 initial starting values and a series of decaying averages to generate interesting patterns. If you are interested in understanding the algorithm, more information can be found here: https://en.wikipedia.org/wiki/Diamond-square_algorithm First, we're going to need one other function: normalization. Noise isn't useful unless it's constrained to a known range, and default Diamond Square doesn't do this. Normalization will force the values (after generation) into the range of 0-1 (both inclusive). NOTE: Reusable code is the best code. All code here will be made into functions.
COMMON DEF NORMALIZE2D D#,SIZEX,SIZEY
 DIM MINV#=MIN(D#)
 DIM MAXV#=MAX(D#)
 DIM RANGE#=MAXV#-MINV#
 DIM X,Y
 FOR Y=0 TO SIZEY-1
  FOR X=0 TO SIZEX-1
   D#[X,Y]=(D#[X,Y]-MINV#)/RANGE#
  NEXT
 NEXT
END
This function is included in Random's Big Dumb Library Now we can do Diamond Square. Diamond square works on a chunk system like minecraft: each chunk is 2N wide and tall and the overall array is a series of these chunks. The array that's returned is JUST a 2D array of values though; the chunks are "imaginary" and are only used in the algorithm. The parameters are: Feature Size (N), Width (Chunks across), Height (Chunks down), Seed (same seed = same noise), Decay (lower=smoother noise). The final dimensions of the returned noise array is (2N*Width+1,2N*Height+1). The Feature Size (N parameter) determines how "big" all the bumps are. Larger N = larger bumps, but remember it scales up by powers of 2 so don't go larger than 8 or so (28 is 256). At 1, the array will be nearly random static noise like the first picture. Experiment with N to find a good feature size for your needs. The decay is inverse smoothness: from 0 to 0.5, it will be exceptionally smooth. From 0.5 on it gets more rough: at 1 (the highest value you should give), it looks like you mixed the static with the good noise. Again, just experiment to find the value you like. It's a parameter for a reason. In our function, we use a seeded random number generator. SB comes with 8 different generators: we're using #7. If you need a different one, just change S.
COMMON DEF DIAMONDSQUARE#(N,WD,HT,SEED,DECAY#)

 DIM S=7
 DIM BLOCK=POW(2,N)
 DIM SIZEX=BLOCK*WD+1
 DIM SIZEY=BLOCK*HT+1
 DIM ENDX=SIZEX-1
 DIM ENDY=SIZEY-1
 DIM D#[SIZEX,SIZEY] 'Final noise array.
 
 DIM P,RDEC#,OFS,HLF,X,Y,NUM,BX,BY
 DIM TOP,BOTTOM,LEFT,RIGHT,REDUX 'Optimization stuff
 RANDOMIZE S,SEED

 'Corners (DS uses initial 4 corner values of each chunk to generate entire array)
 FOR Y=0 TO HT
  FOR X=0 TO WD
   D#[X*BLOCK,Y*BLOCK]=RNDF(S)
  NEXT
 NEXT

 'Shrinking chunk series (look up Diamond Square to see what this is)
 FOR P=N TO 1 STEP -1
  RDEC#=POW(DECAY#,N-P)*0.25 'Decaying randomness (plus a magic constant shrinking)
  OFS=POW(2,P)
  HLF=OFS/2
  'Diamonds
  FOR Y=HLF TO ENDY STEP OFS
   FOR X=HLF TO ENDX STEP OFS
    D#[X,Y]=(D#[X-HLF,Y-HLF]+D#[X+HLF,Y-HLF]+D#[X-HLF,Y+HLF]+D#[X+HLF,Y+HLF])/4+(0.5-RNDF(S))*RDEC#
   NEXT
  NEXT
  'Squares
  FOR Y=0 TO ENDY STEP HLF
   NUM=4
   'All these TOP/BOTTOM/LEFT/RIGHT checks are because the square step might go out of bounds and we need to handle that
   IF Y>0 THEN TOP=Y-HLF ELSE TOP=Y:NUM=3
   IF Y<ENDY THEN BOTTOM=Y+HLF ELSE BOTTOM=Y:NUM=3
   FOR X=HLF*((Y AND (OFS-1))==0) TO ENDX STEP OFS
    REDUX=0
    IF X>0 THEN LEFT=X-HLF ELSE LEFT=X:REDUX=1
    IF X<ENDX THEN RIGHT=X+HLF ELSE RIGHT=X:REDUX=1
    D#[X,Y]=(D#[X,TOP]+D#[X,BOTTOM]+D#[LEFT,Y]+D#[RIGHT,Y])/(NUM-REDUX)+(0.5-RNDF(S))*RDEC#
   NEXT
  NEXT
 NEXT

 NORMALIZE2D D#,SIZEX,SIZEY
 RETURN D#
END
This function is included in Random's Big Dumb Library

Plasma Effect

We will use the result from the DIAMONDSQUARE#() algorithm and convert it into palette data to use in GLOAD. We will only need to generate the noise array once (which takes time), but then we can use the same array for each frame of the animation and just shift the palette colors. Basically, each element in the array will change from 0-1 to, for instance, 0-255, then we'll generate 256 colors for the palette and use GLOAD (which draws palette data with the given colors onto the current GRP) to draw the plasma. Each frame, we'll rotate the palette by 1 so the colors seem to "move" around. The Diamond Square noise gives it that plasma feel. Note: you can use this same method to generate many other kinds of animations. GLOAD is a powerful tool.

Color smoothing

We can't just plop palette colors into an array. To make it look like plasma, the colors need to smoothly transition to each other. Here's a simple color smoothing algorithm which using linear interpolation to blend two colors together by a certain amount:
COMMON DEF LERP(A#,B#,T#)
 T#=MIN(ABS(T#),1)
 RETURN A#+T#*(B#-A#)
END

'Shift is how much of each color to use. 0=all col1, 1=all col2, 0.5=half and half
COMMON DEF BLENDCOLORS(COL1,COL2,SHIFT#) 
 DIM C1R,C1G,C1B,C2R,C2G,C2B,A1,A2
 RGBREAD COL1 OUT A1,C1R,C1G,C1B
 RGBREAD COL2 OUT A2,C2R,C2G,C2B
 RETURN RGB(FLOOR(LERP(A1,A2,SHIFT#)),FLOOR(LERP(C1R,C2R,SHIFT#)),FLOOR(LERP(C1G,C2G,SHIFT#)),FLOOR(LERP(C1B,C2B,SHIFT#)))
END
These functions are included in Random's Big Dumb Library

Plasma

Now for plasma. Note that I'm picking kinda arbitrary parameters for the noise: N, width, and height are VERY important, but the others are just values I like.
DIM N=5
DIM WD=8
DIM HT=8
DIM DECAY#=0.8
DIM DS#[0,0]  'Raw Diamond Square data 

DIM PCNT=256  'How many colors in the whole palette (try to keep it under 256; more is unnecessary)
DIM PL[PCNT] 'The palette of blended colors
DIM CCNT=4  'The separate pure colors to use in the plasma (count)
DIM COLS[CCNT]

'This is a red and blue plasma like in the video. We insert black between to make it look cooler
COLS[0]=#RED
COLS[1]=#BLACK
COLS[2]=#BLUE
COLS[3]=#BLACK

'Use MILLISEC as seed to make it random plasma each time
DS#=DIAMONDSQUARE#(N,WD,HT,MILLISEC,DECAY#)

'Convert DS raw to palette data. Remember how big the DS array is:
DIM SIZEX=POW(2,N)*WD+1
DIM SIZEY=POW(2,N)*HT+1
DIM X,Y,C,I,P
DIM PD[SIZEY,SIZEX] 'Diamond Square data converted to palette range. We reverse X and Y because of how SB does GLOAD
FOR X=0 TO SIZEX-1
 FOR Y=0 TO SIZEY-1
  PD[Y,X]=CEIL((PCNT-1)*DS#[X,Y])
 NEXT
NEXT

'Generate the base plasma palette. Iterate over each color, then spread out the blending over the palette range
FOR C=0 TO CCNT-1
 FOR I=0 TO PCNT/CCNT
  P=C*PCNT/CCNT+I
  IF P>=PCNT THEN BREAK
  PL[P]=BLENDCOLORS(COLS[C],COLS[(C+1)MOD CCNT],I/(PCNT/CCNT))
 NEXT
NEXT

'Now just display the plasma. Remember that GLOAD needs the correct width and height of the array. Also remember we're rotating the palette
WHILE TRUE
 GLOAD 0,0,SIZEX,SIZEY,PD,PL,FALSE
 C=SHIFT(PL)
 PUSH PL,C
 VSYNC
WEND
If you have any questions or if the code doesn't work, please let me know. I might've made a typo or something.

It might be faster to COPY between two arrays instead of using SHIFT/POP (though I don't know if this significantly affects the overall speed)

Replying to:12Me21
It might be faster to COPY between two arrays instead of using SHIFT/POP (though I don't know if this significantly affects the overall speed)
How would that work? Does COPY wrap around?

Replying to:12Me21
It might be faster to COPY between two arrays instead of using SHIFT/POP (though I don't know if this significantly affects the overall speed)
I mean, you'd have to do 2 COPYs
source: 12345
  dest: .....
        ..123 'copy 1
        54123 'copy 2