Game Object Test : Game Object « Game « Java

Java
1. 2D Graphics GUI
2. 3D
3. Advanced Graphics
4. Ant
5. Apache Common
6. Chart
7. Class
8. Collections Data Structure
9. Data Type
10. Database SQL JDBC
11. Design Pattern
12. Development Class
13. EJB3
14. Email
15. Event
16. File Input Output
17. Game
18. Generics
19. GWT
20. Hibernate
21. I18N
22. J2EE
23. J2ME
24. JDK 6
25. JNDI LDAP
26. JPA
27. JSP
28. JSTL
29. Language Basics
30. Network Protocol
31. PDF RTF
32. Reflection
33. Regular Expressions
34. Scripting
35. Security
36. Servlets
37. Spring
38. Swing Components
39. Swing JFC
40. SWT JFace Eclipse
41. Threads
42. Tiny Application
43. Velocity
44. Web Services SOA
45. XML
Java Tutorial
Java Source Code / Java Documentation
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorial
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java » Game » Game ObjectScreenshots 
Game Object Test

       /*
DEVELOPING GAME IN JAVA 

Caracteristiques

Editeur : NEW RIDERS 
Auteur : BRACKEEN 
Parution : 09 2003 
Pages : 972 
Isbn : 1-59273-005-1 
Reliure : Paperback 
Disponibilite : Disponible a la librairie 
*/

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.IndexColorModel;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class GameObjectTest extends GameCore3D {

  public static void main(String[] args) {
    new GameObjectTest().run();
  }

  private static final int NUM_BOTS = 5;

  private static final int NUM_POWER_UPS = 7;

  private static final int GAME_AREA_SIZE = 1500;

  private static final float PLAYER_SPEED = .5f;

  private static final float PLAYER_TURN_SPEED = 0.04f;

  private static final float BULLET_HEIGHT = 75;

  protected GameAction fire = new GameAction("fire",
      GameAction.DETECT_INITAL_PRESS_ONLY);

  private PolygonGroup robotModel;

  private PolygonGroup powerUpModel;

  private PolygonGroup blastModel;

  private GameObjectManager gameObjectManager;

  private TexturedPolygon3D floor;

  public void init() {
    init(LOW_RES_MODES);

    inputManager.mapToKey(fire, KeyEvent.VK_SPACE);
    inputManager.mapToMouse(fire, InputManager.MOUSE_BUTTON_1);
  }

  public void createPolygons() {

    // create floor
    Texture floorTexture = Texture.createTexture("../images/roof1.png",
        true);
    ((ShadedTexturefloorTexture)
        .setDefaultShadeLevel(ShadedTexture.MAX_LEVEL * 4);
    Rectangle3D floorTextureBounds = new Rectangle3D(new Vector3D(000),
        new Vector3D(100)new Vector3D(001), floorTexture
            .getWidth(), floorTexture.getHeight());
    float s = GAME_AREA_SIZE;
    floor = new TexturedPolygon3D(new Vector3D[] { new Vector3D(-s, 0, s),
        new Vector3D(s, 0, s)new Vector3D(s, 0, -s),
        new Vector3D(-s, 0, -s) });
    floor.setTexture(floorTexture, floorTextureBounds);

    // set up the local lights for the model.
    float ambientLightIntensity = .5f;
    List lights = new LinkedList();
    lights.add(new PointLight3D(-100100100.5f, -1));
    lights.add(new PointLight3D(1001000.5f, -1));

    // load the object models
    ObjectLoader loader = new ObjectLoader();
    loader.setLights(lights, ambientLightIntensity);
    try {
      robotModel = loader.loadObject("../images/robot.obj");
      powerUpModel = loader.loadObject("../images/cube.obj");
      blastModel = loader.loadObject("../images/blast.obj");
    catch (IOException ex) {
      ex.printStackTrace();
    }

    // create game objects
    gameObjectManager = new SimpleGameObjectManager();
    gameObjectManager.addPlayer(new GameObject(new PolygonGroup("Player")));
    gameObjectManager.getPlayer().getLocation().y = 5;
    for (int i = 0; i < NUM_BOTS; i++) {
      Bot object = new Bot((PolygonGrouprobotModel.clone());
      placeObject(object);
    }
    for (int i = 0; i < NUM_POWER_UPS; i++) {
      GameObject object = new GameObject((PolygonGrouppowerUpModel
          .clone());
      placeObject(object);
    }
  }

  // randomly place objects in game area
  public void placeObject(GameObject object) {
    float size = GAME_AREA_SIZE;
    object.getLocation().setTo((float) (Math.random() * size - size / 2),
        0(float) (Math.random() * size - size / 2));
    gameObjectManager.add(object);
  }

  public void createPolygonRenderer() {
    viewWindow = new ViewWindow(00, screen.getWidth(),
        screen.getHeight()(floatMath.toRadians(75));

    Transform3D camera = new Transform3D();
    polygonRenderer = new ZBufferedRenderer(camera, viewWindow);
  }

  public void updateWorld(long elapsedTime) {

    float angleVelocity;

    // cap elapsedTime
    elapsedTime = Math.min(elapsedTime, 100);

    GameObject player = gameObjectManager.getPlayer();
    MovingTransform3D playerTransform = player.getTransform();
    Vector3D velocity = playerTransform.getVelocity();

    playerTransform.stop();
    float x = -playerTransform.getSinAngleY();
    float z = -playerTransform.getCosAngleY();
    if (goForward.isPressed()) {
      velocity.add(x, 0, z);
    }
    if (goBackward.isPressed()) {
      velocity.add(-x, 0, -z);
    }
    if (goLeft.isPressed()) {
      velocity.add(z, 0, -x);
    }
    if (goRight.isPressed()) {
      velocity.add(-z, 0, x);
    }
    if (fire.isPressed()) {
      float cosX = playerTransform.getCosAngleX();
      float sinX = playerTransform.getSinAngleX();
      Blast blast = new Blast((PolygonGroupblastModel.clone(),
          new Vector3D(cosX * x, sinX, cosX * z));
      // blast starting location needs work. looks like
      // the blast is coming out of your forehead when
      // you're shooting down.
      blast.getLocation().setTo(player.getX(),
          player.getY() + BULLET_HEIGHT, player.getZ());
      gameObjectManager.add(blast);
    }

    velocity.multiply(PLAYER_SPEED);
    playerTransform.setVelocity(velocity);

    // look up/down (rotate around x)
    angleVelocity = Math.min(tiltUp.getAmount()200);
    angleVelocity += Math.max(-tiltDown.getAmount(), -200);
    playerTransform.setAngleVelocityX(angleVelocity * PLAYER_TURN_SPEED
        200);

    // turn (rotate around y)
    angleVelocity = Math.min(turnLeft.getAmount()200);
    angleVelocity += Math.max(-turnRight.getAmount(), -200);
    playerTransform.setAngleVelocityY(angleVelocity * PLAYER_TURN_SPEED
        200);

    // for now, mark the entire world as visible in this frame.
    gameObjectManager.markAllVisible();

    // update objects
    gameObjectManager.update(elapsedTime);

    // limit look up/down
    float angleX = playerTransform.getAngleX();
    float limit = (floatMath.PI / 2;
    if (angleX < -limit) {
      playerTransform.setAngleX(-limit);
    else if (angleX > limit) {
      playerTransform.setAngleX(limit);
    }

    // set the camera to be 100 units above the player
    Transform3D camera = polygonRenderer.getCamera();
    camera.setTo(playerTransform);
    camera.getLocation().add(01000);

  }

  public void draw(Graphics2D g) {

    polygonRenderer.startFrame(g);

    // draw floor
    polygonRenderer.draw(g, floor);

    // draw objects
    gameObjectManager.draw(g, (GameObjectRendererpolygonRenderer);

    polygonRenderer.endFrame(g);

    super.drawText(g);
  }

}
/**
 * The Bot game object is a small static bot with a turret that turns to face
 * the player.
 */

class Bot extends GameObject {

  private static final float TURN_SPEED = .0005f;

  private static final long DECISION_TIME = 2000;

  protected MovingTransform3D mainTransform;

  protected MovingTransform3D turretTransform;

  protected long timeUntilDecision;

  protected Vector3D lastPlayerLocation;

  public Bot(PolygonGroup polygonGroup) {
    super(polygonGroup);
    mainTransform = polygonGroup.getTransform();
    PolygonGroup turret = polygonGroup.getGroup("turret");
    if (turret != null) {
      turretTransform = turret.getTransform();
    else {
      System.out.println("No turret defined!");
    }
    lastPlayerLocation = new Vector3D();
  }

  public void notifyVisible(boolean visible) {
    if (!isDestroyed()) {
      if (visible) {
        setState(STATE_ACTIVE);
      else {
        setState(STATE_IDLE);
      }
    }
  }

  public void update(GameObject player, long elapsedTime) {
    if (turretTransform == null || isIdle()) {
      return;
    }

    Vector3D playerLocation = player.getLocation();
    if (playerLocation.equals(lastPlayerLocation)) {
      timeUntilDecision = DECISION_TIME;
    else {
      timeUntilDecision -= elapsedTime;
      if (timeUntilDecision <= || !turretTransform.isTurningY()) {
        float x = player.getX() - getX();
        float z = player.getZ() - getZ();
        turretTransform.turnYTo(x, z, -mainTransform.getAngleY(),
            TURN_SPEED);
        lastPlayerLocation.setTo(playerLocation);
        timeUntilDecision = DECISION_TIME;
      }
    }
    super.update(player, elapsedTime);
  }
}

/**
 * The ZBuffer class implements a z-buffer, or depth-buffer, that records the
 * depth of every pixel in a 3D view window. The value recorded for each pixel
 * is the inverse of the depth (1/z), so there is higher precision for close
 * objects and a lower precision for far-away objects (where high depth
 * precision is not as visually important).
 */

class ZBuffer {

  private short[] depthBuffer;

  private int width;

  private int height;

  /**
   * Creates a new z-buffer with the specified width and height.
   */
  public ZBuffer(int width, int height) {
    depthBuffer = new short[width * height];
    this.width = width;
    this.height = height;
    clear();
  }

  /**
   * Gets the width of this z-buffer.
   */
  public int getWidth() {
    return width;
  }

  /**
   * Gets the height of this z-buffer.
   */
  public int getHeight() {
    return height;
  }

  /**
   * Gets the array used for the depth buffer
   */
  public short[] getArray() {
    return depthBuffer;
  }

  /**
   * Clears the z-buffer. All depth values are set to 0.
   */
  public void clear() {
    for (int i = 0; i < depthBuffer.length; i++) {
      depthBuffer[i0;
    }
  }

  /**
   * Sets the depth of the pixel at at specified offset, overwriting its
   * current depth.
   */
  public void setDepth(int offset, short depth) {
    depthBuffer[offset= depth;
  }

  /**
   * Checks the depth at the specified offset, and if the specified depth is
   * lower (is greater than or equal to the current depth at the specified
   * offset), then the depth is set and this method returns true. Otherwise,
   * no action occurs and this method returns false.
   */
  public boolean checkDepth(int offset, short depth) {
    if (depth >= depthBuffer[offset]) {
      depthBuffer[offset= depth;
      return true;
    else {
      return false;
    }
  }

}

/**
 * The FastTexturedPolygonRenderer is a PolygonRenderer that efficiently renders
 * Textures.
 */

class FastTexturedPolygonRenderer extends PolygonRenderer {

  public static final int SCALE_BITS = 12;

  public static final int SCALE = << SCALE_BITS;

  public static final int INTERP_SIZE_BITS = 4;

  public static final int INTERP_SIZE = << INTERP_SIZE_BITS;

  protected Vector3D a = new Vector3D();

  protected Vector3D b = new Vector3D();

  protected Vector3D c = new Vector3D();

  protected Vector3D viewPos = new Vector3D();

  protected BufferedImage doubleBuffer;

  protected short[] doubleBufferData;

  protected HashMap scanRenderers;

  public FastTexturedPolygonRenderer(Transform3D camera, ViewWindow viewWindow) {
    this(camera, viewWindow, true);
  }

  public FastTexturedPolygonRenderer(Transform3D camera,
      ViewWindow viewWindow, boolean clearViewEveryFrame) {
    super(camera, viewWindow, clearViewEveryFrame);
  }

  protected void init() {
    destPolygon = new TexturedPolygon3D();
    scanConverter = new ScanConverter(viewWindow);

    // create renders for each texture (HotSpot optimization)
    scanRenderers = new HashMap();
    scanRenderers.put(PowerOf2Texture.class, new PowerOf2TextureRenderer());
    scanRenderers.put(ShadedTexture.class, new ShadedTextureRenderer());
    scanRenderers.put(ShadedSurface.class, new ShadedSurfaceRenderer());
  }

  public void startFrame(Graphics2D g) {
    // initialize buffer
    if (doubleBuffer == null
        || doubleBuffer.getWidth() != viewWindow.getWidth()
        || doubleBuffer.getHeight() != viewWindow.getHeight()) {
      doubleBuffer = new BufferedImage(viewWindow.getWidth(), viewWindow
          .getHeight(), BufferedImage.TYPE_USHORT_565_RGB);
      //doubleBuffer = g.getDeviceConfiguration().createCompatibleImage(
      //viewWindow.getWidth(), viewWindow.getHeight());

      DataBuffer dest = doubleBuffer.getRaster().getDataBuffer();
      doubleBufferData = ((DataBufferUShortdest).getData();
    }
    // clear view
    if (clearViewEveryFrame) {
      for (int i = 0; i < doubleBufferData.length; i++) {
        doubleBufferData[i0;
      }
    }
  }

  public void endFrame(Graphics2D g) {
    // draw the double buffer onto the screen
    g.drawImage(doubleBuffer, viewWindow.getLeftOffset(), viewWindow
        .getTopOffset()null);
  }

  protected void drawCurrentPolygon(Graphics2D g) {
    if (!(sourcePolygon instanceof TexturedPolygon3D)) {
      // not a textured polygon - return
      return;
    }
    TexturedPolygon3D poly = (TexturedPolygon3DdestPolygon;
    Texture texture = poly.getTexture();
    ScanRenderer scanRenderer = (ScanRendererscanRenderers.get(texture
        .getClass());
    scanRenderer.setTexture(texture);
    Rectangle3D textureBounds = poly.getTextureBounds();

    a.setToCrossProduct(textureBounds.getDirectionV(), textureBounds
        .getOrigin());
    b.setToCrossProduct(textureBounds.getOrigin(), textureBounds
        .getDirectionU());
    c.setToCrossProduct(textureBounds.getDirectionU(), textureBounds
        .getDirectionV());

    int y = scanConverter.getTopBoundary();
    viewPos.y = viewWindow.convertFromScreenYToViewY(y);
    viewPos.z = -viewWindow.getDistance();

    while (y <= scanConverter.getBottomBoundary()) {
      ScanConverter.Scan scan = scanConverter.getScan(y);

      if (scan.isValid()) {
        viewPos.x = viewWindow.convertFromScreenXToViewX(scan.left);
        int offset = (y - viewWindow.getTopOffset())
            * viewWindow.getWidth()
            (scan.left - viewWindow.getLeftOffset());

        scanRenderer.render(offset, scan.left, scan.right);
      }
      y++;
      viewPos.y--;
    }
  }

  /**
   * The ScanRenderer class is an abstract inner class of
   * FastTexturedPolygonRenderer that provides an interface for rendering a
   * horizontal scan line.
   */
  public abstract class ScanRenderer {

    protected Texture currentTexture;

    public void setTexture(Texture texture) {
      this.currentTexture = texture;
    }

    public abstract void render(int offset, int left, int right);

  }

  //================================================
  // FASTEST METHOD: no texture (for comparison)
  //================================================
  public class Method0 extends ScanRenderer {

    public void render(int offset, int left, int right) {
      for (int x = left; x <= right; x++) {
        doubleBufferData[offset++(short0x0007;
      }
    }
  }

  //================================================
  // METHOD 1: access pixel buffers directly
  // and use textures sizes that are a power of 2
  //================================================
  public class Method1 extends ScanRenderer {

    public void render(int offset, int left, int right) {
      for (int x = left; x <= right; x++) {
        int tx = (int) (a.getDotProduct(viewPos/ c
            .getDotProduct(viewPos));
        int ty = (int) (b.getDotProduct(viewPos/ c
            .getDotProduct(viewPos));
        doubleBufferData[offset++= currentTexture.getColor(tx, ty);
        viewPos.x++;
      }
    }
  }

  //================================================
  // METHOD 2: avoid redundant calculations
  //================================================
  public class Method2 extends ScanRenderer {

    public void render(int offset, int left, int right) {
      float u = a.getDotProduct(viewPos);
      float v = b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = a.x;
      float dv = b.x;
      float dz = c.x;
      for (int x = left; x <= right; x++) {
        doubleBufferData[offset++= currentTexture.getColor(
            (int) (u / z)(int) (v / z));
        u += du;
        v += dv;
        z += dz;
      }
    }
  }

  //================================================
  // METHOD 3: use ints instead of floats
  //================================================
  public class Method3 extends ScanRenderer {

    public void render(int offset, int left, int right) {
      int u = (int) (SCALE * a.getDotProduct(viewPos));
      int v = (int) (SCALE * b.getDotProduct(viewPos));
      int z = (int) (SCALE * c.getDotProduct(viewPos));
      int du = (int) (SCALE * a.x);
      int dv = (int) (SCALE * b.x);
      int dz = (int) (SCALE * c.x);
      for (int x = left; x <= right; x++) {
        doubleBufferData[offset++= currentTexture.getColor(u / z, v
            / z);
        u += du;
        v += dv;
        z += dz;
      }
    }
  }

  //================================================
  // METHOD 4: reduce the number of divides
  // (interpolate every 16 pixels)
  // Also, apply a VM optimization by referring to
  // the texture's class rather than it's parent class.
  //================================================

  // the following three ScanRenderers are the same, but refer
  // to textures explicitly as either a PowerOf2Texture, a
  // ShadedTexture, or a ShadedSurface.
  // This allows HotSpot to do some inlining of the textures'
  // getColor() method, which significantly increases
  // performance.

  public class PowerOf2TextureRenderer extends ScanRenderer {

    public void render(int offset, int left, int right) {
      PowerOf2Texture texture = (PowerOf2TexturecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += interpSize;
        }

      }
    }
  }

  public class ShadedTextureRenderer extends ScanRenderer {

    public void render(int offset, int left, int right) {
      ShadedTexture texture = (ShadedTexturecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += interpSize;
        }

      }
    }
  }

  public class ShadedSurfaceRenderer extends ScanRenderer {

    public int checkBounds(int vScaled, int bounds) {
      int v = vScaled >> SCALE_BITS;
      if (v < 0) {
        vScaled = 0;
      else if (v >= bounds) {
        vScaled = (bounds - 1<< SCALE_BITS;
      }
      return vScaled;
    }

    public void render(int offset, int left, int right) {
      ShadedSurface texture = (ShadedSurfacecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);

          // make sure tx, ty, nextTx, and nextTy are
          // all within bounds
          tx = checkBounds(tx, texture.getWidth());
          ty = checkBounds(ty, texture.getHeight());
          nextTx = checkBounds(nextTx, texture.getWidth());
          nextTy = checkBounds(nextTy, texture.getHeight());

          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            doubleBufferData[offset++= texture.getColor(
                tx >> SCALE_BITS, ty >> SCALE_BITS);
            tx += dtx;
            ty += dty;
          }
          x += interpSize;
        }
      }

    }
  }

}

/**
 * The ShadedSurfacePolygonRenderer is a PolygonRenderer that renders polygons
 * with ShadedSurfaces. It keeps track of built surfaces, and clears any
 * surfaces that weren't used in the last rendered frame to save memory.
 */

class ShadedSurfacePolygonRenderer extends FastTexturedPolygonRenderer {

  private List builtSurfaces = new LinkedList();

  public ShadedSurfacePolygonRenderer(Transform3D camera,
      ViewWindow viewWindow) {
    this(camera, viewWindow, true);
  }

  public ShadedSurfacePolygonRenderer(Transform3D camera,
      ViewWindow viewWindow, boolean eraseView) {
    super(camera, viewWindow, eraseView);
  }

  public void endFrame(Graphics2D g) {
    super.endFrame(g);

    // clear all built surfaces that weren't used this frame.
    Iterator i = builtSurfaces.iterator();
    while (i.hasNext()) {
      ShadedSurface surface = (ShadedSurfacei.next();
      if (surface.isDirty()) {
        surface.clearSurface();
        i.remove();
      else {
        surface.setDirty(true);
      }
    }
  }

  protected void drawCurrentPolygon(Graphics2D g) {
    buildSurface();
    super.drawCurrentPolygon(g);
  }

  /**
   * Builds the surface of the polygon if it has a ShadedSurface that is
   * cleared.
   */
  protected void buildSurface() {
    // build surface, if needed
    if (sourcePolygon instanceof TexturedPolygon3D) {
      Texture texture = ((TexturedPolygon3DsourcePolygon).getTexture();
      if (texture instanceof ShadedSurface) {
        ShadedSurface surface = (ShadedSurfacetexture;
        if (surface.isCleared()) {
          surface.buildSurface();
          builtSurfaces.add(surface);
        }
        surface.setDirty(false);
      }
    }
  }

}

/**
 * The ZBufferedRenderer is a PolygonRenderer that renders polygons with a
 * Z-Buffer to ensure correct rendering (closer objects appear in front of
 * farther away objects).
 */

class ZBufferedRenderer extends ShadedSurfacePolygonRenderer implements
    GameObjectRenderer {
  /**
   * The minimum distance for z-buffering. Larger values give more accurate
   * calculations for further distances.
   */
  protected static final int MIN_DISTANCE = 12;

  protected TexturedPolygon3D temp;

  protected ZBuffer zBuffer;

  // used for calculating depth
  protected float w;

  public ZBufferedRenderer(Transform3D camera, ViewWindow viewWindow) {
    this(camera, viewWindow, true);
  }

  public ZBufferedRenderer(Transform3D camera, ViewWindow viewWindow,
      boolean eraseView) {
    super(camera, viewWindow, eraseView);
    temp = new TexturedPolygon3D();
  }

  protected void init() {
    destPolygon = new TexturedPolygon3D();
    scanConverter = new ScanConverter(viewWindow);

    // create renders for each texture (HotSpot optimization)
    scanRenderers = new HashMap();
    scanRenderers
        .put(PowerOf2Texture.class, new PowerOf2TextureZRenderer());
    scanRenderers.put(ShadedTexture.class, new ShadedTextureZRenderer());
    scanRenderers.put(ShadedSurface.class, new ShadedSurfaceZRenderer());
  }

  public void startFrame(Graphics2D g) {
    super.startFrame(g);
    // initialize depth buffer
    if (zBuffer == null || zBuffer.getWidth() != viewWindow.getWidth()
        || zBuffer.getHeight() != viewWindow.getHeight()) {
      zBuffer = new ZBuffer(viewWindow.getWidth(), viewWindow.getHeight());
    else if (clearViewEveryFrame) {
      zBuffer.clear();
    }
  }

  public boolean draw(Graphics2D g, GameObject object) {
    return draw(g, object.getPolygonGroup());
  }

  public boolean draw(Graphics2D g, PolygonGroup group) {
    boolean visible = false;
    group.resetIterator();
    while (group.hasNext()) {
      group.nextPolygonTransformed(temp);
      visible |= draw(g, temp);
    }
    return visible;
  }

  protected void drawCurrentPolygon(Graphics2D g) {
    if (!(sourcePolygon instanceof TexturedPolygon3D)) {
      // not a textured polygon - return
      return;
    }
    buildSurface();
    TexturedPolygon3D poly = (TexturedPolygon3DdestPolygon;
    Texture texture = poly.getTexture();
    ScanRenderer scanRenderer = (ScanRendererscanRenderers.get(texture
        .getClass());
    scanRenderer.setTexture(texture);
    Rectangle3D textureBounds = poly.getTextureBounds();

    a.setToCrossProduct(textureBounds.getDirectionV(), textureBounds
        .getOrigin());
    b.setToCrossProduct(textureBounds.getOrigin(), textureBounds
        .getDirectionU());
    c.setToCrossProduct(textureBounds.getDirectionU(), textureBounds
        .getDirectionV());

    // w is used to compute depth at each pixel
    w = SCALE
        * MIN_DISTANCE
        * Short.MAX_VALUE
        (viewWindow.getDistance() * c.getDotProduct(textureBounds
            .getOrigin()));

    int y = scanConverter.getTopBoundary();
    viewPos.y = viewWindow.convertFromScreenYToViewY(y);
    viewPos.z = -viewWindow.getDistance();

    while (y <= scanConverter.getBottomBoundary()) {
      ScanConverter.Scan scan = scanConverter.getScan(y);

      if (scan.isValid()) {
        viewPos.x = viewWindow.convertFromScreenXToViewX(scan.left);
        int offset = (y - viewWindow.getTopOffset())
            * viewWindow.getWidth()
            (scan.left - viewWindow.getLeftOffset());

        scanRenderer.render(offset, scan.left, scan.right);
      }
      y++;
      viewPos.y--;
    }
  }

  // the following three ScanRenderers are the same, but refer
  // to textures explicitly as either a PowerOf2Texture, a
  // ShadedTexture, or a ShadedSurface.
  // This allows HotSpot to do some inlining of the textures'
  // getColor() method, which significantly increases
  // performance.

  public class PowerOf2TextureZRenderer extends ScanRenderer {

    public void render(int offset, int left, int right) {
      PowerOf2Texture texture = (PowerOf2TexturecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int depth = (int) (w * z);
      int dDepth = (int) (w * c.x);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset++= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += interpSize;

        }

      }
    }
  }

  public class ShadedTextureZRenderer extends ScanRenderer {

    public void render(int offset, int left, int right) {
      ShadedTexture texture = (ShadedTexturecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int depth = (int) (w * z);
      int dDepth = (int) (w * c.x);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += interpSize;
        }

      }
    }
  }

  public class ShadedSurfaceZRenderer extends ScanRenderer {

    public int checkBounds(int vScaled, int bounds) {
      int v = vScaled >> SCALE_BITS;
      if (v < 0) {
        vScaled = 0;
      else if (v >= bounds) {
        vScaled = (bounds - 1<< SCALE_BITS;
      }
      return vScaled;
    }

    public void render(int offset, int left, int right) {
      ShadedSurface texture = (ShadedSurfacecurrentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int depth = (int) (w * z);
      int dDepth = (int) (w * c.x);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx>> INTERP_SIZE_BITS;
          int dty = (nextTy - ty>> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += INTERP_SIZE;
        else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);

          // make sure tx, ty, nextTx, and nextTy are
          // all within bounds
          tx = checkBounds(tx, texture.getWidth());
          ty = checkBounds(ty, texture.getHeight());
          nextTx = checkBounds(nextTx, texture.getWidth());
          nextTy = checkBounds(nextTy, texture.getHeight());

          int dtx = (nextTx - tx/ interpSize;
          int dty = (nextTy - ty/ interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset,
                (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset= texture.getColor(
                  tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += interpSize;

        }

      }
    }
  }

}

/**
 * The Blast GameObject is a projectile, designed to travel in a straight line
 * for five seconds, then die. Blasts destroy Bots instantly.
 */

class Blast extends GameObject {

  private static final long DIE_TIME = 5000;

  private static final float SPEED = 1.5f;

  private static final float ROT_SPEED = .008f;

  private long aliveTime;

  /**
   * Create a new Blast with the specified PolygonGroup and normalized vector
   * direction.
   */
  public Blast(PolygonGroup polygonGroup, Vector3D direction) {
    super(polygonGroup);
    MovingTransform3D transform = polygonGroup.getTransform();
    Vector3D velocity = transform.getVelocity();
    velocity.setTo(direction);
    velocity.multiply(SPEED);
    transform.setVelocity(velocity);
    //transform.setAngleVelocityX(ROT_SPEED);
    transform.setAngleVelocityY(ROT_SPEED);
    transform.setAngleVelocityZ(ROT_SPEED);
    setState(STATE_ACTIVE);
  }

  public void update(GameObject player, long elapsedTime) {
    aliveTime += elapsedTime;
    if (aliveTime >= DIE_TIME) {
      setState(STATE_DESTROYED);
    else {
      super.update(player, elapsedTime);
    }
  }

}

/**
 * The InputManager manages input of key and mouse events. Events are mapped to
 * GameActions.
 */

class InputManager implements KeyListener, MouseListener, MouseMotionListener,
    MouseWheelListener {
  /**
   * An invisible cursor.
   */
  public static final Cursor INVISIBLE_CURSOR = Toolkit.getDefaultToolkit()
      .createCustomCursor(Toolkit.getDefaultToolkit().getImage(""),
          new Point(00)"invisible");

  // mouse codes
  public static final int MOUSE_MOVE_LEFT = 0;

  public static final int MOUSE_MOVE_RIGHT = 1;

  public static final int MOUSE_MOVE_UP = 2;

  public static final int MOUSE_MOVE_DOWN = 3;

  public static final int MOUSE_WHEEL_UP = 4;

  public static final int MOUSE_WHEEL_DOWN = 5;

  public static final int MOUSE_BUTTON_1 = 6;

  public static final int MOUSE_BUTTON_2 = 7;

  public static final int MOUSE_BUTTON_3 = 8;

  private static final int NUM_MOUSE_CODES = 9;

  // key codes are defined in java.awt.KeyEvent.
  // most of the codes (except for some rare ones like
  // "alt graph") are less than 600.
  private static final int NUM_KEY_CODES = 600;

  private GameAction[] keyActions = new GameAction[NUM_KEY_CODES];

  private GameAction[] mouseActions = new GameAction[NUM_MOUSE_CODES];

  private Point mouseLocation;

  private Point centerLocation;

  private Component comp;

  private Robot robot;

  private boolean isRecentering;

  /**
   * Creates a new InputManager that listens to input from the specified
   * component.
   */
  public InputManager(Component comp) {
    this.comp = comp;
    mouseLocation = new Point();
    centerLocation = new Point();

    // register key and mouse listeners
    comp.addKeyListener(this);
    comp.addMouseListener(this);
    comp.addMouseMotionListener(this);
    comp.addMouseWheelListener(this);

    // allow input of the TAB key and other keys normally
    // used for focus traversal
    comp.setFocusTraversalKeysEnabled(false);
  }

  /**
   * Sets the cursor on this InputManager's input component.
   */
  public void setCursor(Cursor cursor) {
    comp.setCursor(cursor);
  }

  /**
   * Sets whether realtive mouse mode is on or not. For relative mouse mode,
   * the mouse is "locked" in the center of the screen, and only the changed
   * in mouse movement is measured. In normal mode, the mouse is free to move
   * about the screen.
   */
  public void setRelativeMouseMode(boolean mode) {
    if (mode == isRelativeMouseMode()) {
      return;
    }

    if (mode) {
      try {
        robot = new Robot();
        mouseLocation.x = comp.getWidth() 2;
        mouseLocation.y = comp.getHeight() 2;
        recenterMouse();
      catch (AWTException ex) {
        // couldn't create robot!
        robot = null;
      }
    else {
      robot = null;
    }
  }

  /**
   * Returns whether or not relative mouse mode is on.
   */
  public boolean isRelativeMouseMode() {
    return (robot != null);
  }

  /**
   * Maps a GameAction to a specific key. The key codes are defined in
   * java.awt.KeyEvent. If the key already has a GameAction mapped to it, the
   * new GameAction overwrites it.
   */
  public void mapToKey(GameAction gameAction, int keyCode) {
    keyActions[keyCode= gameAction;
  }

  /**
   * Maps a GameAction to a specific mouse action. The mouse codes are defined
   * herer in InputManager (MOUSE_MOVE_LEFT, MOUSE_BUTTON_1, etc). If the
   * mouse action already has a GameAction mapped to it, the new GameAction
   * overwrites it.
   */
  public void mapToMouse(GameAction gameAction, int mouseCode) {
    mouseActions[mouseCode= gameAction;
  }

  /**
   * Clears all mapped keys and mouse actions to this GameAction.
   */
  public void clearMap(GameAction gameAction) {
    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i== gameAction) {
        keyActions[inull;
      }
    }

    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i== gameAction) {
        mouseActions[inull;
      }
    }

    gameAction.reset();
  }

  /**
   * Gets a List of names of the keys and mouse actions mapped to this
   * GameAction. Each entry in the List is a String.
   */
  public List getMaps(GameAction gameCode) {
    ArrayList list = new ArrayList();

    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i== gameCode) {
        list.add(getKeyName(i));
      }
    }

    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i== gameCode) {
        list.add(getMouseName(i));
      }
    }
    return list;
  }

  /**
   * Resets all GameActions so they appear like they haven't been pressed.
   */
  public void resetAllGameActions() {
    for (int i = 0; i < keyActions.length; i++) {
      if (keyActions[i!= null) {
        keyActions[i].reset();
      }
    }

    for (int i = 0; i < mouseActions.length; i++) {
      if (mouseActions[i!= null) {
        mouseActions[i].reset();
      }
    }
  }

  /**
   * Gets the name of a key code.
   */
  public static String getKeyName(int keyCode) {
    return KeyEvent.getKeyText(keyCode);
  }

  /**
   * Gets the name of a mouse code.
   */
  public static String getMouseName(int mouseCode) {
    switch (mouseCode) {
    case MOUSE_MOVE_LEFT:
      return "Mouse Left";
    case MOUSE_MOVE_RIGHT:
      return "Mouse Right";
    case MOUSE_MOVE_UP:
      return "Mouse Up";
    case MOUSE_MOVE_DOWN:
      return "Mouse Down";
    case MOUSE_WHEEL_UP:
      return "Mouse Wheel Up";
    case MOUSE_WHEEL_DOWN:
      return "Mouse Wheel Down";
    case MOUSE_BUTTON_1:
      return "Mouse Button 1";
    case MOUSE_BUTTON_2:
      return "Mouse Button 2";
    case MOUSE_BUTTON_3:
      return "Mouse Button 3";
    default:
      return "Unknown mouse code " + mouseCode;
    }
  }

  /**
   * Gets the x position of the mouse.
   */
  public int getMouseX() {
    return mouseLocation.x;
  }

  /**
   * Gets the y position of the mouse.
   */
  public int getMouseY() {
    return mouseLocation.y;
  }

  /**
   * Uses the Robot class to try to postion the mouse in the center of the
   * screen.
   * <p>
   * Note that use of the Robot class may not be available on all platforms.
   */
  private synchronized void recenterMouse() {
    if (robot != null && comp.isShowing()) {
      centerLocation.x = comp.getWidth() 2;
      centerLocation.y = comp.getHeight() 2;
      SwingUtilities.convertPointToScreen(centerLocation, comp);
      isRecentering = true;
      robot.mouseMove(centerLocation.x, centerLocation.y);
    }
  }

  private GameAction getKeyAction(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode < keyActions.length) {
      return keyActions[keyCode];
    else {
      return null;
    }
  }

  /**
   * Gets the mouse code for the button specified in this MouseEvent.
   */
  public static int getMouseButtonCode(MouseEvent e) {
    switch (e.getButton()) {
    case MouseEvent.BUTTON1:
      return MOUSE_BUTTON_1;
    case MouseEvent.BUTTON2:
      return MOUSE_BUTTON_2;
    case MouseEvent.BUTTON3:
      return MOUSE_BUTTON_3;
    default:
      return -1;
    }
  }

  private GameAction getMouseButtonAction(MouseEvent e) {
    int mouseCode = getMouseButtonCode(e);
    if (mouseCode != -1) {
      return mouseActions[mouseCode];
    else {
      return null;
    }
  }

  // from the KeyListener interface
  public void keyPressed(KeyEvent e) {
    GameAction gameAction = getKeyAction(e);
    if (gameAction != null) {
      gameAction.press();
    }
    // make sure the key isn't processed for anything else
    e.consume();
  }

  // from the KeyListener interface
  public void keyReleased(KeyEvent e) {
    GameAction gameAction = getKeyAction(e);
    if (gameAction != null) {
      gameAction.release();
    }
    // make sure the key isn't processed for anything else
    e.consume();
  }

  // from the KeyListener interface
  public void keyTyped(KeyEvent e) {
    // make sure the key isn't processed for anything else
    e.consume();
  }

  // from the MouseListener interface
  public void mousePressed(MouseEvent e) {
    GameAction gameAction = getMouseButtonAction(e);
    if (gameAction != null) {
      gameAction.press();
    }
  }

  // from the MouseListener interface
  public void mouseReleased(MouseEvent e) {
    GameAction gameAction = getMouseButtonAction(e);
    if (gameAction != null) {
      gameAction.release();
    }
  }

  // from the MouseListener interface
  public void mouseClicked(MouseEvent e) {
    // do nothing
  }

  // from the MouseListener interface
  public void mouseEntered(MouseEvent e) {
    mouseMoved(e);
  }

  // from the MouseListener interface
  public void mouseExited(MouseEvent e) {
    mouseMoved(e);
  }

  // from the MouseMotionListener interface
  public void mouseDragged(MouseEvent e) {
    mouseMoved(e);
  }

  // from the MouseMotionListener interface
  public synchronized void mouseMoved(MouseEvent e) {
    // this event is from re-centering the mouse - ignore it
    if (isRecentering && centerLocation.x == e.getX()
        && centerLocation.y == e.getY()) {
      isRecentering = false;
    else {
      int dx = e.getX() - mouseLocation.x;
      int dy = e.getY() - mouseLocation.y;
      mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);
      mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);

      if (isRelativeMouseMode()) {
        recenterMouse();
      }
    }

    mouseLocation.x = e.getX();
    mouseLocation.y = e.getY();

  }

  // from the MouseWheelListener interface
  public void mouseWheelMoved(MouseWheelEvent e) {
    mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, e.getWheelRotation());
  }

  private void mouseHelper(int codeNeg, int codePos, int amount) {
    GameAction gameAction;
    if (amount < 0) {
      gameAction = mouseActions[codeNeg];
    else {
      gameAction = mouseActions[codePos];
    }
    if (gameAction != null) {
      gameAction.press(Math.abs(amount));
      gameAction.release();
    }
  }

}
/**
 * The ScreenManager class manages initializing and displaying full screen
 * graphics modes.
 */

class ScreenManager {

  private GraphicsDevice device;

  /**
   * Creates a new ScreenManager object.
   */
  public ScreenManager() {
    GraphicsEnvironment environment = GraphicsEnvironment
        .getLocalGraphicsEnvironment();
    device = environment.getDefaultScreenDevice();
  }

  /**
   * Returns a list of compatible display modes for the default device on the
   * system.
   */
  public DisplayMode[] getCompatibleDisplayModes() {
    return device.getDisplayModes();
  }

  /**
   * Returns the first compatible mode in a list of modes. Returns null if no
   * modes are compatible.
   */
  public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {
    DisplayMode goodModes[] = device.getDisplayModes();
    for (int i = 0; i < modes.length; i++) {
      for (int j = 0; j < goodModes.length; j++) {
        if (displayModesMatch(modes[i], goodModes[j])) {
          return modes[i];
        }
      }

    }

    return null;
  }

  /**
   * Returns the current display mode.
   */
  public DisplayMode getCurrentDisplayMode() {
    return device.getDisplayMode();
  }

  /**
   * Determines if two display modes "match". Two display modes match if they
   * have the same resolution, bit depth, and refresh rate. The bit depth is
   * ignored if one of the modes has a bit depth of
   * DisplayMode.BIT_DEPTH_MULTI. Likewise, the refresh rate is ignored if one
   * of the modes has a refresh rate of DisplayMode.REFRESH_RATE_UNKNOWN.
   */
  public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2)

  {
    if (mode1.getWidth() != mode2.getWidth()
        || mode1.getHeight() != mode2.getHeight()) {
      return false;
    }

    if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
        && mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI
        && mode1.getBitDepth() != mode2.getBitDepth()) {
      return false;
    }

    if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
        && mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN
        && mode1.getRefreshRate() != mode2.getRefreshRate()) {
      return false;
    }

    return true;
  }

  /**
   * Enters full screen mode and changes the display mode. If the specified
   * display mode is null or not compatible with this device, or if the
   * display mode cannot be changed on this system, the current display mode
   * is used.
   * <p>
   * The display uses a BufferStrategy with 2 buffers.
   */
  public void setFullScreen(DisplayMode displayMode) {
    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    frame.setIgnoreRepaint(true);
    frame.setResizable(false);

    device.setFullScreenWindow(frame);

    if (displayMode != null && device.isDisplayChangeSupported()) {
      try {
        device.setDisplayMode(displayMode);
      catch (IllegalArgumentException ex) {
      }
      // fix for mac os x
      frame.setSize(displayMode.getWidth(), displayMode.getHeight());
    }
    // avoid potential deadlock in 1.4.1_02
    try {
      EventQueue.invokeAndWait(new Runnable() {
        public void run() {
          frame.createBufferStrategy(2);
        }
      });
    catch (InterruptedException ex) {
      // ignore
    catch (InvocationTargetException ex) {
      // ignore
    }

  }

  /**
   * Gets the graphics context for the display. The ScreenManager uses double
   * buffering, so applications must call update() to show any graphics drawn.
   * <p>
   * The application must dispose of the graphics object.
   */
  public Graphics2D getGraphics() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      BufferStrategy strategy = window.getBufferStrategy();
      return (Graphics2Dstrategy.getDrawGraphics();
    else {
      return null;
    }
  }

  /**
   * Updates the display.
   */
  public void update() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      BufferStrategy strategy = window.getBufferStrategy();
      if (!strategy.contentsLost()) {
        strategy.show();
      }
    }
    // Sync the display on some systems.
    // (on Linux, this fixes event queue problems)
    //Toolkit.getDefaultToolkit().sync();
  }

  /**
   * Returns the window currently used in full screen mode. Returns null if
   * the device is not in full screen mode.
   */
  public JFrame getFullScreenWindow() {
    return (JFramedevice.getFullScreenWindow();
  }

  /**
   * Returns the width of the window currently used in full screen mode.
   * Returns 0 if the device is not in full screen mode.
   */
  public int getWidth() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      return window.getWidth();
    else {
      return 0;
    }
  }

  /**
   * Returns the height of the window currently used in full screen mode.
   * Returns 0 if the device is not in full screen mode.
   */
  public int getHeight() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      return window.getHeight();
    else {
      return 0;
    }
  }

  /**
   * Restores the screen's display mode.
   */
  public void restoreScreen() {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      window.dispose();
    }
    device.setFullScreenWindow(null);
  }

  /**
   * Creates an image compatible with the current display.
   */
  public BufferedImage createCompatibleImage(int w, int h, int transparancy) {
    Window window = device.getFullScreenWindow();
    if (window != null) {
      GraphicsConfiguration gc = window.getGraphicsConfiguration();
      return gc.createCompatibleImage(w, h, transparancy);
    }
    return null;
  }
}

/**
 * Simple abstract class used for testing. Subclasses should implement the
 * draw() method.
 */

abstract class GameCore {

  protected static final int DEFAULT_FONT_SIZE = 24;

  // various lists of modes, ordered by preference
  protected static final DisplayMode[] MID_RES_MODES = {
      new DisplayMode(800600160)new DisplayMode(800600320),
      new DisplayMode(800600240)new DisplayMode(640480160),
      new DisplayMode(640480320)new DisplayMode(640480240),
      new DisplayMode(1024768160),
      new DisplayMode(1024768320),
      new DisplayMode(1024768240)};

  protected static final DisplayMode[] LOW_RES_MODES = {
      new DisplayMode(640480160)new DisplayMode(640480320),
      new DisplayMode(640480240)new DisplayMode(800600160),
      new DisplayMode(800600320)new DisplayMode(800600240),
      new DisplayMode(1024768160),
      new DisplayMode(1024768320),
      new DisplayMode(1024768240)};

  protected static final DisplayMode[] VERY_LOW_RES_MODES = {
      new DisplayMode(320240160)new DisplayMode(400300160),
      new DisplayMode(512384160)new DisplayMode(640480160),
      new DisplayMode(800600160)};

  private boolean isRunning;

  protected ScreenManager screen;

  protected int fontSize = DEFAULT_FONT_SIZE;

  /**
   * Signals the game loop that it's time to quit
   */
  public void stop() {
    isRunning = false;
  }

  /**
   * Calls init() and gameLoop()
   */
  public void run() {
    try {
      init();
      gameLoop();
    finally {
      if (screen != null) {
        screen.restoreScreen();
      }
      lazilyExit();
    }
  }

  /**
   * Exits the VM from a daemon thread. The daemon thread waits 2 seconds then
   * calls System.exit(0). Since the VM should exit when only daemon threads
   * are running, this makes sure System.exit(0) is only called if neccesary.
   * It's neccesary if the Java Sound system is running.
   */
  public void lazilyExit() {
    Thread thread = new Thread() {
      public void run() {
        // first, wait for the VM exit on its own.
        try {
          Thread.sleep(2000);
        catch (InterruptedException ex) {
        }
        // system is still running, so force an exit
        System.exit(0);
      }
    };
    thread.setDaemon(true);
    thread.start();
  }

  /**
   * Sets full screen mode and initiates and objects.
   */
  public void init() {
    init(MID_RES_MODES);
  }

  /**
   * Sets full screen mode and initiates and objects.
   */
  public void init(DisplayMode[] possibleModes) {
    screen = new ScreenManager();
    DisplayMode displayMode = screen.findFirstCompatibleMode(possibleModes);
    screen.setFullScreen(displayMode);

    Window window = screen.getFullScreenWindow();
    window.setFont(new Font("Dialog", Font.PLAIN, fontSize));
    window.setBackground(Color.blue);
    window.setForeground(Color.white);

    isRunning = true;
  }

  public Image loadImage(String fileName) {
    return new ImageIcon(fileName).getImage();
  }

  /**
   * Runs through the game loop until stop() is called.
   */
  public void gameLoop() {
    long startTime = System.currentTimeMillis();
    long currTime = startTime;

    while (isRunning) {
      long elapsedTime = System.currentTimeMillis() - currTime;
      currTime += elapsedTime;

      // update
      update(elapsedTime);

      // draw the screen
      Graphics2D g = screen.getGraphics();
      draw(g);
      g.dispose();
      screen.update();

      // don't take a nap! run as fast as possible
      /*
       * try { Thread.sleep(20); } catch (InterruptedException ex) { }
       */
    }
  }

  /**
   * Updates the state of the game/animation based on the amount of elapsed
   * time that has passed.
   */
  public void update(long elapsedTime) {
    // do nothing
  }

  /**
   * Draws to the screen. Subclasses must override this method.
   */
  public abstract void draw(Graphics2D g);
}
/**
 * The ViewWindow class represents the geometry of a view window for 3D viewing.
 */

class ViewWindow {

  private Rectangle bounds;

  private float angle;

  private float distanceToCamera;

  /**
   * Creates a new ViewWindow with the specified bounds on the screen and
   * horizontal view angle.
   */
  public ViewWindow(int left, int top, int width, int height, float angle) {
    bounds = new Rectangle();
    this.angle = angle;
    setBounds(left, top, width, height);
  }

  /**
   * Sets the bounds for this ViewWindow on the screen.
   */
  public void setBounds(int left, int top, int width, int height) {
    bounds.x = left;
    bounds.y = top;
    bounds.width = width;
    bounds.height = height;
    distanceToCamera = (bounds.width / 2(floatMath.tan(angle / 2);
  }

  /**
   * Sets the horizontal view angle for this ViewWindow.
   */
  public void setAngle(float angle) {
    this.angle = angle;
    distanceToCamera = (bounds.width / 2(floatMath.tan(angle / 2);
  }

  /**
   * Gets the horizontal view angle of this view window.
   */
  public float getAngle() {
    return angle;
  }

  /**
   * Gets the width of this view window.
   */
  public int getWidth() {
    return bounds.width;
  }

  /**
   * Gets the height of this view window.
   */
  public int getHeight() {
    return bounds.height;
  }

  /**
   * Gets the y offset of this view window on the screen.
   */
  public int getTopOffset() {
    return bounds.y;
  }

  /**
   * Gets the x offset of this view window on the screen.
   */
  public int getLeftOffset() {
    return bounds.x;
  }

  /**
   * Gets the distance from the camera to to this view window.
   */
  public float getDistance() {
    return distanceToCamera;
  }

  /**
   * Converts an x coordinate on this view window to the corresponding x
   * coordinate on the screen.
   */
  public float convertFromViewXToScreenX(float x) {
    return x + bounds.x + bounds.width / 2;
  }

  /**
   * Converts a y coordinate on this view window to the corresponding y
   * coordinate on the screen.
   */
  public float convertFromViewYToScreenY(float y) {
    return -y + bounds.y + bounds.height / 2;
  }

  /**
   * Converts an x coordinate on the screen to the corresponding x coordinate
   * on this view window.
   */
  public float convertFromScreenXToViewX(float x) {
    return x - bounds.x - bounds.width / 2;
  }

  /**
   * Converts an y coordinate on the screen to the corresponding y coordinate
   * on this view window.
   */
  public float convertFromScreenYToViewY(float y) {
    return -y + bounds.y + bounds.height / 2;
  }

  /**
   * Projects the specified vector to the screen.
   */
  public void project(Vector3D v) {
    // project to view window
    v.x = distanceToCamera * v.x / -v.z;
    v.y = distanceToCamera * v.y / -v.z;

    // convert to screen coordinates
    v.x = convertFromViewXToScreenX(v.x);
    v.y = convertFromViewYToScreenY(v.y);
  }
}

/**
 * The Vector3D class implements a 3D vector with the floating-point values x,
 * y, and z. Vectors can be thought of either as a (x,y,z) point or as a vector
 * from (0,0,0) to (x,y,z).
 */

class Vector3D implements Transformable {

  public float x;

  public float y;

  public float z;

  /**
   * Creates a new Vector3D at (0,0,0).
   */
  public Vector3D() {
    this(000);
  }

  /**
   * Creates a new Vector3D with the same values as the specified Vector3D.
   */
  public Vector3D(Vector3D v) {
    this(v.x, v.y, v.z);
  }

  /**
   * Creates a new Vector3D with the specified (x, y, z) values.
   */
  public Vector3D(float x, float y, float z) {
    setTo(x, y, z);
  }

  /**
   * Checks if this Vector3D is equal to the specified Object. They are equal
   * only if the specified Object is a Vector3D and the two Vector3D's x, y,
   * and z coordinates are equal.
   */
  public boolean equals(Object obj) {
    Vector3D v = (Vector3Dobj;
    return (v.x == x && v.y == y && v.z == z);
  }

  /**
   * Checks if this Vector3D is equal to the specified x, y, and z
   * coordinates.
   */
  public boolean equals(float x, float y, float z) {
    return (this.x == x && this.y == y && this.z == z);
  }

  /**
   * Sets the vector to the same values as the specified Vector3D.
   */
  public void setTo(Vector3D v) {
    setTo(v.x, v.y, v.z);
  }

  /**
   * Sets this vector to the specified (x, y, z) values.
   */
  public void setTo(float x, float y, float z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  /**
   * Adds the specified (x, y, z) values to this vector.
   */
  public void add(float x, float y, float z) {
    this.x += x;
    this.y += y;
    this.z += z;
  }

  /**
   * Subtracts the specified (x, y, z) values to this vector.
   */
  public void subtract(float x, float y, float z) {
    add(-x, -y, -z);
  }

  /**
   * Adds the specified vector to this vector.
   */
  public void add(Vector3D v) {
    add(v.x, v.y, v.z);
  }

  /**
   * Subtracts the specified vector from this vector.
   */
  public void subtract(Vector3D v) {
    add(-v.x, -v.y, -v.z);
  }

  /**
   * Multiplies this vector by the specified value. The new length of this
   * vector will be length()*s.
   */
  public void multiply(float s) {
    x *= s;
    y *= s;
    z *= s;
  }

  /**
   * Divides this vector by the specified value. The new length of this vector
   * will be length()/s.
   */
  public void divide(float s) {
    x /= s;
    y /= s;
    z /= s;
  }

  /**
   * Returns the length of this vector as a float.
   */
  public float length() {
    return (floatMath.sqrt(x * x + y * y + z * z);
  }

  /**
   * Converts this Vector3D to a unit vector, or in other words, a vector of
   * length 1. Same as calling v.divide(v.length()).
   */
  public void normalize() {
    divide(length());
  }

  /**
   * Converts this Vector3D to a String representation.
   */
  public String toString() {
    return "(" + x + ", " + y + ", " + z + ")";
  }

  /**
   * Rotate this vector around the x axis the specified amount. The specified
   * angle is in radians. Use Math.toRadians() to convert from degrees to
   * radians.
   */
  public void rotateX(float angle) {
    rotateX((floatMath.cos(angle)(floatMath.sin(angle));
  }

  /**
   * Rotate this vector around the y axis the specified amount. The specified
   * angle is in radians. Use Math.toRadians() to convert from degrees to
   * radians.
   */
  public void rotateY(float angle) {
    rotateY((floatMath.cos(angle)(floatMath.sin(angle));
  }

  /**
   * Rotate this vector around the z axis the specified amount. The specified
   * angle is in radians. Use Math.toRadians() to convert from degrees to
   * radians.
   */
  public void rotateZ(float angle) {
    rotateZ((floatMath.cos(angle)(floatMath.sin(angle));
  }

  /**
   * Rotate this vector around the x axis the specified amount, using
   * pre-computed cosine and sine values of the angle to rotate.
   */
  public void rotateX(float cosAngle, float sinAngle) {
    float newY = y * cosAngle - z * sinAngle;
    float newZ = y * sinAngle + z * cosAngle;
    y = newY;
    z = newZ;
  }

  /**
   * Rotate this vector around the y axis the specified amount, using
   * pre-computed cosine and sine values of the angle to rotate.
   */
  public void rotateY(float cosAngle, float sinAngle) {
    float newX = z * sinAngle + x * cosAngle;
    float newZ = z * cosAngle - x * sinAngle;
    x = newX;
    z = newZ;
  }

  /**
   * Rotate this vector around the y axis the specified amount, using
   * pre-computed cosine and sine values of the angle to rotate.
   */
  public void rotateZ(float cosAngle, float sinAngle) {
    float newX = x * cosAngle - y * sinAngle;
    float newY = x * sinAngle + y * cosAngle;
    x = newX;
    y = newY;
  }

  /**
   * Adds the specified transform to this vector. This vector is first
   * rotated, then translated.
   */
  public void add(Transform3D xform) {

    // rotate
    addRotation(xform);

    // translate
    add(xform.getLocation());
  }

  /**
   * Subtracts the specified transform to this vector. This vector translated,
   * then rotated.
   */
  public void subtract(Transform3D xform) {

    // translate
    subtract(xform.getLocation());

    // rotate
    subtractRotation(xform);
  }

  /**
   * Rotates this vector with the angle of the specified transform.
   */
  public void addRotation(Transform3D xform) {
    rotateX(xform.getCosAngleX(), xform.getSinAngleX());
    rotateZ(xform.getCosAngleZ(), xform.getSinAngleZ());
    rotateY(xform.getCosAngleY(), xform.getSinAngleY());
  }

  /**
   * Rotates this vector with the opposite angle of the specified transform.
   */
  public void subtractRotation(Transform3D xform) {
    // note that sin(-x) == -sin(x) and cos(-x) == cos(x)
    rotateY(xform.getCosAngleY(), -xform.getSinAngleY());
    rotateZ(xform.getCosAngleZ(), -xform.getSinAngleZ());
    rotateX(xform.getCosAngleX(), -xform.getSinAngleX());
  }

  /**
   * Returns the dot product of this vector and the specified vector.
   */
  public float getDotProduct(Vector3D v) {
    return x * v.x + y * v.y + z * v.z;
  }

  /**
   * Sets this vector to the cross product of the two specified vectors.
   * Either of the specified vectors can be this vector.
   */
  public void setToCrossProduct(Vector3D u, Vector3D v) {
    // assign to local vars first in case u or v is 'this'
    float x = u.y * v.z - u.z * v.y;
    float y = u.z * v.x - u.x * v.z;
    float z = u.x * v.y - u.y * v.x;
    this.x = x;
    this.y = y;
    this.z = z;
  }

}

abstract class GameCore3D extends GameCore {

  private static final long INSTRUCTIONS_TIME = 4000;

  protected PolygonRenderer polygonRenderer;

  protected ViewWindow viewWindow;

  protected List polygons;

  private boolean drawFrameRate = false;

  private boolean drawInstructions = true;

  private long drawInstructionsTime = 0;

  // for calculating frame rate
  private int numFrames;

  private long startTime;

  private float frameRate;

  protected InputManager inputManager;

  private GameAction exit = new GameAction("exit");

  private GameAction smallerView = new GameAction("smallerView",
      GameAction.DETECT_INITAL_PRESS_ONLY);

  private GameAction largerView = new GameAction("largerView",
      GameAction.DETECT_INITAL_PRESS_ONLY);

  private GameAction frameRateToggle = new GameAction("frameRateToggle",
      GameAction.DETECT_INITAL_PRESS_ONLY);

  protected GameAction goForward = new GameAction("goForward");

  protected GameAction goBackward = new GameAction("goBackward");

  protected GameAction goUp = new GameAction("goUp");

  protected GameAction goDown = new GameAction("goDown");

  protected GameAction goLeft = new GameAction("goLeft");

  protected GameAction goRight = new GameAction("goRight");

  protected GameAction turnLeft = new GameAction("turnLeft");

  protected GameAction turnRight = new GameAction("turnRight");

  protected GameAction tiltUp = new GameAction("tiltUp");

  protected GameAction tiltDown = new GameAction("tiltDown");

  protected GameAction tiltLeft = new GameAction("tiltLeft");

  protected GameAction tiltRight = new GameAction("tiltRight");

  public void init(DisplayMode[] modes) {
    super.init(modes);

    inputManager = new InputManager(screen.getFullScreenWindow());
    inputManager.setRelativeMouseMode(true);
    inputManager.setCursor(InputManager.INVISIBLE_CURSOR);

    inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
    inputManager.mapToKey(goForward, KeyEvent.VK_W);
    inputManager.mapToKey(goForward, KeyEvent.VK_UP);
    inputManager.mapToKey(goBackward, KeyEvent.VK_S);
    inputManager.mapToKey(goBackward, KeyEvent.VK_DOWN);
    inputManager.mapToKey(goLeft, KeyEvent.VK_A);
    inputManager.mapToKey(goLeft, KeyEvent.VK_LEFT);
    inputManager.mapToKey(goRight, KeyEvent.VK_D);
    inputManager.mapToKey(goRight, KeyEvent.VK_RIGHT);
    inputManager.mapToKey(goUp, KeyEvent.VK_PAGE_UP);
    inputManager.mapToKey(goDown, KeyEvent.VK_PAGE_DOWN);
    inputManager.mapToMouse(turnLeft, InputManager.MOUSE_MOVE_LEFT);
    inputManager.mapToMouse(turnRight, InputManager.MOUSE_MOVE_RIGHT);
    inputManager.mapToMouse(tiltUp, InputManager.MOUSE_MOVE_UP);
    inputManager.mapToMouse(tiltDown, InputManager.MOUSE_MOVE_DOWN);

    inputManager.mapToKey(tiltLeft, KeyEvent.VK_INSERT);
    inputManager.mapToKey(tiltRight, KeyEvent.VK_DELETE);

    inputManager.mapToKey(smallerView, KeyEvent.VK_SUBTRACT);
    inputManager.mapToKey(smallerView, KeyEvent.VK_MINUS);
    inputManager.mapToKey(largerView, KeyEvent.VK_ADD);
    inputManager.mapToKey(largerView, KeyEvent.VK_PLUS);
    inputManager.mapToKey(largerView, KeyEvent.VK_EQUALS);
    inputManager.mapToKey(frameRateToggle, KeyEvent.VK_R);

    // create the polygon renderer
    createPolygonRenderer();

    // create polygons
    polygons = new ArrayList();
    createPolygons();
  }

  public abstract void createPolygons();

  public void createPolygonRenderer() {
    // make the view window the entire screen
    viewWindow = new ViewWindow(00, screen.getWidth(),
        screen.getHeight()(floatMath.toRadians(75));

    Transform3D camera = new Transform3D(01000);
    polygonRenderer = new SolidPolygonRenderer(camera, viewWindow);
  }

  /**
   * Sets the view bounds, centering the view on the screen.
   */
  public void setViewBounds(int width, int height) {
    width = Math.min(width, screen.getWidth());
    height = Math.min(height, screen.getHeight());
    width = Math.max(64, width);
    height = Math.max(48, height);
    viewWindow.setBounds((screen.getWidth() - width2(screen
        .getHeight() - height2, width, height);

    // clear the screen if view size changed
    // (clear both buffers)
    for (int i = 0; i < 2; i++) {
      Graphics2D g = screen.getGraphics();
      g.setColor(Color.BLACK);
      g.fillRect(00, screen.getWidth(), screen.getHeight());
      screen.update();
    }

  }

  public void update(long elapsedTime) {

    // check options
    if (exit.isPressed()) {
      stop();
      return;
    }
    if (largerView.isPressed()) {
      setViewBounds(viewWindow.getWidth() 64,
          viewWindow.getHeight() 48);
    else if (smallerView.isPressed()) {
      setViewBounds(viewWindow.getWidth() 64,
          viewWindow.getHeight() 48);
    }
    if (frameRateToggle.isPressed()) {
      drawFrameRate = !drawFrameRate;
    }

    drawInstructionsTime += elapsedTime;
    if (drawInstructionsTime >= INSTRUCTIONS_TIME) {
      drawInstructions = false;
    }
    updateWorld(elapsedTime);
  }

  public void updateWorld(long elapsedTime) {

    // cap elapsedTime
    elapsedTime = Math.min(elapsedTime, 100);

    float angleChange = 0.0002f * elapsedTime;
    float distanceChange = .5f * elapsedTime;

    Transform3D camera = polygonRenderer.getCamera();
    Vector3D cameraLoc = camera.getLocation();

    // apply movement
    if (goForward.isPressed()) {
      cameraLoc.x -= distanceChange * camera.getSinAngleY();
      cameraLoc.z -= distanceChange * camera.getCosAngleY();
    }
    if (goBackward.isPressed()) {
      cameraLoc.x += distanceChange * camera.getSinAngleY();
      cameraLoc.z += distanceChange * camera.getCosAngleY();
    }
    if (goLeft.isPressed()) {
      cameraLoc.x -= distanceChange * camera.getCosAngleY();
      cameraLoc.z += distanceChange * camera.getSinAngleY();
    }
    if (goRight.isPressed()) {
      cameraLoc.x += distanceChange * camera.getCosAngleY();
      cameraLoc.z -= distanceChange * camera.getSinAngleY();
    }
    if (goUp.isPressed()) {
      cameraLoc.y += distanceChange;
    }
    if (goDown.isPressed()) {
      cameraLoc.y -= distanceChange;
    }

    // look up/down (rotate around x)
    int tilt = tiltUp.getAmount() - tiltDown.getAmount();
    tilt = Math.min(tilt, 200);
    tilt = Math.max(tilt, -200);

    // limit how far you can look up/down
    float newAngleX = camera.getAngleX() + tilt * angleChange;
    newAngleX = Math.max(newAngleX, (float-Math.PI / 2);
    newAngleX = Math.min(newAngleX, (floatMath.PI / 2);
    camera.setAngleX(newAngleX);

    // turn (rotate around y)
    int turn = turnLeft.getAmount() - turnRight.getAmount();
    turn = Math.min(turn, 200);
    turn = Math.max(turn, -200);
    camera.rotateAngleY(turn * angleChange);

    // tilet head left/right (rotate around z)
    if (tiltLeft.isPressed()) {
      camera.rotateAngleZ(10 * angleChange);
    }
    if (tiltRight.isPressed()) {
      camera.rotateAngleZ(-10 * angleChange);
    }
  }

  public void draw(Graphics2D g) {
    int viewX1 = viewWindow.getLeftOffset();
    int viewY1 = viewWindow.getTopOffset();
    int viewX2 = viewX1 + viewWindow.getWidth();
    int viewY2 = viewY1 + viewWindow.getHeight();
    if (viewX1 != || viewY1 != 0) {
      g.setColor(Color.BLACK);
      g.fillRect(00, viewX1, screen.getHeight());
      g.fillRect(viewX2, 0, screen.getWidth() - viewX2, screen
          .getHeight());
      g.fillRect(viewX1, 0, viewWindow.getWidth(), viewY1);
      g.fillRect(viewX1, viewY2, viewWindow.getWidth(), screen
          .getHeight()
          - viewY2);
    }

    drawPolygons(g);
    drawText(g);
  }

  public void drawPolygons(Graphics2D g) {
    polygonRenderer.startFrame(g);
    for (int i = 0; i < polygons.size(); i++) {
      polygonRenderer.draw(g, (Polygon3Dpolygons.get(i));
    }
    polygonRenderer.endFrame(g);
  }

  public void drawText(Graphics2D g) {

    // draw text
    if (drawInstructions) {
      // fade out the text over 500 ms
      long fade = INSTRUCTIONS_TIME - drawInstructionsTime;
      if (fade < 500) {
        fade = fade * 255 500;
        g.setColor(new Color(0xffffff ((intfade << 24)true));
      else {
        g.setColor(Color.WHITE);
      }

      g.drawString("Use the mouse/arrow keys to move. "
          "Press Esc to exit."5, fontSize);
    }
    // (you may have to turn off the BufferStrategy in
    // ScreenManager for more accurate tests)
    if (drawFrameRate) {
      g.setColor(Color.WHITE);
      calcFrameRate();
      g.drawString(frameRate + " frames/sec"5, screen.getHeight() 5);
    }
  }

  public void calcFrameRate() {
    numFrames++;
    long currTime = System.currentTimeMillis();

    // calculate the frame rate every 500 milliseconds
    if (currTime > startTime + 500) {
      frameRate = (floatnumFrames * 1000 (currTime - startTime);
      startTime = currTime;
      numFrames = 0;
    }
  }

}

/**
 * The SolidPolygon3D class is a Polygon with a color.
 */

class SolidPolygon3D extends Polygon3D {

  private Color color = Color.GREEN;

  public SolidPolygon3D() {
    super();
  }

  public SolidPolygon3D(Vector3D v0, Vector3D v1, Vector3D v2) {
    this(new Vector3D[] { v0, v1, v2 });
  }

  public SolidPolygon3D(Vector3D v0, Vector3D v1, Vector3D v2, Vector3D v3) {
    this(new Vector3D[] { v0, v1, v2, v3 });
  }

  public SolidPolygon3D(Vector3D[] vertices) {
    super(vertices);
  }

  public void setTo(Polygon3D polygon) {
    super.setTo(polygon);
    if (polygon instanceof SolidPolygon3D) {
      color = ((SolidPolygon3Dpolygon).color;
    }
  }

  /**
   * Gets the color of this solid-colored polygon used for rendering this
   * polygon.
   */
  public Color getColor() {
    return color;
  }

  /**
   * Sets the color of this solid-colored polygon used for rendering this
   * polygon.
   */
  public void setColor(Color color) {
    this.color = color;
  }

}

/**
 * The SolidPolygonRenderer class transforms and draws solid-colored polygons
 * onto the screen.
 */

class SolidPolygonRenderer extends PolygonRenderer {

  public SolidPolygonRenderer(Transform3D camera, ViewWindow viewWindow) {
    this(camera, viewWindow, true);
  }

  public SolidPolygonRenderer(Transform3D camera, ViewWindow viewWindow,
      boolean clearViewEveryFrame) {
    super(camera, viewWindow, clearViewEveryFrame);
  }

  /**
   * Draws the current polygon. At this point, the current polygon is
   * transformed, clipped, projected, scan-converted, and visible.
   */
  protected void drawCurrentPolygon(Graphics2D g) {

    // set the color
    if (sourcePolygon instanceof SolidPolygon3D) {
      g.setColor(((SolidPolygon3DsourcePolygon).getColor());
    else {
      g.setColor(Color.GREEN);
    }

    // draw the scans
    int y = scanConverter.getTopBoundary();
    while (y <= scanConverter.getBottomBoundary()) {
      ScanConverter.Scan scan = scanConverter.getScan(y);
      if (scan.isValid()) {
        g.drawLine(scan.left, y, scan.right, y);
      }
      y++;
    }
  }
}

/**
 * The ScanConverter class converts a projected polygon into a series of
 * horizontal scans for drawing.
 */

class ScanConverter {

  private static final int SCALE_BITS = 16;

  private static final int SCALE = << SCALE_BITS;

  private static final int SCALE_MASK = SCALE - 1;

  protected ViewWindow view;

  protected Scan[] scans;

  protected int top;

  protected int bottom;

  /**
   * A horizontal scan line.
   */
  public static class Scan {
    public int left;

    public int right;

    /**
     * Sets the left and right boundary for this scan if the x value is
     * outside the current boundary.
     */
    public void setBoundary(int x) {
      if (x < left) {
        left = x;
      }
      if (x - > right) {
        right = x - 1;
      }
    }

    /**
     * Clears this scan line.
     */
    public void clear() {
      left = Integer.MAX_VALUE;
      right = Integer.MIN_VALUE;
    }

    /**
     * Determines if this scan is valid (if left <= right).
     */
    public boolean isValid() {
      return (left <= right);
    }

    /**
     * Sets this scan.
     */
    public void setTo(int left, int right) {
      this.left = left;
      this.right = right;
    }

    /**
     * Checks if this scan is equal to the specified values.
     */
    public boolean equals(int left, int right) {
      return (this.left == left && this.right == right);
    }
  }

  /**
   * Creates a new ScanConverter for the specified ViewWindow. The
   * ViewWindow's properties can change in between scan conversions.
   */
  public ScanConverter(ViewWindow view) {
    this.view = view;
  }

  /**
   * Gets the top boundary of the last scan-converted polygon.
   */
  public int getTopBoundary() {
    return top;
  }

  /**
   * Gets the bottom boundary of the last scan-converted polygon.
   */
  public int g