

// The Earthquake Applet
// ------------------------------------------------------------
// This program is written by Hinkuok Kong (hkong@cs.ucla.edu).
// Copyright (C) 1995 Hinkuok Kong
// This program is free software; you can redistribute it and/or modify
// it.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

import java.applet.*;
import java.awt.*;
import java.awt.image.*;

// quakeControl
// ------------------------------------------------------------
// A class to control the scale of the earthquake
// ------------------------------------------------------------
class quakeControl {
  
  private float scale = (float) 0.0;
  
  // report the current scale and cause a wait based on scale
  public synchronized float rock() {
    if (scale == 0.0) {
      try {
        wait();
      } catch (InterruptedException e) {
      }
    } 
    else {
      try {
        wait((long)(500/scale));
      } catch (InterruptedException e) {
      }
      scale *= 0.985;
      if (scale < 1)
        scale = (float) 0.0;
    }
    return scale;
  }

  // intensify the quake
  public synchronized void worsen() {
    scale += 2.5;
    notify();
  }

  // stop the earthquake
  public void reset() {
    scale = (float) 0.0;
  }

}

// earthquake
// ------------------------------------------------------------
// The earthquake applet class
// ------------------------------------------------------------
public class earthquake extends Applet implements Runnable {

  // tracker -- for synchronous image loading
  private MediaTracker tracker;

  // quake -- the earthquake control object
  private quakeControl quake;

  // building -- the image of the target building

  private Image building;

  // shaker -- the actual thread to shake the building

  private Thread shaker;

  // scale -- last reported scale

  private float scale;

  // iw -- image width
  // ih -- image height
  // xdiff -- width difference between image and viewing area
  // ydiff -- height difference between image and viewing area
  // xoff -- current x offset of the image 
  // yoff -- current y offset of the image

  private int iw, ih, xdiff, ydiff, xoff, yoff;

  // pieces_x -- x coordinate of small image pieces
  // pieces_y -- y coordinate of small image pieces
  // piece_width -- width of each small image piece
  // piece_height -- height of each small image piece 

  private int pieces_x[], pieces_y[], piece_width, piece_height;

  // canvas -- drawing canvas
  // pieces -- small image pieces

  private Image canvas, pieces[];

  // canvasGC -- graphics context of canvas

  private Graphics canvasGC;

  // up -- true if the building image is moving upward
  // collapsed -- true if building has totally collapsed
  // collapsing -- true if building is collapsing

  private boolean up, collapsed, collapsing;


  public void init() {

    // allocating variables and setting default values
    tracker = new MediaTracker(this);
    quake = new quakeControl();
    String at = getParameter("img");
    String image = (at != null) ? at : "royce.gif";
    building = getImage(getCodeBase(), image);
    tracker.addImage(building, 0);
    building.getWidth(this);
    building.getHeight(this);
    pieces_x = new int[64];
    pieces_y = new int[64];
    pieces = new Image[64];
    collapsed = false;
    collapsing = false;
  }

  public boolean imageUpdate(Image img, int flags, 
			     int x, int y, 
			     int width, int height) {

    // update various variables after the image size is known
    if (img == building) {
      if ((flags & WIDTH) != 0)
	iw = width;
      if ((flags & HEIGHT) != 0)
	ih = height;
      if ((((flags & WIDTH) != 0) || ((flags & HEIGHT) != 0)) && 
	  (iw > 0) && (ih > 0) && 
	  (ih > size().height) && (iw > size().width)) {

	// calculate width and height for small image
	piece_width = iw / 8;
	piece_height = ih / 8;

	// set starting coordinates for each individual small image
	for (int i = 0; i < 8; i++)
	  for (int j = 0; j < 8; j++) {
	    pieces_x[i * 8 + j] = iw * j / 8;
	    pieces_y[i * 8 + j] = iw * i / 8;
	  }

	xdiff = iw - size().width;
	ydiff = ih - size().height;
	xoff = - (xdiff / 2);
	yoff = - (ydiff / 2);
	up = false;
      }
    }
    return super.imageUpdate(img, flags, x, y, width, height);
  }

  public void start() {
    try {
      showStatus("earthquake: loading and chopping image ...");
      if (canvas == null) {
	tracker.waitForID(0);
	canvas = createImage(building.getWidth(this), 
			     building.getHeight(this));
	tracker.addImage(canvas, 2);
	canvasGC = canvas.getGraphics();
      }
      canvasGC.drawImage(building, 0, 0, this);
      if (pieces[0] == null) {
	tracker.waitForID(0);

	// chop the building image into pieces
	for (int i = 0; i < 64; i++) {
	  CropImageFilter cropFilter = new CropImageFilter(pieces_x[i],
							   pieces_y[i],
							   piece_width,
							   piece_height);
	  pieces[i] = createImage(new FilteredImageSource(building.getSource(),
							  cropFilter));
	  tracker.addImage(pieces[i], 1);
	}
	tracker.waitForID(1);
      showStatus("");
      }
    } catch (InterruptedException e) {
    }

    if (shaker == null) {
      scale = (float) 0.0;
      shaker = new Thread(this);
      shaker.start();
    }
  }

  public void stop() {
    if (shaker != null) {
      shaker.stop();
      shaker = null;
    }
    quake.reset();
  }

  public void run() {

    while ((iw == -1) || (ih == -1) || ((xdiff > 0) && (ydiff > 0))) {

      // get the current scale
      scale = quake.rock();

      if (collapsed == true) {
	canvasGC.drawImage(building, 0, 0, this);
	collapsed = false;
      }

      if (scale < 10) {

	// when scale is less than 10, keeping rocking

	if ((xdiff > 0) && (ydiff > 0)) {
	  if (up)
	    yoff = - (ydiff + yoff);
	  else {
	    xoff += -2 + Math.random() * 5.0;
	    if (xoff > 0)
	      xoff = 0;
	    if (xoff < -xdiff)
	      xoff = -xdiff;
	    yoff = (int) ((1.0 - Math.pow(Math.random(), scale)) * 
			  (ydiff / 2)) - 
			    (ydiff / 2);
	  }
	  up = !up;
	}
	repaint();
      }
      else {

	// otherwise, start the collapsing process

	collapsing = true;
	xoff = - (xdiff / 2);
	yoff = - (ydiff / 2);
	for (int k = 0; k < 80; k++) {
	  for (int i = 0; i < 64; i++) {
	      pieces_x[i] += -1 + Math.random() * 3;
	      pieces_y[i] += Math.random() * 6 * (ih - pieces_y[i] + 50) / ih;
	      canvasGC.drawImage(pieces[i],
			  pieces_x[i],
			  pieces_y[i],
			  this);
	    }
	  repaint();
	  try {
	    Thread.sleep(150);
	  } catch (InterruptedException e) {
	  }
	}
	for (int i = 0; i < 8; i++)
	  for (int j = 0; j < 8; j++) {
	    pieces_x[i * 8 + j] = iw * j / 8;
	    pieces_y[i * 8 + j] = iw * i / 8;
	  }
	quake.reset();
	collapsed = true;
	collapsing = false;
      }
    }
  }

  public void paint(Graphics g) {
    if (canvas != null)
      g.drawImage(canvas, xoff, yoff, this);
  }
  
  public void update(Graphics g) {
    paint(g);
  }

  public boolean mouseDown(Event e, int x, int y) {
    if (!collapsing)
      quake.worsen();
    return true;
  }

}





