Your browser does not support the applet tag

Pool

Code sample

Here's the complete source code for Pool
The switchable image files are passed in as applet parameters. So is the "pool mask", a simple black and white image mask that defines the action zone.

  <applet code="Pool.class"  name="pool" mayscript>
  <param name="totalimages"  value="3">
  <param name="image0"       value="a.jpg">
  <param name="image1"       value="b.jpg">
  <param name="image2"       value="c.jpg">
  <param name="mask"         value="poolmask.gif">
  <param name="refresh"      value="12">
  </applet>
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.PixelGrabber;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import netscape.javascript.*;

public class Pool extends Applet implements MouseListener, MouseMotionListener, Runnable
{
  private JSObject window;                // allows the applet to call JavaScript
  private Thread runner = null;           // animation thread
  private MemoryImageSource source = null;// animated image source
  private Image backBuffer = null;        // animated image
  private Image[] image = null;           // array of various images
  private Image waterMask  = null;        // black and white mask
  private Graphics backGraphics = null;   // offscreen graphics object
  private int[][][] heightMap;            // water surface (2 pages)
  private int[][] pixels;                 // image pixels (2 images)
  private int[] screen;                   // working area
  private int[][] mask;                   // circular mask (constrains water effect)
  private int[] line;                     // precalculated line offsets

  private int wScreen, hScreen, space;
  private int radius, height, density;
  private int refresh;
  private int page = 0;
  private int totalImages = 0;
  private int currentImage = 0;

  public Pool()
  {
    radius = 4;
    height = 200;
    density = 4;
  }

  public void init()
  {
    // get a reference to the JavaScript window
    window = JSObject.getWindow(this);
    wScreen = getSize().width;
    hScreen = getSize().height;

    // allocate the arrays
    String str = getParameter("totalImages");
    if (str != null) totalImages = Integer.parseInt(str);
    image = new Image[totalImages];
    pixels = new int[totalImages][wScreen * hScreen];
    screen = new int[wScreen * hScreen];
    heightMap = new int[2][wScreen][hScreen];
    line = new int[hScreen];

    // initialise the images, and set up the offscreen buffer
    MediaTracker tracker = new MediaTracker(this);
    try
    {
      // read in the supplied images
      for (int i = 0; i < totalImages; i++)
        image[i] = getImage(getDocumentBase(), getParameter("image" + i, ""));

      //  read in the water mask
      waterMask = getImage(getDocumentBase(), getParameter("mask", ""));

      // wait until all the image data has been read
      for (int i = 0; i < totalImages; i++)
        tracker.addImage(image[i], 0);

      tracker.addImage(waterMask, 1);
      tracker.waitForAll();

      // create the water mask
      if (waterMask != null) createMaskFromImage();
      else createMask();

      // copy the image data into the pixel arrays
      for (int i = 0; i < totalImages; i++)
      {
        PixelGrabber pg = new PixelGrabber(image[i], 0, 0, wScreen, hScreen, 
                                           pixels[i], 0, wScreen);
        pg.grabPixels();
      }

      // initialise the working area with pixels[0]
      System.arraycopy(pixels[0], 0, screen, 0, wScreen*hScreen);
      source = new MemoryImageSource(wScreen, hScreen, screen, 0, wScreen);
      source.setAnimated(true);
      backBuffer = createImage(source);

      // use image[0] as an offscreen image buffer
      image[0] = createImage(wScreen, hScreen);
      backGraphics = image[0].getGraphics();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
  
    addMouseMotionListener(this);
    addMouseListener(this);
  
    // store the offsets for every line
    for (int y = 0; y < hScreen; y++) line[y] = y * wScreen;
    space = wScreen * hScreen - 1;

    // get the refresh rate (in milliseconds)
    try { refresh = Integer.parseInt(getParameter("refresh")); }
    catch (Exception e) { refresh = 5; }
  }
      
  /**
  * Create a centered, circular water mask
  */
  private void createMask()
  {
    // get the desired mask radius
    int maskRadius = 0;
    String str = getParameter("radius");
    if (str == null)
      maskRadius = Math.min(wScreen, hScreen) / 2;
    else
      maskRadius = Integer.parseInt(str);

    // generate the mask array
    mask = new int[wScreen][hScreen];
    for (int y = 0; y < hScreen; y++)
    {
      for (int x = 0; x < wScreen; x++)
      {
        // - find the image center
        int centerX = wScreen/2;
        int centerY = hScreen/2;

        // - convert x and y to cartesian coordinates
        int cX = x - centerX;
        int cY = centerY - y;

        // - find the radius of the point
        int r = (int)Math.sqrt(cX*cX + cY*cY);

        // - mark up the map
        if (r < maskRadius)
            mask[x][y] = 1;
        else
            mask[x][y] = 0;
      }
    }
  }

  /**
  * Create a water mask based on the supplied image
  */
  private void createMaskFromImage()
  {
    mask = new int[wScreen][hScreen];
    int[] maskpixels = new int[wScreen * hScreen];

    // populate the mask array
    try
    {
      PixelGrabber pg = new PixelGrabber(waterMask, 0, 0, wScreen, hScreen, 
                                         maskpixels, 0, wScreen);
      pg.grabPixels();

      // copy the image pixels into the mask array
      for (int y = 0; y < hScreen; y++)
      {
        int offset = y * wScreen;
        for (int x = 0; x < wScreen; x++)
        {
          // int alpha = (maskpixels[offset + x] >> 24) & 0xff;
          int red  = (maskpixels[offset + x] >> 16) & 0xff;
          int green = (maskpixels[offset + x] >>  8) & 0xff;
          int blue  = (maskpixels[offset + x]      ) & 0xff;

          // black maps to 1 and white maps to 0
          if (red == 255 && green == 255 && blue == 255)
            mask[x][y] = 0;
          else
            mask[x][y] = 1;
        }
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public String getParameter(String key, String def)
  {
    return (getParameter(key) != null ? getParameter(key) : def);
  }

  /**
  * Raise the heightmap at position (cx, cy)
  */
  public void makeTurbulence(int cx, int cy)
  {
    // keep turbulence within the circle mask
    if (mask[cx][cy] == 0) return;

    // precalculate the square of the radius 
    // (note: radius = "drop" radius)
    int r2 = radius * radius;

    // find the bounds of the drop
    int left =  cx < radius  ? -cx : -radius;
    int right = cx > wScreen - radius - 2  ? wScreen - cx - 2 : radius;
    int top =  cy < radius  ? -cy : -radius ;
    int bottom = cy > hScreen - radius - 2  ? hScreen - cy - 2 : radius;

    // raise the height map at the drop area
    for (int x = left; x < right; x++)
    {
      int x2 = x*x;
      for (int y = top; y < bottom; y++)
      {
        int squarex = (x - cx) * (x - cx);
        int squarey = (y - cy) * (y - cy);
        double dist = Math.sqrt(squarex + squarey);
        if ((x2 + (y * y)) < r2)
          heightMap[page^1][cx + x][cy + y] += height;
      }
    }
  }

  public void mouseMoved(MouseEvent me)
  {
    makeTurbulence(me.getX(), me.getY());
  }

  public void mousePressed(MouseEvent me)
  {
    // calls the Javascript function "pressButton()"
    String[] args = {""};
    window.call("pressButton", args);
  }

  public void mouseReleased(MouseEvent me)
  {
    // calls the Javascript function "releaseButton()"
    String[] args = {""};
    window.call("releaseButton", args);
  }

  /**
  * Drip a drop into the center of the pool
  */
  public void drop()
  {
    // called by the Javascript function "makeDrop()"
    makeTurbulence(wScreen/2, hScreen/2);
  }

  /**
  * Rotate the pool image
  */
  public void nextPoolImage()
  {
    // called by the Javascript function "nextPoolImage()"
    currentImage++;
    currentImage %= totalImages;
    setImage(currentImage);
  }

  /**
  * Change the current pool image
  */
  public void setPoolImage(int imageNum)
  {
    // called by the Javascript function "setPoolImage()"
    currentImage = imageNum;
    setImage(imageNum);
  }

  private void setImage(int image)
  {
    // copy the image's pixels into the screen array
    System.arraycopy(pixels[image], 0, screen, 0, wScreen * hScreen);
  }

  public void mouseClicked(MouseEvent me) {}
  public void mouseExited(MouseEvent me)  {}
  public void mouseEntered(MouseEvent me) {}
  public void mouseDragged(MouseEvent me) {}

  public void paint(Graphics g)
  {
    // paint the offscreen image to the screen
    if (backBuffer != null)
      g.drawImage(image[0], 0, 0, null);
  }

  public void update(Graphics g)
  {
    // refresh the image source
    source.newPixels(0, 0, wScreen, hScreen);

    // and paint it onto the offscreen image (image[0])
    backGraphics.drawImage(backBuffer, 0, 0, null);
    paint(g);
  }

  public void start()
  {
    // start up an animation thread
    if (runner == null)
    {
      runner = new Thread(this, "Pool");
      runner.setPriority(Thread.MIN_PRIORITY);
      runner.start();
    }
  }
  
  public void stop()
  {
    // stop the animation thread
    runner = null;
  }

  public void run()
  {
    while (runner != null)
    {
      waterCalc();    // update the heightmap
      waterPaint();   // paint the working area
      repaint();
      page ^= 1;      // switch the heightmap page

      // take a quick nap
      try
      {
        Thread.currentThread().sleep(refresh);
      }
      catch(InterruptedException e)
      {
        e.printStackTrace();
      }
    }
  }

  /**
  * Update the heightmap with the latest ripples
  */
  private void waterCalc()
  {
    for (int x = 0; x < wScreen; x++)
    {
      for (int y = 0; y < hScreen; y++)
      {
        // do the water filter
        int n = y-1 < 0 ? 0 : y-1;
        int s = y+1 > (hScreen)-1 ? (hScreen)-1 : y+1;
        int e = x+1 > (wScreen)-1 ? (wScreen)-1 : x+1;
        int w = x-1 < 0 ? 0 : x-1;

        int value = ((heightMap[page][w][n]
                    + heightMap[page][x][n]
                    + heightMap[page][e][n]
                    + heightMap[page][w][y]
                    + heightMap[page][e][y]
                    + heightMap[page][w][s]
                    + heightMap[page][x][s]
                    + heightMap[page][e][s]) >> 2)
                    - heightMap[page^1][x][y];

        heightMap[page^1][x][y] = value - (value >> density);
      }
    }
  }

  /**
  * Update the screen with the latest ripples
  */
  private void waterPaint()
  {
    for (int y = 0; y < hScreen - 1; y++)
    {
      for (int x = 0; x < wScreen - 1; x++)
      {
        // don't paint pixels outside the circle mask
        if (mask[x][y] == 0) continue;

        // use deltas to distort the underlying image
        int deltax = heightMap[page][x][y] - heightMap[page][(x)+1][y];
        int deltay = heightMap[page][x][y] - heightMap[page][x][(y)+1];
        int offsetx = (deltax>>4) + x;
        int offsety = (deltay>>4) + y;
        int offset = (offsety*wScreen) + offsetx;
        offset = offset < 0 ? 0 : offset > space ? space : offset;

        // catch the light
        int pixel = pixels[currentImage][offset];
        int red   = (pixel >> 16) & 0xff;
        int green = (pixel >>  8) & 0xff;
        int blue  = (pixel  ) & 0xff;
        red += deltax>>1;
        green += deltax>>1;
        blue += deltax>>1;
        red = red > 255 ? 255 : red < 0 ? 0 : red;
        green = green > 255 ? 255 : green < 0 ? 0 : green;
        blue = blue > 255 ? 255 : blue < 0 ? 0 : blue;

        // update the image source
        screen[line[y]+x] = 0xff000000 | (red << 16) | (green << 8) | blue;
      }
    }
  }
}