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 2
N 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 (2
N*Width+1,2
N*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 (2
8 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.