Monkey Fighter Monkey Fighter
Demo Help Help Search Search Shop Shop
confetti

The Confetti applet is a visual effects demo that is similar to confetti floating through air. If the mouse is off the applet, the confetti is distributed randomly. When the mouse is on the applet, the confetti follows the mouse spraying forth like a fountain. Additionally, the confetti inherits the vertical and horizontal momentum of the mouse creating the illusion of being flung from the mouse location.


The Algorithm
The algorithm is fairly basic. Each peice of confetti is starts with an initial X and Y velocity. Then , the X and Y velocity of the mouse movement is added to the initial velocity. Once started, the confetti continues movement with its initial velocity. Gravity is simulated by adding a constant positve Y value to the Y velocity each cycle. This continues until the bottom or side of the applet is reach. When this happens, the confetti starts all over. The confetti is allowed to extend past the top edge because, unlike the other three edges, it may eventually come back into the visible area.


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.awt.event.*;
import java.applet.*;
import java.awt.image.*;

Here are the imports. java.awt contains the Graphics class and event model. java.awt.event is needed for event model, too. java.applet is needed, well, because it is an applet after all! And, java.awt.image contains the BufferedImage and DataBuffer classes that are needed for fast graphics.


public class Confetti extends Applet implements Runnable

Confetti.java is the name of the source file. So, this file needs to have a public class named Confetti. Since I want it to be an applet, I must extend Applet. Finally, this class implements Runnable. Applets that run continuously, instead of waiting on events, must run in a thread and implementing the Runnable interface is an easy way create a thread.


{ boolean stop   = false;
  int     Stars  = 2500;
  int[]   StarC  = new int[Stars+1];
  float[] StarX  = new float[Stars+1];
  float[] StarY  = new float[Stars+1];
  float[] StarXA = new float[Stars+1];
  float[] StarYA = new float[Stars+1];

  float MouseX =0, MouseY =0;
  float MouseCX=0, MouseCY=0;
  float MouseXA=0, MouseYA=0;

  int W, H;

  BufferedImage  biScreen;
  DataBuffer db;
  Thread tConfetti;

This is all the variables that are global to this module. I saved some typing but not specifying the type access priviledges. So, they all default to protected. The first variable, stop, indicates whether to stop the main loop of this thread or not. Stars is the number of peices of confetti (let's call them stars from now on) that are processed. If your computer is super fast, try changing this to a super big number. StarC is the color of the star. StarX and StarY are the coordinate location. StarXA and StarYA are the speed of the star on the X/Y axis. MouseX and MouseY virtual mouse coordinates and MouseCX and MouseCY are the actual coordinates of the mouse. Virtual coordinates are used we want the effect to be smooth. If the users moves the mouse fast, the effect would be choppy. By using virtual mouse coordinates, the mouse location can smoothly chases the actual location. Thus, MouseXA and MouseYA is the speed at which the virtual mouse location is moving. W and H are the width and height of the applet. biScreen is the image that all the drawing is performed on. db is the buffer that stores the pixel data for the screen image. And finally, tConfetti stores the reference to the current thread.


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

Confetti implements the runnable interface. To start a Runnable class, there must be a start method. This is where the thread is started and I used the standard textbook technique to start the thread.


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

Notice that stop is set to true which causes the main loop to terminate. That is the preferred method now because of problems with Thread.stop(). Here is what Sun has to say about Thread.Stop(): Stopping a thread with Thread.stop() causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running.


  public void init()
  { this.enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);

    W = this.getSize().width;
    H = this.getSize().height;

    biScreen = new BufferedImage(W, H, 1);
    db = biScreen.getRaster().getDataBuffer();

    for(int x=0; x<W; x++)
      for(int y=0; y<H; y++)
        db.setElem(x+(y*W), 0xFFFFFF);

    for(int t=0; t<Stars; t++)
      newStar(t);
  }

For applets, Init is called before run and is the intended location for initializing program variables. Thus, the first line registers the mouse motion event for further processing. The applet's width and height are then acquired. The BufferedImage constructor is used to create a new image with the applets width and height as its size. The 1 indicates that the image will be of type TYPE_INT_RGB. Interestingly, If you changed it to a 2, the image would be TYPE_INT_ARGB. You would think that having an 8 bit alpha channel would make for great Java demos. However, Java currently only supports hardware acceleration for 1 bit alpha channels. So, using TYPE_INT_ARGB is painfully slow. Anyhow, db is assigned the reference to biScreen's databuffer. Next is a nested for loop that clears the screen. Notice that wrote the pixel data directly to the data buffer with the db.setElem. Lastly, we initialize all the stars.


  public void run()
  { while(!stop)
    { MouseXA = (MouseCX-MouseX)/5.0f;
      MouseYA = (MouseCY-MouseY)/5.0f;
      MouseX +=MouseXA*2.5f;
      MouseY +=MouseYA*2.5f;

run is the main routine and it loop until stop is true. The amount of mouse movement is computed and scaled. Then the virtual mouse location is updated and scaled accordingly. You might think that I should have computed the scaling of MouseX/Y with MouseXA/YA. However, MouseXA/YA is used in newStar to add mouse momentum to the stars movement. And, the virtual mouse speed and stars initial momentum needs to be different.


      for(int t=0; t<Stars; t++)
      { StarX[t]+=StarXA[t];
        StarY[t]+=StarYA[t];
        StarYA[t]+=(H/100000.0f);
        if((StarY[t]+3>H)||(StarX[t]+3>W)||(StarX[t]-2<0))
          newStar(t);

Here, the star's movement is updated and checked against the left, right and bottom screen edges. If the star lies outside an edge, it is recycled. Also note that the star's YA is always incremented by 1/100000th of the screen height. That causes the stars to slowly drift down the screen and at a rate proportional to the size of the applet.


        if(StarY[t]-1>0)
        { db.setElem((int)StarX[t] + ((int)(StarY[t]+1)*W), StarC[t]);
          db.setElem((int)StarX[t] + ((int)(StarY[t]-1)*W), StarC[t]);
          db.setElem((int)StarX[t] + ((int)StarY[t]*W)-1, StarC[t]);
          db.setElem((int)StarX[t] + ((int)StarY[t]*W)+1, StarC[t]);   }
      }

Now that the star locations are updated, they may be drawn to the buffer. Each star is made of four pixels that looks something like a hollow diamond. We want to allow the stars to be flung above the top of the screen. So, any star above the top edge won't be drawn until it comes back down. Those stars that are not clipped are drawn by writting to the databuffer. setElem(offset, color) is the method where offset is the offset of the pixel in the buffer and color is the RGB value represented by an int. Remember, the screen was TYPE_INT_RGB? Well, that means the databuffer will have a single band where each consecutive int represent the color for each consecutive pixel. Simple, huh?


      repaint(0, 0, W, H);

      try
      { Thread.sleep(10); }
      catch (InterruptedException e)
      {;}

Now is a good time to paint the screen and sleep for a bit. Sleeping is a good idea for applets with event handles. It prevents starving the event handler.


      for(int t=0; t<Stars; t++)
        if(StarY[t]-1>0)
        { db.setElem((int)StarX[t] + ((int)(StarY[t]+1)*W), 0xFFFFFF);
          db.setElem((int)StarX[t] + ((int)(StarY[t]-1)*W), 0xFFFFFF);
          db.setElem((int)StarX[t] + ((int)StarY[t]*W)-1,   0xFFFFFF);
          db.setElem((int)StarX[t] + ((int)StarY[t]*W)+1,   0xFFFFFF);   }
    }
  }

Before looping back to the beginning, the stars need to be erased. This is easy to do. Just set the stars to the background color. Now, you might be thinking, "why not just clear the background". Well, I have only 2500 stars, which means 10,000 pixel writes. If my applet was a modest 640x400, I would need to write 307,200 pixels!


  private void newStar(int t)
  { if((MouseX<10)||(MouseX>W-10)||(MouseY>H-10))
    { StarX[t]=(int)(Math.random()*(W-10))+5;
      StarY[t]=(int)(Math.random()*(H-10))+5; }
    else
    { StarX[t]  =(int)MouseX;
      StarY[t]  =(int)MouseY; }

    StarXA[t] = (float)Math.random()*(W/250)-(W/500) + MouseXA;
    StarYA[t] = (float)Math.random()*(H/100)-(H/150) + MouseYA;
    StarC[t]  = ((int)(Math.random()*250)<<16) |
                ((int)(Math.random()*250)<<8)  |
                 (int)(Math.random()*250);
  }

This method resets stars. If the mouse is located 5 pixels within the applet, start the star from the virtual mouse location. Otherwise, pick a point randomly. Next, set it moving in a random direction and add the momentum from the mouse movement. Finally, pick a random color for the star. Notice that I generate the red, green and blue separately then combine the bits logically to form an RGB int value.


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

The update method is overridden to prevent the unnecessary clearing of the background.


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

We drew the biScreen image in the run method. So, all we need to do in paint is draw it in this Graphics context.


  public void processMouseMotionEvent(MouseEvent e)
  { switch(e.getID())
    { case MouseEvent.MOUSE_DRAGGED:
      case MouseEvent.MOUSE_MOVED:   MouseCX = (float)e.getX();
                                     MouseCY = (float)e.getY();
} } }

Here is the mouse event handler. For this applet, all I needed was the code to update MouseCX/CY. However, I thought that someone might want to see how to distinquish between a dragged mouse and a moved mouse. In this case, draggin and moving run the same code. Thus, the whole switch statement has no effect but to serve as an example.


Applets

Plasma
Confetti

Tutorials

Plasma
Confetti

The Code

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