Plasma
----------------------------------------------
-----------************************-----------
-----------**CODE YOUR OWN PLASMA**-----------
-----------************************-----------
-------------BY SPANSH / DYNAMIX--------------
Disclaimer
I'm sorry I have to do this but here goes. I accept no responsibility for any damage done to your computer by either my programs or by your following my instructions. Any problems caused by this tutorial are entirely your problem. Do not come to me saying 'Your program deleted windows' or any other line. You use this tutorial at your own risk. All programs have been tested on my computer and other peoples computers and have worked okay without problems.
Any examples in this tutorial are going to be in pascal. I'm sorry if you don't like this but I don't code in C yet, and Assembler is out of the question for understanding the code.
In this tutorial I am going to show you how to code your own plasma. Now you may be saying to yourself at this point "Plasma what the heck is he going on about". Well read on if you do. A plasma is a waving multicoloured pattern on the screen. It can be created in a number of ways
- Sin/Cos plasma which is like moving circles on a screen.
- Fractal plasma which is a static fractal picture with a *palette rotation*.
- Any other picture with a *palette rotation*.
** By palette rotation I mean changing the colours in the picture ie replace all of the pixels coloured black with a blue colour.
A palette is a table of colours like so.
Colour | Red | Green | Blue |
1 | 2 | 5 | 4 |
2 | 3 | 4 | 4 |
3 | 4 | 3 | 4 |
4 | 5 | 2 | 4 |
This ia a small palette of 4 colours to rotate that palette you would move all the colours down one and move the last colour to the top. Then repeat that operation and your are rotating the palette.
Okay, the plasma I am going to explain here is the sin/cos plasma. This is done by using a *sine wave*.
**A sine wave is calculated using the mathematical function sine. The sine function is mainly used in trigonometry with triangles.
The Sine Wave
y
1| /-\ /-\
|/ \ / \
0|-----|-----|-----|-----|->x
| \ / \ /
-1| \-/ \-/
|
| | | | |
0 180 360 540 720 (Okay so my ASCII art stinks)
In Maths Terms
This is the graph that you would get if you plotted the number that you put into your sine function as the x value and the sine of that number as the y value.
We don't need to worry about all that maths bit. All that we need to know is that it makes a nice pretty wavy shape. Now that wavy shape goes from -1 to 1 and back. That will happen all the time (unless we got a problem). Now in order to get a sine wave that is a little less repetetive we need to add together a few sine waves. (Here's where it gets a bit more complicated). To get a different sine wave you can instead of getting the sine of the x value we can multiply that number by 2 and the sine wave will be more squashed up.
Like so
The Sine Wave (Version 2)
y
1| /-\ /-\
|/ \ / \
0|-----|-----|-----|-----|->x
| \ / \ /
-1| \-/ \-/
|
| | | | |
0 360 720 1080 1440
In Maths Terms
Now That is the same graph but the x line is more squashed up. It looks a lot more understandable (I hope) in the example program (Sines.exe/pas).
You can also make a more spaced out sine wave by using Y = \sin\left(\frac{X}{2}\right) if you follow the reasoning. I am going to stop using ASCII art and hope that you will be able to figure out the maths yourself now. If you can't then I would get a piece of paper and plot the graphs for yourself to see. Or alternatively look at the example program.
Now we can add these sine waves together. Say we take three sine waves.
We Can add them together and make them into a graph by using.
Now we can multiply each of those sine waves by a number.
Now we can make this into a dynamic graph. Okay okay I will explain a Dynamic graph to you. A dynamic graph is a graph of three variables.
like
If We start with A as 0 then we plot
Then we add 1 to a and plot
So a dynamic graph is like a bit like a film. Now to make the sine graph into a nice dynamic graph then we do the following. We first make a new variable such as A.
The we take our old equation
And much in the same way as we could make the sine wave more spaced out or squashed up by changing X (by multiplying it or by sharing it by a number).
We can do the same to a by using
Then we take add these to our equation like so.
Wow our equation is getting rather big isn't it ? This equation is exactly how the last part of the example program is done. First you plot the equation with a equal to 0. Then you add a tiny amount to A and plot the equation again.
Now that is pretty in itself.
Now comes another hard part actually creating the plasma
Say we have a square 100 by 100 pixels, okay. Then we make two of our moving sine waves. Now instead of plotting the (X,Y) graph we take the coordinates for each of our pixels in the square. say we have the first coordinate (0,0).
Now in the first sine wave equation we place the X-Coordinate of our square. Then in our second sine wave equation we place the Y-Coordinate of our square. The We add the two answers we get from our equations together, and we colour the pixel at those the coordinates that we placed into our equations ((0,0) in this example) with the value we obtained by adding the two answers together. And then when we have gone through the square and coloured every pixel. We ann a tiny amount to our dynamic variable (A is the one we were using) and start again. This will produce our plasma. Okay so this sounds extremely hard. Its not really. Take a look at the sample program (Plasma.pas/exe)
Now comes another good bit Speeding the plasma up
Okay so you might not have been very impressed by that plasma program but imagine it running at 30 frames per second in full screen and a nice palette. Good eh. Well Heres the good bit speeding the plasma up.
Well first of all calculating sines is a very slow way of doing things. Now maybe you know or maybe you don't but your basic sine wave repeats itself after every 360 degrees. So we could create an *array* of the values of each sine.
**If you don't know what an array is then here is a quick description. If a variable is like a little hole that you can store data in like this.
_
|_|
Variable A
Then an array would be like this
_ _ _ _ _ _ _ _ _ _
|_|_|_|_|_|_|_|_|_|_|
Array A
An array is like a stack of variables.
Now if the array was called 'A' then we could access the second variable in that array by (In Pascal anyway) using the identifier like this.
A[10] would give us the 10th variable in the array A. The identifier can be any ordinal type.
An ordinal type is a type that has no decimal places so in this case any of the following would be valid ordinal types.
A to Z, Any number from 0 to Infinity (there are limits but never mind that).
You could not have any of the following
PI, 3.5746389 as these are both reals.
Anyway back to our sine array. So I f we created an array called SinArray.
In pascal we would declare this in our var field like this :
Var
SinArray : Array [0..360] Of Real;
The name of our array is SinArray, it is of type real and it has 360 variables in it
Now we have to create that array. By create it I mean place the values of sine into it. This is very simple. We just do this by using a for loop
(In Pascal)
For Counter := 0 to 360 Do
Begin
Angle := Rad(Coutner);
SinArray[Counter] := Sin(Angle);
End;
Another advantage of this is that we can stop using those stupid radians
Yaaaaaay
Okay but now we have a small problem that the angle we create might be over 360. Wee cannot access that array after the 360th variable so what do we do? But we know that the sine function repeats itself every 360 degrees so we just divide by 360 and find the remainder. In pascal we would use the mod function.So to Access our array we would do the following.
XAnswer := 20 * SinArray[((X * 2 ) + (ToAdd )) Mod 360]+
30 * SinArray[((X ) + (ToAdd * 2 )) Mod 360]+
50 * SinArray[((X Div 2) + (ToAdd Div 2)) Mod 360] ;
{Since the identifier for the array has to be of ordinal type and we are now using degrees we can made ToAdd an Integer and add 1 to it each frame.} I have changed the /'s into divs because a / will give us a real value but the div function will give us an ordinal value.}
Okay now we have our array. For an example of this look at the example program (PlasArra.Pas/exe)
Hey we now have a nice plasma. It is quite fast but what it is only 100x100 squares. Okay now we are going to have to make a bit of a leap in knowledge here.
Fixed Point Arithmetic
Now here comes a hard part.
In this plasma all these truncs and rounds that we use are very slow. We need a faster way of doing this and fixed point is a way of using integers to simulate real numbers. I can hear you saying now what the hell is he going on about. A real number is a real number and an Integer is an integer they can't be both can they ?? Well you're right of course they can't. But I we did our trunc before the array then we would lose a lot off accuracy in fact we would only get about 4 colours. Well yes but watch this hopefully nice example.
We have our real number 3.12548 ok.
Now if we wanted to put that into fixed point we could
(I know I'm using 10's here but it is to make understanding much easier)
multiply it by 100000.
3.12548 * 100000 = 312548.
Well so what I hear you say. Well if you notice that that has no decimal point in it but it still contains all of the original numbers in exactly the same order. And it turns out that we can treat this exactly in the same way as the original number (adding, subtracting, multiplying, dividing anyway) so long as we divide our final number by 10000. Now all we are doing is a mix of those four functions. So if we made our array fixed point then we could lose all of those truncs in the plasma code. Yaaaay. Now it also turns out that you don't have to only multiply by powers of 10 we could multiply by any number so long as we share by that number at the end. Now we all know that the computer works in binary right. Well binary works in powers of 2 so we can make the computer work very fast by using the following numbers.
21 = 2
22 = 4
23 = 8
24 = 16
25 = 32
26 = 64
27 = 128
28 = 256
29 = 512
210 = 1024
Now we can do a very fast multiply by a power of 2 by using the shifting. In pascal we use SHR and SHL. We could multiply by 4 by using
OurNumber Shl 2 = Answer. so we SH(L or R) by the power to multiply by 2 to that power. To divide by that you simply SHR instead of SHL.
So Instead of multiplying by 10000 we are going to use 1024 as this is a power of 2. To convert our final number back to what we want we simply use SHR 10. Ok. If not then try to look at the code in the example program (PlasFix.pas/exe)
There is a downside in fixed point in that we do lose a bit of accuracy but since we are going to need an integer anyway there won't be too much of a difference.
Okay I still have a few tricks up my sleeve.
Now When we are using a putpixel we are having to calculate the position of each pixel every time we place one. Now we know exactly where we are going to be putting each pixel it will be right after the last one. Then when we get to the end oa line it will start from the beginning again. So instead of using a putpixel we could just keep a counter variable that keeps track of where we are in the plasma. Look at the program to see what I mean. (PlasFast.exe/pas) This program includes these last two pointers.
Okay you know I said the computer works fastest with powers of two. Well The mod function is also a bit slow. nd if we were getting the remainder of a power of two then we could just use the And function.
We would use it like so
Number Mod 512 = Answer;
Number And 511 = SameAnswer;
So we and by 1 less than the power. Don't ask me why I do know but this tut is getting too long as it is.
Now I bet you are saying to me that this is of no use to us as 360 is not a power of 2. Ok I'll give you that. but what if we could forget using degrees and make our own measurement that went about 360 degrees in 512 units. It's actually not that hard. All we have to do Is divide our Angle (In Radians) by 360 and multiply by 512. We only have to do this once (when we calculate the array)Take a look at the example program (Plasfast.pas/exe) if you don't understand.
There is one more optimisation which I have found out and I don't think anyone else has used it. If I am wrong then correct me. The way I do my Plasma's is almost fully realtime ie I add up the sine waves in the loop. This makes it much much more versatile, but this does mean that the calculation for the x part of the loop is performed 64000 times a frame. Now if you look at it then you will notice that it is going to be the same values calculated for every frame for each y value. So if we create an array that is the size of our screen width and before you start the main loop:
For Y := 0 to 199 Do
Then you do a loop like this
For X := 0 to 319 Do
XValues[X] := {Your sin Calculations}
Position := 0;
For Y := 0 to 199 Do
Begin
YAnswer := {Your sin calculations}
For X := 0 to 319 Do
Begin
XAnswer := XValues[X];
Colour := (Xanswer + YAnswer) Shr 10;
Mem[VGA:Position] := Colour;
Inc(Position);
End;
End;
This loop will be much much faster that before.
Well I think thats it from me. Here are a few greets and I have also included my plasma (in exe form plasmess.exe) It is coded in Pascal and Inline assembler and is only 3kish.
Spansh Greets
=============
____________________
Tig2 - Thanx for all your help |If you want some |
(Get his PMode tut now) |tutorials from me |
Jungle - Without you there would be no tut. |then mail me, if I |
Statix - Thanx for putting up with me at Wired, |get a few requests |
when is the next demo/intro out ? |from people of what |
Optix - Again Thanx for putting up with me at Wired,|they want then I |
When is Molejo out ? |will do a tutorial |
AquaFresh - Thanx for getting me to Wired :) |for what the most |
Auruora - You know who you are. |requested stuff is |
Denthor - For getting me into VGA coding. |Ask for anything |
RyanT - For not adding me to X's user list :) |Textured Tunnels, |
Fatar - For all your help |Shadebobs, Wormholes|
D_Talbane - Cool mov file man B-) |, Starfields (I got |
#coders - you guys know who you are :) |a cool one), Binary,|
|Optimising, Whatever|
|____________________|
MiKMaN Greetz
=============
<Put your greetz here mik>
/---------------------------+---------------------------\
|Dynamix Are :- | Mail |
+---------------------------+---------------------------+
|Spansh = Coder. |Gareth.Harper@onyxnet.co.uk|
|MiKMaN = GFX + 3d Objects.|PMGroom@Compuserve.com |
+---------------------------+---------------------------+
|Mik I need some 3d objects now, anytime will do. :) |
\---------------------------+---------------------------/
Oh yeah we need a musician if u wanna be in the group send us an example of your music and we'll get back to ya.
SINES.PAS
{$X+}
Program Sines;
Uses Plas,Crt;
Var
Counter : Integer;
Angle,Temp,ToAdd : Real;
{This will be the number we put into the sine function}
Procedure SetUpAxis;
Begin
Cls(VGA,0);
HLine(0,319,100,9,VGA);
End;
Begin
WriteLN('This program will do the following things');
WriteLN('1. Display a basic sine wave');
WriteLN('2. Display a more squashed up sine wave done by multiplying the angle by 2');
WriteLN('3. Display a more spaced out sine wave done by dividing the angle by 2');
WriteLN('4. Display these three sine waves all added together');
WriteLN('5. Display how to make these sine waves move');
ReadKey;
SetMCGA;
{Get us into graphics mode}
{This bit sets up the axis}
SetUpAxis;
For Counter := 0 to 319 Do
{I do this only Because I use mode 13h and that can only fit 320 pixels
on the screen so I use 0 to 180 and multiply by two}
Begin
Angle := Rad(Counter*2);
{Rad is a function that changes degrees into radians. Radians are a
stupid measurement like degrees only more complex. The Turbo Pascal
sine routine only accepts radian measurements. We don't need to worry about
them just use the function to convert your degrees to it}
Putpixel(Counter,100+Trunc(99*Sin(Angle)),10,VGA);
End;
Readkey;
SetUpAxis;
For Counter := 0 to 319 Do
Begin
Angle := Rad(Counter*4);
Putpixel(Counter,100+Trunc(99*Sin(Angle)),11,VGA);
{I multiply by 99 to make the effect larger}
End;
Readkey;
SetUpAxis;
For Counter := 0 to 319 Do
Begin
Angle := Rad(Counter);
Putpixel(Counter,100+Trunc(99*Sin(Angle)),11,VGA);
{I multiply by 99 to make the effect larger}
End;
Readkey;
SetUpAxis;
For Counter := 0 to 319 Do
Begin
Angle := Rad(Counter*8);
Temp := 20 * Sin(angle);
Angle := Rad(Counter);
Temp := 50 * Sin(angle) + Temp;
Angle := Rad(Counter*4);
Temp := 30 * Sin(angle) + Temp;
Putpixel(Counter,100+Trunc(Temp),11,VGA);
End;
Readkey;
SetUpAxis;
ToAdd := 0;
While Not Keypressed Do
Begin
ToAdd := Toadd + 0.05;
SetUpAxis;
For Counter := 0 to 319 Do
Begin
Angle := Rad(Counter*8);
Temp := 20 * Sin(angle+ToAdd*4);
Angle := Rad(Counter);
Temp := 50 * Sin(angle+ToAdd/2) + Temp;
Angle := Rad(Counter*4);
Temp := 30 * Sin(angle+ToAdd) + Temp;
Putpixel(Counter,100+Trunc(Temp),11,VGA);
End;
WaitRetrace;
WaitRetrace;
End;
SetText;
{Put us back in text mode}
WriteLN('Dont send me mail telling me that these routines are crap.');
WriteLN('I know that they are but they are to show people the basics');
WriteLN('They are not here to show people how fast my code is');
WriteLN('If you want fast code from me then mail me with an example of your');
WriteLN('plasma andI will send you mine');
WriteLN('Mail me at Gareth.Harper@Onyxnet.Co.Uk');
End.
PLASMA.PAS
{$X+}
Program Plasma;
Uses Plas,CRT;
Var
XAnswer,YAnswer,
ToAdd : Real;
X,Y : Integer;
Begin
WriteLN('This program will only do one thing :(.');
WriteLN('It will draw our basic plasma very slowly so''s you can see it');
WriteLN('Okay so maybe it''s slow because the code''s crap');
WriteLN('Change the numbers have a play thats what its here for.');
ReadKey;
SetMCGA;
ToAdd := 0;
While Not Keypressed Do
{Repeat this code until someone pressed escape}
Begin
ToAdd := ToAdd + 1;
{Here we add our small amount to our dynamic variable}
For X := 0 to 100 Do
Begin
XAnswer := 20 * Sin(Rad((X*4) +(ToAdd)))+
30 * Sin(Rad((X)+(ToAdd*4)))+
50 * Sin(Rad((X/4) + (ToAdd/4)));
{This is our first answer for our X coordinate of the square}
For Y := 0 to 100 Do
Begin
YAnswer := 40 * Sin(Rad((Y*6) +(ToAdd)))+
40 * Sin(Rad((Y)+(ToAdd*6)))+
20 * Sin(Rad((Y) + (ToAdd/6)));
{This is our second answer for our Y coordinate of the square}
PutPixel(X,Y,Trunc(XAnswer+YAnswer),VGA);
End;
End;
End;
SetText;
WriteLN('I''m gonna say this again because it''s evident in this program a lot');
WriteLN('This code is sloooow do not write to me saying that, I know.');
WriteLN('Write to me if you code any plasma''s though and I''ll send you some');
WriteLN('comments Gareth.Harper@Onyxnet.CO.Uk is the place to write.');
End.
PLASFAST.PAS
{$X+}
Program Plasma;
Uses Plas,CRT;
Var
XAnswer,YAnswer,
ToAdd,Position,
X,Y : Integer;
SinArray : Array [0..512] Of Integer;
XValues : Array [0..319] Of Integer;
{This is the array which is gonna store the x values}
Begin
WriteLN('This program will only do one thing :(.');
WriteLN('It will our plasma in realtime until you press a key.');
WriteLN('Change the numbers have a play thats what its here for.');
WriteLN('This program will be faster though because it uses arrays');
WriteLN('This one uses fixed point and it will be even faster');
WriteLN('This uses all the tricks (well most anyway) that I know and is fastest yet');
ReadKey;
SetMCGA;
{Now to create our sine array}
For X := 0 to 512 Do
Begin
SinArray[X] := Trunc(Sin(Rad(X)/512*360)*1024);
End;
ToAdd := 0;
While Not Keypressed Do
Begin
Position := 0;
ToAdd := ToAdd + 1;
{Here we add our small amount to our dynamic variable}
For X := 0 to 319 Do
XValues[X] := 20 * SinArray[((X Shl 2) + (ToAdd )) And 511]+
30 * SinArray[((X ) + (ToAdd Shl 2)) And 511]+
50 * SinArray[((X Shr 2) + (ToAdd Shr 2)) And 511];
{This is where we create that list of x values}
For Y := 0 to 199 Do
Begin
YAnswer := 40 * SinArray[((Y Shl 3) + (ToAdd )) And 511]+
40 * SinArray[((Y ) + (ToAdd Shl 3)) And 511]+
20 * SinArray[((Y ) + (ToAdd Shr 3)) And 511];
{This is our second answer for our Y coordinate of the square}
For X := 0 to 319 Do
Begin
{This is our first answer for our X coordinate of the square}
XAnswer := XValues[X];
Mem[VGA:Position] := (XAnswer+Yanswer) Shr 10;
Position := Position + 1;
End;
End;
End;
SetText;
WriteLN('I''m gonna say this again because it''s evident in this program a lot');
WriteLN('This code is sloooow do not write to me saying that, I know.');
WriteLN('Write to me if you code any plasma''s though and I''ll send you some');
WriteLN('comments Gareth.Harper@Onyxnet.CO.Uk is the place to write.');
End.