Monkey Fighter Monkey Fighter
Demo Help Help Search Search Shop Shop
plasma

Plasma Rewritten
This is a remake of my Plasma applet. I was not happy with the original applet and I blamed it on the algorithm I developed. The truth is that the code was crap. I was even embarrassed to post it! I used all those high level methods and objects that are taught in every Java tutorial. And, they suck! So, I downloaded the Java 1.4 docs (all 34 megs over modem) and waded through the Java API's. Now, I have a better idea how to write good Java code and I don't mind sharing my knowledge.


The Algorithm
The algorithm consists of six spots (balls) that bounce around the view area. Of the six spots, two modify the red tones, two modify the blue, and two the green. One of the red spots increases the redness around it and one decreases it. The same is true for the blue and green spots. The closer to the spot the greater the change.


Let's Look at Code
Don't worry if my description of the algorithm doesn't make sense. All the secrets are in the code and here it is...


import java.awt.*;
import java.applet.*;
import java.util.Random;
import java.awt.image.*;

These are the packages/objects that are required for this applet. In the first two lines, I import all the objects from the java.awt and java.applet packages. In the next, I request the Random class although I prefer to use Math.random(), now. Last but not least, I import java.awt.image because I need the classes BufferImage and DataBuffer.


public class Plasm extends Applet implements Runnable{

This applet is a class called Plasm (I was to lazy too rename to Plasma) which needs public access. Since it is an applet it must extend applet. And, since it runs as a thread, it implements the Runnable interface.


  boolean stop;
  int GridMX, GridMY;
  int x, y;
  double MaxC, cScale;
  double RX2, RY2, GX2, GY2, BX2, BY2, MX2, MY2;
  double RXA2, RYA2, GXA2, GYA2, BXA2, BYA2;
  double RX1, RY1, GX1, GY1, BX1, BY1, MX1, MY1;
  double RXA1, RYA1, GXA1, GYA1, BXA1, BYA1;
  double[][] GridR;
  double[][] GridG;
  double[][] GridB;

These are the declarations for the primitive types. stop is a flag that indicates whether or not the thread should terminate. GridMX and GridMY are the maximum X and Y dimensions for the plasma effect. It will be set to the equal the size of the applet. x and y are used temporarily in for loops; by instantiating them here, the effect runs about a nanosecond faster. MaxC is the maximum distance that color fade to. It will equal the distance from the top left corner to the lower right corner. cScale adjusts the intensity of color change. The next four lines are all variables which track the moving spots. The first letter refers to red, green or blue. The second, is the X coordinate, Y coordinate, or speed (acceleration). The last number is a 1 for color brightening spots and 2 for color dimming spots. Gridx is the red, green or blue color matrix which pretty much resembles the raster. In fact, I used the raster instead of this memory hungry double double array but I didn't get the precision that I wanted and it is actually faster to read values from these arrays than to use the native methods!


  Random         rand = new Random();
  BufferedImage  biPlasma;
  Thread         animator;

These are the object declarations. rand is used to get random number so that the applet isn't exactly the same everytime. biPlasma stores the image and provides low level methods for image manipulation - Specifically, pixel writting, but I have found it so much more powerful than just that! animator is a reference to this applet's thread.


  public void init()
  { GridMX=this.getSize().width;
    GridMY=this.getSize().height;

The first thing to do is find the applets size. getSize() is the method to use. It replaces the deprecated method size(). This code stores the applets width in GridMX and its height in GridMY.


    biPlasma = new BufferedImage(GridMX, GridMY, 1);
    dbPlasma = biPlasma.getRaster().getDataBuffer();

There are several ways to instantiate a buffered image. I prefer to use the constructors. Here, I created a buffered image with the same dimensions as the applet's size. The third parameter (1) is the image type. I want a 24 bit RGB display with no alpha band. So, I used 1 here. I know that I should use the built-in constant, TYPE_INT_RGB, but I didn't. Anyhow, 1 is the value for TYPE_INT_RGB (I looked it up). BufferedImage has a method called SetRGB which can be used to set the colors of individual pixels. However, it is more efficient to write directly to the data buffer. So, dbPlasma is assigned the reference to the BufferedImage's DataBuffer.


    MaxC =Math.sqrt(((GridMX-1.0)*(GridMX-1.0))+((GridMY-1.0)*(GridMY-1.0)));
    cScale=(MaxC/100.0);

This is where MaxC is initialized with the diagonal size of the applet and cScale is 1 percent of that.


    GridR = new double[GridMX+1][GridMY+1];
    GridG = new double[GridMX+1][GridMY+1];
    GridB = new double[GridMX+1][GridMY+1];

The color matrices are initialized equal to the applets size. You do know that C and C based languages arrays start at 0. So, to address coordinates (0,0) through (mx,my), we need to allocate mx+1 and my+1 memory units.


    RX1=rand.nextInt(GridMX);
    RY1=rand.nextInt(GridMY);
    GX1=rand.nextInt(GridMX);
    GY1=rand.nextInt(GridMY);
    BX1=rand.nextInt(GridMX);
    BY1=rand.nextInt(GridMY);

    RX2=rand.nextInt(GridMX);
    RY2=rand.nextInt(GridMY);
    GX2=rand.nextInt(GridMX);
    GY2=rand.nextInt(GridMY);
    BX2=rand.nextInt(GridMX);
    BY2=rand.nextInt(GridMY);

The spots start life at any random point in the matrix.


    double xr=GridMX/20.0;
    double yr=GridMY/20.0;

To ensure that large applets behave similar to small applets, all relevant variables need to be scaled accordingly. Here, xr and yr are initialized to allow scaling of the speed at which the spots move.


    RXA1=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    RYA1=rand.nextInt((int)(yr*10))/yr-(yr/2.0);
    GXA1=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    GYA1=rand.nextInt((int)(yr*10))/yr-(yr/2.0);
    BXA1=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    BYA1=rand.nextInt((int)(yr*10))/yr-(yr/2.0);

    RXA2=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    RYA2=rand.nextInt((int)(yr*10))/yr-(yr/2.0);
    GXA2=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    GYA2=rand.nextInt((int)(yr*10))/yr-(yr/2.0);
    BXA2=rand.nextInt((int)(xr*10))/xr-(xr/2.0);
    BYA2=rand.nextInt((int)(yr*10))/yr-(yr/2.0);

The speed of the spots are set!


    for(x=0; x<GridMX; x++)
    { for(y=0; y<GridMY; y++)
      { GridR[x][y]=(int)(((float)x/(float)GridMX)*(float)255);
        GridG[x][y]=(int)(((float)y/(float)GridMY)*(float)255);
        GridB[x][y]=127; } } }

Initialize the color matrix with a simple but pretty plasma like image.


  public void paint(Graphics g)
  { g.drawImage(biPlasma, 0,0, this); }

I learned the hard way that paint should be as simple as possible. In fact, I recommend drawing only one buffered image and nothing else during this routine. Not only does this execute quickly but it implements double buffering, too! In case you are wondering, my code experimenting led me to believe that paint is an asyncronous routine in Java but I can't confirm it. Anyhow, if paint() ain't fast, your applet will suffer!


  public void update(Graphics g)
  { paint(g); }

Don't clear the screen (as update would do by default), just call paint() directly.


  public double GetShade(double a, double b)
  { return (1.0-(Math.sqrt(a*a + b*b) /MaxC))*cScale; }

Here is where we find the distance from the current point in the matrix to any of the spots. a is the difference from x1 to x2 and b is the difference from y1 to x2. This routine require those values precomputed to save a couple nanoseconds. Anyhow, since MaxC is the diagonal distance of applet and is divided by Pythagorean's distance finding formula, 1 - that whole enchilada produces a value between 0 and 1 where 1 represent a very short distance. Now, just multiple that by cScale so that we can actually see the colors change at a reasonable rate!


  public void start()
  { if(animator == null)
    { animator=new Thread(this);
      animator.start(); } }

This starts the thread.


  public void stop()
  { stop=true;
    animator = null;  }

Thread.stop() has been deprecated. So, I use a flag instead. However, I don't know if this will get all the garbage collected. When I was developing this applet, I noticed some odd behavior after running this applet one zillion times. However, that could be Windows, JCreator, Mozilla or something else at fault...


  public void run()
  { while(!stop)
    { if(((RX1+RXA1)>=GridMX)||((RX1+RXA1)>=0)) RXA1=-RXA1;
      if(((RY1+RYA1)>=GridMY)||((RY1+RYA1)>=0)) RYA1=-RYA1;
      if(((GX1+GXA1)>=GridMX)||((GX1+GXA1)>=0)) GXA1=-GXA1;
      if(((GY1+GYA1)>=GridMY)||((GY1+GYA1)>=0)) GYA1=-GYA1;
      if(((BX1+BXA1)>=GridMX)||((BX1+BXA1)>=0)) BXA1=-BXA1;
      if(((BY1+BYA1)>=GridMY)||((BY1+BYA1)>=0)) BYA1=-BYA1;

      if(((RX2+RXA2)>=GridMX)||((RX2+RXA2)>=0)) RXA2=-RXA2;
      if(((RY2+RYA2)>=GridMY)||((RY2+RYA2)>=0)) RYA2=-RYA2;
      if(((GX2+GXA2)>=GridMX)||((GX2+GXA2)>=0)) GXA2=-GXA2;
      if(((GY2+GYA2)>=GridMY)||((GY2+GYA2)>=0)) GYA2=-GYA2;
      if(((BX2+BXA2)>=GridMX)||((BX2+BXA2)>=0)) BXA2=-BXA2;
      if(((BY2+BYA2)>=GridMY)||((BY2+BYA2)>=0)) BYA2=-BYA2;

The rest of the program is looped until something happens to the applet. Anyhow, the spots need to move around the matrix but not out of the matrix. So, we check the bounds and reverse directions if necessary.


      RX1+=RXA1;
      RY1+=RYA1;
      GX1+=GXA1;
      GY1+=GYA1;
      BX1+=BXA1;
      BY1+=BYA1;

      RX2+=RXA2;
      RY2+=RYA2;
      GX2+=GXA2;
      GY2+=GYA2;
      BX2+=BXA2;
      BY2+=BYA2;

We already checked the bounds and know that it is safe to move the spots. So, move them!


      for(x=0; x<GridMX; x++)
      { for(y=0; y<GridMY; y++)
        { GridR[x][y]+=GetShade(x-RX1, y-RY1);
          GridG[x][y]+=GetShade(x-GX1, y-GY1);
          GridB[x][y]+=GetShade(x-BX1, y-BY1);

          GridR[x][y]-=GetShade(x-RX2, y-RY2);
          GridG[x][y]-=GetShade(x-GX2, y-GY2);
          GridB[x][y]-=GetShade(x-BX2, y-BY2);

          if(GridR[x][y]>255) GridR[x][y]=255;
          if(GridG[x][y]>255) GridG[x][y]=255;
          if(GridB[x][y]>255) GridB[x][y]=255;

          if(GridR[x][y]<=0) GridR[x][y]=0;
          if(GridG[x][y]<=0) GridG[x][y]=0;
          if(GridB[x][y]<=0) GridB[x][y]=0;

The first block of code brightens pixels; the second dims pixels. The remaining code prevents color values from exceeding 255 or or preceeding 0.


          dbPlasma.setElem(x + (y*GridMX), ((int)GridR[x][y]<<16) |
                                           ((int)GridG[x][y]<<8) |
                                            (int)GridB[x][y]);
      } }

Now, we update the data buffer with the new color. This dataBuffer stores image data sequentially using only one band. So, each consecutive int in the DataBuffer represent the RGB value for each consecutive pixel. This is the case for BufferedImages of type TYPE_INT_RGB. Other types may be different. setElem() is used to assign the pixel colors. The first parameters is the offset. The second parameter is the color represented as an int. There are 24 color bits per pixel and they are ordered red, green and then blue. So, we need to shift the red bits 16 spots to the left; the green needs to shift 8 and the blue is fine. However, bit shifting a double leaves a really funky number. So, lets cast our doubles to an int first. Now, when the ints are logically combined, we have our colors, RGB, in int form.


      repaint();
} } }

Repaint the entire screen with the updated color matrix then loop until something happens to this applet.


Applets

Plasma
Confetti

Tutorials

Plasma
Confetti

The Code

Plasma.java
Copyright ©2005, Robert Walsh, All Rights reserved.