Collision 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 
Collision 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.geom.Line2D;
import java.awt.geom.Point2D;
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.Map;
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 CollisionTest extends ShooterCore {

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

  protected BSPTree bspTree;

  protected String mapFile;

  public CollisionTest(String[] args) {
    super(args);
    for (int i = 0; mapFile == null && i < args.length; i++) {
      if (mapFile == null && !args[i].startsWith("-")) {
        mapFile = args[i];
      }
    }
    if (mapFile == null) {
      mapFile = "../images/sample.map";
    }
  }

  public void createPolygons() {
    Graphics2D g = screen.getGraphics();
    g.setColor(Color.BLACK);
    g.fillRect(00, screen.getWidth(), screen.getHeight());
    g.setColor(Color.WHITE);
    g.drawString("Loading..."5, screen.getHeight() 5);
    screen.update();

    float ambientLightIntensity = .2f;
    List lights = new LinkedList();
    lights.add(new PointLight3D(-100100100.3f, -1));
    lights.add(new PointLight3D(1001000.3f, -1));

    MapLoader loader = new MapLoader();
    loader.setObjectLights(lights, ambientLightIntensity);

    try {
      bspTree = loader.loadMap(mapFile);
    catch (IOException ex) {
      ex.printStackTrace();
    }

    CollisionDetection collisionDetection = new CollisionDetection(bspTree);
    gameObjectManager = new GridGameObjectManager(bspTree.calcBounds(),
        collisionDetection);
    gameObjectManager.addPlayer(new GameObject(new PolygonGroup("Player")));

    // set up player bounds
    PolygonGroupBounds playerBounds = gameObjectManager.getPlayer()
        .getBounds();
    playerBounds.setTopHeight(Player.DEFAULT_PLAYER_HEIGHT);
    playerBounds.setRadius(Player.DEFAULT_PLAYER_RADIUS);

    ((BSPRendererpolygonRenderer).setGameObjectManager(gameObjectManager);

    createGameObjects(loader.getObjectsInMap());
    Transform3D start = loader.getPlayerStartLocation();
    gameObjectManager.getPlayer().getTransform().setTo(start);
  }

  private void createGameObjects(List mapObjects) {
    Iterator i = mapObjects.iterator();
    while (i.hasNext()) {
      PolygonGroup group = (PolygonGroupi.next();
      String filename = group.getFilename();
      if ("robot.obj".equals(filename)) {
        gameObjectManager.add(new Bot(group));
      else {
        // static object
        gameObjectManager.add(new GameObject(group));
      }
    }
  }

  public void drawPolygons(Graphics2D g) {

    polygonRenderer.startFrame(g);

    // draw polygons in bsp tree (set z buffer)
    ((BSPRendererpolygonRenderer).draw(g, bspTree);

    // draw game object polygons (check and set z buffer)
    gameObjectManager.draw(g, (GameObjectRendererpolygonRenderer);

    polygonRenderer.endFrame(g);

  }
}

/**
 * A GameObject that can jump.
 */

class JumpingGameObject extends GameObject {

  public static final float DEFAULT_JUMP_HEIGHT = 64;

  protected float jumpVelocity;

  public JumpingGameObject(PolygonGroup group) {
    super(group);
    setJumpHeight(DEFAULT_JUMP_HEIGHT);
  }

  /**
   * Sets how high this GameObject can jump.
   */
  public void setJumpHeight(float jumpHeight) {
    jumpVelocity = Physics.getInstance().getJumpVelocity(jumpHeight);
  }

  /**
   * Causes this GameObject to jump if the jumping flag is set and this object
   * is not already jumping.
   */
  public void setJumping(boolean isJumping) {
    if (isJumping() != isJumping) {
      super.setJumping(isJumping);
      if (isJumping) {
        Physics.getInstance().jump(this, jumpVelocity);
      }
    }
  }

  public void notifyFloorCollision() {
    // the object has landed.
    setJumping(false);
  }

}
/**
 * The Physics class is a singleton that represents various attributes (like
 * gravity) and the functions to manipulate objects based on those physical
 * attributes. Currently, only gravity and scoot-up (acceleration when traveling
 * up stairs) are supported.
 */

class Physics {

  /**
   * Default gravity in units per millisecond squared
   */
  public static final float DEFAULT_GRAVITY_ACCEL = -.002f;

  /**
   * Default scoot-up (acceleration traveling up stairs) in units per
   * millisecond squared.
   */
  public static final float DEFAULT_SCOOT_ACCEL = .006f;

  private static Physics instance;

  private float gravityAccel;

  private float scootAccel;

  private Vector3D velocity = new Vector3D();

  /**
   * Gets the Physics instance. If a Physics instance does not yet exist, one
   * is created with the default attributes.
   */
  public static synchronized Physics getInstance() {
    if (instance == null) {
      instance = new Physics();
    }
    return instance;
  }

  protected Physics() {
    gravityAccel = DEFAULT_GRAVITY_ACCEL;
    scootAccel = DEFAULT_SCOOT_ACCEL;
  }

  /**
   * Gets the gravity acceleration in units per millisecond squared.
   */
  public float getGravityAccel() {
    return gravityAccel;
  }

  /**
   * Sets the gravity acceleration in units per millisecond squared.
   */
  public void setGravityAccel(float gravityAccel) {
    this.gravityAccel = gravityAccel;
  }

  /**
   * Gets the scoot-up acceleration in units per millisecond squared. The
   * scoot up acceleration can be used for smoothly traveling up stairs.
   */
  public float getScootAccel() {
    return scootAccel;
  }

  /**
   * Sets the scoot-up acceleration in units per millisecond squared. The
   * scoot up acceleration can be used for smoothly traveling up stairs.
   */
  public void setScootAccel(float scootAccel) {
    this.scootAccel = scootAccel;
  }

  /**
   * Applies gravity to the specified GameObject according to the amount of
   * time that has passed.
   */
  public void applyGravity(GameObject object, long elapsedTime) {
    velocity.setTo(0, gravityAccel * elapsedTime, 0);
    object.getTransform().addVelocity(velocity);
  }

  /**
   * Applies the scoot-up acceleration to the specified GameObject according
   * to the amount of time that has passed.
   */
  public void scootUp(GameObject object, long elapsedTime) {
    velocity.setTo(0, scootAccel * elapsedTime, 0);
    object.getTransform().addVelocity(velocity);
  }

  /**
   * Applies the negative scoot-up acceleration to the specified GameObject
   * according to the amount of time that has passed.
   */
  public void scootDown(GameObject object, long elapsedTime) {
    velocity.setTo(0, -scootAccel * elapsedTime, 0);
    object.getTransform().addVelocity(velocity);
  }

  /**
   * Sets the specified GameObject's vertical velocity to jump to the
   * specified height. Calls getJumpVelocity() to calculate the velocity,
   * which uses the Math.sqrt() function.
   */
  public void jumpToHeight(GameObject object, float jumpHeight) {
    jump(object, getJumpVelocity(jumpHeight));
  }

  /**
   * Sets the specified GameObject's vertical velocity to the specified jump
   * velocity.
   */
  public void jump(GameObject object, float jumpVelocity) {
    velocity.setTo(0, jumpVelocity, 0);
    object.getTransform().getVelocity().y = 0;
    object.getTransform().addVelocity(velocity);
  }

  /**
   * Returns the vertical velocity needed to jump the specified height (based
   * on current gravity). Uses the Math.sqrt() function.
   */
  public float getJumpVelocity(float jumpHeight) {
    // use velocity/acceleration formal: v*v = -2 * a(y-y0)
    // (v is jump velocity, a is accel, y-y0 is max height)
    return (floatMath.sqrt(-* gravityAccel * jumpHeight);
  }
}

/**
 * A Player object.
 */

class Player extends JumpingGameObject {

  public static final float DEFAULT_PLAYER_RADIUS = 32;

  public static final float DEFAULT_PLAYER_HEIGHT = 128;

  public Player() {
    this(new PolygonGroup("Player"));

    // set up player bounds
    PolygonGroupBounds playerBounds = getBounds();
    playerBounds.setTopHeight(DEFAULT_PLAYER_HEIGHT);
    playerBounds.setRadius(DEFAULT_PLAYER_RADIUS);
  }

  public Player(PolygonGroup group) {
    super(group);
  }

}

/**
 * The GridGameObjectManager is a GameObjectManager that integrally arranges
 * GameObjects on a 2D grid for visibility determination and to limit the number
 * of tests for collision detection.
 */

class GridGameObjectManager implements GameObjectManager {

  /**
   * Default grid size of 512. The grid size should be larger than the largest
   * object's diameter.
   */
  private static final int GRID_SIZE_BITS = 9;

  private static final int GRID_SIZE = << GRID_SIZE_BITS;

  /**
   * The Cell class represents a cell in the grid. It contains a list of game
   * objects and a visible flag.
   */
  private static class Cell {
    List objects;

    boolean visible;

    Cell() {
      objects = new ArrayList();
      visible = false;
    }
  }

  private Cell[] grid;

  private Rectangle mapBounds;

  private int gridWidth;

  private int gridHeight;

  private List allObjects;

  private GameObject player;

  private Vector3D oldLocation;

  private CollisionDetection collisionDetection;

  /**
   * Creates a new GridGameObjectManager with the specified map bounds and
   * collision detection handler. GameObjects outside the map bounds will
   * never be shown.
   */
  public GridGameObjectManager(Rectangle mapBounds,
      CollisionDetection collisionDetection) {
    this.mapBounds = mapBounds;
    this.collisionDetection = collisionDetection;
    gridWidth = (mapBounds.width >> GRID_SIZE_BITS1;
    gridHeight = (mapBounds.height >> GRID_SIZE_BITS1;
    grid = new Cell[gridWidth * gridHeight];
    for (int i = 0; i < grid.length; i++) {
      grid[inew Cell();
    }
    allObjects = new ArrayList();
    oldLocation = new Vector3D();
  }

  /**
   * Converts a map x-coordinate to a grid x-coordinate.
   */
  private int convertMapXtoGridX(int x) {
    return (x - mapBounds.x>> GRID_SIZE_BITS;
  }

  /**
   * Converts a map y-coordinate to a grid y-coordinate.
   */
  private int convertMapYtoGridY(int y) {
    return (y - mapBounds.y>> GRID_SIZE_BITS;
  }

  /**
   * Marks all objects as potentially visible (should be drawn).
   */
  public void markAllVisible() {
    for (int i = 0; i < grid.length; i++) {
      grid[i].visible = true;
    }
  }

  /**
   * Marks all objects within the specified 2D bounds as potentially visible
   * (should be drawn).
   */
  public void markVisible(Rectangle bounds) {
    int x1 = Math.max(0, convertMapXtoGridX(bounds.x));
    int y1 = Math.max(0, convertMapYtoGridY(bounds.y));
    int x2 = Math.min(gridWidth - 1, convertMapXtoGridX(bounds.x
        + bounds.width));
    int y2 = Math.min(gridHeight - 1, convertMapYtoGridY(bounds.y
        + bounds.height));

    for (int y = y1; y <= y2; y++) {
      int offset = y * gridWidth;
      for (int x = x1; x <= x2; x++) {
        grid[offset + x].visible = true;
      }
    }
  }

  /**
   * Adds a GameObject to this manager.
   */
  public void add(GameObject object) {
    if (object != null) {
      if (object == player) {
        // ensure player always moves first
        allObjects.add(0, object);
      else {
        allObjects.add(object);
      }
      Cell cell = getCell(object);
      if (cell != null) {
        cell.objects.add(object);
      }

    }
  }

  /**
   * Removes a GameObject from this manager.
   */
  public void remove(GameObject object) {
    if (object != null) {
      allObjects.remove(object);
      Cell cell = getCell(object);
      if (cell != null) {
        cell.objects.remove(object);
      }
    }
  }

  /**
   * Adds a GameObject to this manager, specifying it as the player object. An
   * existing player object, if any, is not removed.
   */
  public void addPlayer(GameObject player) {
    this.player = player;
    if (player != null) {
      player.notifyVisible(true);
      add(player);
    }
  }

  /**
   * Gets the object specified as the Player object, or null if no player
   * object was specified.
   */
  public GameObject getPlayer() {
    return player;
  }

  /**
   * Gets the cell the specified GameObject is in, or null if the GameObject
   * is not within the map bounds.
   */
  private Cell getCell(GameObject object) {
    int x = convertMapXtoGridX((intobject.getX());
    int y = convertMapYtoGridY((intobject.getZ());
    return getCell(x, y);
  }

  /**
   * Gets the cell of the specified grid location, or null if the grid
   * location is invalid.
   */
  private Cell getCell(int x, int y) {

    // check bounds
    if (x < || y < || x >= gridWidth || y >= gridHeight) {
      return null;
    }

    // get the cell at the x,y location
    return grid[x + y * gridWidth];
  }

  /**
   * Updates all objects based on the amount of time passed from the last
   * update and applied collision detection.
   */
  public void update(long elapsedTime) {
    for (int i = 0; i < allObjects.size(); i++) {
      GameObject object = (GameObjectallObjects.get(i);

      // save the object's old position
      Cell oldCell = getCell(object);
      oldLocation.setTo(object.getLocation());

      // move the object
      object.update(player, elapsedTime);

      // remove the object if destroyed
      if (object.isDestroyed()) {
        allObjects.remove(i);
        i--;
        if (oldCell != null) {
          oldCell.objects.remove(object);
        }
        continue;
      }

      // if the object moved, do collision detection
      if (!object.getLocation().equals(oldLocation)) {

        // check walls, floors, and ceilings
        collisionDetection.checkBSP(object, oldLocation, elapsedTime);

        // check other objects
        if (checkObjectCollision(object, oldLocation)) {
          // revert to old position
          object.getLocation().setTo(oldLocation);
        }

        // update grid location
        Cell cell = getCell(object);
        if (cell != oldCell) {
          if (oldCell != null) {
            oldCell.objects.remove(object);
          }
          if (cell != null) {
            cell.objects.add(object);
          }
        }
      }

    }
  }

  /**
   * Checks to see if the specified object collides with any other object.
   */
  public boolean checkObjectCollision(GameObject object, Vector3D oldLocation) {

    boolean collision = false;

    // use the object's (x,z) position (ground plane)
    int x = convertMapXtoGridX((intobject.getX());
    int y = convertMapYtoGridY((intobject.getZ());

    // check the object's surrounding 9 cells
    for (int i = x - 1; i <= x + 1; i++) {
      for (int j = y - 1; j <= y + 1; j++) {
        Cell cell = getCell(i, j);
        if (cell != null) {
          collision |= collisionDetection.checkObject(object,
              cell.objects, oldLocation);
        }
      }
    }

    return collision;
  }

  /**
   * Draws all visible objects and marks all objects as not visible.
   */
  public void draw(Graphics2D g, GameObjectRenderer r) {
    for (int i = 0; i < grid.length; i++) {
      List objects = grid[i].objects;
      for (int j = 0; j < objects.size(); j++) {
        GameObject object = (GameObjectobjects.get(j);
        boolean visible = false;
        if (grid[i].visible) {
          visible = r.draw(g, object);
        }
        if (object != player) {
          // notify objects if they are visible
          object.notifyVisible(visible);
        }
      }
      grid[i].visible = false;
    }
  }
}

/**
 * The CollisionDetection class handles collision detection between the
 * GameObjects, and between GameObjects and a BSP tree. When a collision occurs,
 * the GameObject stops.
 */

class CollisionDetection {

  /**
   * Bounding game object corners used to test for intersection with the BSP
   * tree. Corners are in either clockwise or counter-clockwise order.
   */
  private static final Point2D.Float[] CORNERS = new Point2D.Float(-1, -1),
      new Point2D.Float(-11)new Point2D.Float(11),
      new Point2D.Float(1, -1)};

  private BSPTree bspTree;

  private BSPLine path;

  private Point2D.Float intersection;

  /**
   * Creates a new CollisionDetection object for the specified BSP tree.
   */
  public CollisionDetection(BSPTree bspTree) {
    this.bspTree = bspTree;
    path = new BSPLine();
    intersection = new Point2D.Float();
  }

  /**
   * Checks a GameObject against the BSP tree. Returns true if a wall
   * collision occurred.
   */
  public boolean checkBSP(GameObject object, Vector3D oldLocation,
      long elapsedTime) {

    boolean wallCollision = false;

    // check walls if x or z position changed
    if (object.getX() != oldLocation.x || object.getZ() != oldLocation.z) {
      wallCollision = (checkWalls(object, oldLocation, elapsedTime!= null);
    }

    getFloorAndCeiling(object);
    checkFloorAndCeiling(object, elapsedTime);

    return wallCollision;
  }

  /**
   * Gets the floor and ceiling values for the specified GameObject. Calls
   * object.setFloorHeight() and object.setCeilHeight() to set the floor and
   * ceiling values.
   */
  public void getFloorAndCeiling(GameObject object) {
    float x = object.getX();
    float z = object.getZ();
    float r = object.getBounds().getRadius() 1;
    float floorHeight = Float.MIN_VALUE;
    float ceilHeight = Float.MAX_VALUE;
    BSPTree.Leaf leaf = bspTree.getLeaf(x, z);
    if (leaf != null) {
      floorHeight = leaf.floorHeight;
      ceilHeight = leaf.ceilHeight;
    }

    // check surrounding four points
    for (int i = 0; i < CORNERS.length; i++) {
      float xOffset = r * CORNERS[i].x;
      float zOffset = r * CORNERS[i].y;
      leaf = bspTree.getLeaf(x + xOffset, z + zOffset);
      if (leaf != null) {
        floorHeight = Math.max(floorHeight, leaf.floorHeight);
        ceilHeight = Math.min(ceilHeight, leaf.ceilHeight);
      }
    }

    object.setFloorHeight(floorHeight);
    object.setCeilHeight(ceilHeight);
  }

  /**
   * Checks for object collisions with the floor and ceiling. Uses
   * object.getFloorHeight() and object.getCeilHeight() for the floor and
   * ceiling values.
   */
  protected void checkFloorAndCeiling(GameObject object, long elapsedTime) {
    boolean collision = false;

    float floorHeight = object.getFloorHeight();
    float ceilHeight = object.getCeilHeight();
    float bottomHeight = object.getBounds().getBottomHeight();
    float topHeight = object.getBounds().getTopHeight();

    if (!object.isFlying()) {
      object.getLocation().y = floorHeight - bottomHeight;
    }
    // check if below floor
    if (object.getY() + bottomHeight < floorHeight) {
      object.notifyFloorCollision();
      object.getTransform().getVelocity().y = 0;
      object.getLocation().y = floorHeight - bottomHeight;
    }
    // check if hitting ceiling
    else if (object.getY() + topHeight > ceilHeight) {
      object.notifyCeilingCollision();
      object.getTransform().getVelocity().y = 0;
      object.getLocation().y = ceilHeight - topHeight;
    }

  }

  /**
   * Checks for a game object collision with the walls of the BSP tree.
   * Returns the first wall collided with, or null if there was no collision.
   */
  public BSPPolygon checkWalls(GameObject object, Vector3D oldLocation,
      long elapsedTime) {
    Vector3D v = object.getTransform().getVelocity();
    PolygonGroupBounds bounds = object.getBounds();
    float x = object.getX();
    float y = object.getY();
    float z = object.getZ();
    float r = bounds.getRadius();
    float stepSize = 0;
    if (!object.isFlying()) {
      stepSize = BSPPolygon.PASSABLE_WALL_THRESHOLD;
    }
    float bottom = object.getY() + bounds.getBottomHeight() + stepSize;
    float top = object.getY() + bounds.getTopHeight();

    // pick closest intersection of 4 corners
    BSPPolygon closestWall = null;
    float closestDistSq = Float.MAX_VALUE;
    for (int i = 0; i < CORNERS.length; i++) {
      float xOffset = r * CORNERS[i].x;
      float zOffset = r * CORNERS[i].y;
      BSPPolygon wall = getFirstWallIntersection(oldLocation.x + xOffset,
          oldLocation.z + zOffset, x + xOffset, z + zOffset, bottom,
          top);
      if (wall != null) {
        float x2 = intersection.x - xOffset;
        float z2 = intersection.y - zOffset;
        float dx = (x2 - oldLocation.x);
        float dz = (z2 - oldLocation.z);
        float distSq = dx * dx + dz * dz;
        // pick the wall with the closest distance, or
        // if the distances are equal, pick the current
        // wall if the offset has the same sign as the
        // velocity.
        if (distSq < closestDistSq
            || (distSq == closestDistSq
                && MoreMath.sign(xOffset== MoreMath.sign(v.x&& MoreMath
                .sign(zOffset== MoreMath.sign(v.z))) {
          closestWall = wall;
          closestDistSq = distSq;
          object.getLocation().setTo(x2, y, z2);
        }
      }
    }

    if (closestWall != null) {
      object.notifyWallCollision();
    }

    // make sure the player bounds is empty
    // (avoid colliding with sharp corners)
    x = object.getX();
    z = object.getZ();
    r -= 1;
    for (int i = 0; i < CORNERS.length; i++) {
      int next = i + 1;
      if (next == CORNERS.length) {
        next = 0;
      }
      // use (r-1) so this doesn't interfere with normal
      // collisions
      float xOffset1 = r * CORNERS[i].x;
      float zOffset1 = r * CORNERS[i].y;
      float xOffset2 = r * CORNERS[next].x;
      float zOffset2 = r * CORNERS[next].y;

      BSPPolygon wall = getFirstWallIntersection(x + xOffset1, z
          + zOffset1, x + xOffset2, z + zOffset2, bottom, top);
      if (wall != null) {
        object.notifyWallCollision();
        object.getLocation().setTo(oldLocation.x, object.getY(),
            oldLocation.z);
        return wall;
      }
    }

    return closestWall;
  }

  /**
   * Gets the first intersection, if any, of the path (x1,z1)-> (x2,z2) with
   * the walls of the BSP tree. Returns the first BSPPolygon intersection, or
   * null if no intersection occurred.
   */
  public BSPPolygon getFirstWallIntersection(float x1, float z1, float x2,
      float z2, float yBottom, float yTop) {
    return getFirstWallIntersection(bspTree.getRoot(), x1, z1, x2, z2,
        yBottom, yTop);
  }

  /**
   * Gets the first intersection, if any, of the path (x1,z1)-> (x2,z2) with
   * the walls of the BSP tree, starting with the specified node. Returns the
   * first BSPPolyon intersection, or null if no intersection occurred.
   */
  protected BSPPolygon getFirstWallIntersection(BSPTree.Node node, float x1,
      float z1, float x2, float z2, float yBottom, float yTop) {
    if (node == null || node instanceof BSPTree.Leaf) {
      return null;
    }

    int start = node.partition.getSideThick(x1, z1);
    int end = node.partition.getSideThick(x2, z2);
    float intersectionX;
    float intersectionZ;

    if (end == BSPLine.COLLINEAR) {
      end = start;
    }

    if (start == BSPLine.COLLINEAR) {
      intersectionX = x1;
      intersectionZ = z1;
    else if (start != end) {
      path.setLine(x1, z1, x2, z2);
      node.partition.getIntersectionPoint(path, intersection);
      intersectionX = intersection.x;
      intersectionZ = intersection.y;
    else {
      intersectionX = x2;
      intersectionZ = z2;
    }

    if (start == BSPLine.COLLINEAR && start == end) {
      return null;
    }

    // check front part of line
    if (start != BSPLine.COLLINEAR) {
      BSPPolygon wall = getFirstWallIntersection(
          (start == BSPLine.FRONT? node.front : node.back, x1, z1,
          intersectionX, intersectionZ, yBottom, yTop);
      if (wall != null) {
        return wall;
      }
    }

    // test this boundary
    if (start != end || start == BSPLine.COLLINEAR) {
      BSPPolygon wall = getWallCollision(node.polygons, x1, z1, x2, z2,
          yBottom, yTop);
      if (wall != null) {
        intersection.setLocation(intersectionX, intersectionZ);
        return wall;
      }
    }

    // check back part of line
    if (start != end) {
      BSPPolygon wall = getFirstWallIntersection(
          (end == BSPLine.FRONT? node.front : node.back,
          intersectionX, intersectionZ, x2, z2, yBottom, yTop);
      if (wall != null) {
        return wall;
      }
    }

    // not found
    return null;
  }

  /**
   * Checks if the specified path collides with any of the collinear list of
   * polygons. The path crosses the line represented by the polygons, but the
   * polygons may not necessarily cross the path.
   */
  protected BSPPolygon getWallCollision(List polygons, float x1, float z1,
      float x2, float z2, float yBottom, float yTop) {
    path.setLine(x1, z1, x2, z2);
    for (int i = 0; i < polygons.size(); i++) {
      BSPPolygon poly = (BSPPolygonpolygons.get(i);
      BSPLine wall = poly.getLine();

      // check if not wall
      if (wall == null) {
        continue;
      }

      // check if not vertically in the wall (y axis)
      if (wall.top <= yBottom || wall.bottom > yTop) {
        continue;
      }

      // check if moving to back of wall
      if (wall.getSideThin(x2, z2!= BSPLine.BACK) {
        continue;
      }

      // check if path crosses wall
      int side1 = path.getSideThin(wall.x1, wall.y1);
      int side2 = path.getSideThin(wall.x2, wall.y2);
      if (side1 != side2) {
        return poly;
      }
    }
    return null;
  }

  /**
   * Checks if the specified object collisions with any other object in the
   * specified list.
   */
  public boolean checkObject(GameObject objectA, List objects,
      Vector3D oldLocation) {
    boolean collision = false;
    for (int i = 0; i < objects.size(); i++) {
      GameObject objectB = (GameObjectobjects.get(i);
      collision |= checkObject(objectA, objectB, oldLocation);
    }
    return collision;
  }

  /**
   * Returns true if the two specified objects collide. Object A is the moving
   * object, and Object B is the object to check. Uses bounding upright
   * cylinders (circular base and top) to determine collisions.
   */
  public boolean checkObject(GameObject objectA, GameObject objectB,
      Vector3D oldLocation) {
    // don't collide with self
    if (objectA == objectB) {
      return false;
    }

    PolygonGroupBounds boundsA = objectA.getBounds();
    PolygonGroupBounds boundsB = objectB.getBounds();

    // first, check y axis collision (assume height is pos)
    float Ay1 = objectA.getY() + boundsA.getBottomHeight();
    float Ay2 = objectA.getY() + boundsA.getTopHeight();
    float By1 = objectB.getY() + boundsB.getBottomHeight();
    float By2 = objectB.getY() + boundsB.getTopHeight();
    if (By2 < Ay1 || By1 > Ay2) {
      return false;
    }

    // next, check 2D, x/z plane collision (circular base)
    float dx = objectA.getX() - objectB.getX();
    float dz = objectA.getZ() - objectB.getZ();
    float minDist = boundsA.getRadius() + boundsB.getRadius();
    float distSq = dx * dx + dz * dz;
    float minDistSq = minDist * minDist;
    if (distSq < minDistSq) {
      return handleObjectCollision(objectA, objectB, distSq, minDistSq,
          oldLocation);
    }
    return false;
  }

  /**
   * Handles an object collision. Object A is the moving object, and Object B
   * is the object that Object A collided with.
   */
  protected boolean handleObjectCollision(GameObject objectA,
      GameObject objectB, float distSq, float minDistSq,
      Vector3D oldLocation) {
    objectA.notifyObjectCollision(objectB);
    return true;
  }

}

/**
 * The PolygonGroupBounds represents a cylinder bounds around a PolygonGroup
 * that can be used for collision detection.
 */

class PolygonGroupBounds {

  private float topHeight;

  private float bottomHeight;

  private float radius;

  /**
   * Creates a new PolygonGroupBounds with no bounds.
   */
  public PolygonGroupBounds() {

  }

  /**
   * Creates a new PolygonGroupBounds with the bounds of the specified
   * PolygonGroup.
   */
  public PolygonGroupBounds(PolygonGroup group) {
    setToBounds(group);
  }

  /**
   * Sets this to the bounds of the specified PolygonGroup.
   */
  public void setToBounds(PolygonGroup group) {
    topHeight = Float.MIN_VALUE;
    bottomHeight = Float.MAX_VALUE;
    radius = 0;

    group.resetIterator();
    while (group.hasNext()) {
      Polygon3D poly = group.nextPolygon();
      for (int i = 0; i < poly.getNumVertices(); i++) {
        Vector3D v = poly.getVertex(i);
        topHeight = Math.max(topHeight, v.y);
        bottomHeight = Math.min(bottomHeight, v.y);
        // compute radius squared
        radius = Math.max(radius, v.x * v.x + v.z * v.z);
      }
    }

    if (radius == 0) {
      // empty polygon group!
      topHeight = 0;
      bottomHeight = 0;
    else {
      radius = (floatMath.sqrt(radius);
    }
  }

  public float getTopHeight() {
    return topHeight;
  }

  public void setTopHeight(float topHeight) {
    this.topHeight = topHeight;
  }

  public float getBottomHeight() {
    return bottomHeight;
  }

  public void setBottomHeight(float bottomHeight) {
    this.bottomHeight = bottomHeight;
  }

  public float getRadius() {
    return radius;
  }

  public void setRadius(float radius) {
    this.radius = radius;
  }

}
/**
 * The PolygonGroup is a group of polygons with a MovingTransform3D.
 * PolygonGroups can also contain other PolygonGroups.
 */

class PolygonGroup implements Transformable {

  private String name;

  private String filename;

  private List objects;

  private MovingTransform3D transform;

  private int iteratorIndex;

  /**
   * Creates a new, empty PolygonGroup.
   */
  public PolygonGroup() {
    this("unnamed");
  }

  /**
   * Creates a new, empty PolygonGroup with te specified name.
   */
  public PolygonGroup(String name) {
    setName(name);
    objects = new ArrayList();
    transform = new MovingTransform3D();
    iteratorIndex = 0;
  }

  /**
   * Gets the MovingTransform3D for this PolygonGroup.
   */
  public MovingTransform3D getTransform() {
    return transform;
  }

  /**
   * Gets the name of this PolygonGroup.
   */
  public String getName() {
    return name;
  }

  /**
   * Sets the name of this PolygonGroup.
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Gets the filename of this PolygonGroup.
   */
  public String getFilename() {
    return filename;
  }

  /**
   * Sets the filename of this PolygonGroup.
   */
  public void setFilename(String filename) {
    this.filename = filename;
  }

  /**
   * Adds a polygon to this group.
   */
  public void addPolygon(Polygon3D o) {
    objects.add(o);
  }

  /**
   * Adds a PolygonGroup to this group.
   */
  public void addPolygonGroup(PolygonGroup p) {
    objects.add(p);
  }

  /**
   * Clones this polygon group. Polygon3Ds are shared between this group and
   * the cloned group; Transform3Ds are copied.
   */
  public Object clone() {
    PolygonGroup group = new PolygonGroup(name);
    group.setFilename(filename);
    for (int i = 0; i < objects.size(); i++) {
      Object obj = objects.get(i);
      if (obj instanceof Polygon3D) {
        group.addPolygon((Polygon3Dobj);
      else {
        PolygonGroup grp = (PolygonGroupobj;
        group.addPolygonGroup((PolygonGroupgrp.clone());
      }
    }
    group.transform = (MovingTransform3Dtransform.clone();
    return group;
  }

  /**
   * Gets the PolygonGroup in this group with the specified name, or null if
   * none found.
   */
  public PolygonGroup getGroup(String name) {
    // check for this group
    if (this.name != null && this.name.equals(name)) {
      return this;
    }
    for (int i = 0; i < objects.size(); i++) {
      Object obj = objects.get(i);
      if (obj instanceof PolygonGroup) {
        PolygonGroup subgroup = ((PolygonGroupobj).getGroup(name);
        if (subgroup != null) {
          return subgroup;
        }
      }
    }

    // group not found
    return null;
  }

  /**
   * Resets the polygon iterator for this group.
   
   @see #hasNext
   @see #nextPolygon
   */
  public void resetIterator() {
    iteratorIndex = 0;
    for (int i = 0; i < objects.size(); i++) {
      Object obj = objects.get(i);
      if (obj instanceof PolygonGroup) {
        ((PolygonGroupobj).resetIterator();
      }
    }
  }

  /**
   * Checks if there is another polygon in the current iteration.
   
   @see #resetIterator
   @see #nextPolygon
   */
  public boolean hasNext() {
    return (iteratorIndex < objects.size());
  }

  /**
   * Gets the next polygon in the current iteration.
   
   @see #resetIterator
   @see #hasNext
   */
  public Polygon3D nextPolygon() {
    Object obj = objects.get(iteratorIndex);

    if (obj instanceof PolygonGroup) {
      PolygonGroup group = (PolygonGroupobj;
      Polygon3D poly = group.nextPolygon();
      if (!group.hasNext()) {
        iteratorIndex++;
      }
      return poly;
    else {
      iteratorIndex++;
      return (Polygon3Dobj;
    }
  }

  /**
   * Gets the next polygon in the current iteration, applying the
   * MovingTransform3Ds to it, and storing it in 'cache'.
   */
  public void nextPolygonTransformed(Polygon3D cache) {
    Object obj = objects.get(iteratorIndex);

    if (obj instanceof PolygonGroup) {
      PolygonGroup group = (PolygonGroupobj;
      group.nextPolygonTransformed(cache);
      if (!group.hasNext()) {
        iteratorIndex++;
      }
    else {
      iteratorIndex++;
      cache.setTo((Polygon3Dobj);
    }

    cache.add(transform);
  }

  /**
   * Updates the MovingTransform3Ds of this group and any subgroups.
   */
  public void update(long elapsedTime) {
    transform.update(elapsedTime);
    for (int i = 0; i < objects.size(); i++) {
      Object obj = objects.get(i);
      if (obj instanceof PolygonGroup) {
        PolygonGroup group = (PolygonGroupobj;
        group.update(elapsedTime);
      }
    }
  }

  // from the Transformable interface

  public void add(Vector3D u) {
    transform.getLocation().add(u);
  }

  public void subtract(Vector3D u) {
    transform.getLocation().subtract(u);
  }

  public void add(Transform3D xform) {
    addRotation(xform);
    add(xform.getLocation());
  }

  public void subtract(Transform3D xform) {
    subtract(xform.getLocation());
    subtractRotation(xform);
  }

  public void addRotation(Transform3D xform) {
    transform.rotateAngleX(xform.getAngleX());
    transform.rotateAngleY(xform.getAngleY());
    transform.rotateAngleZ(xform.getAngleZ());
  }

  public void subtractRotation(Transform3D xform) {
    transform.rotateAngleX(-xform.getAngleX());
    transform.rotateAngleY(-xform.getAngleY());
    transform.rotateAngleZ(-xform.getAngleZ());
  }

}

/**
 * The PolygonRenderer class is an abstract class that transforms and draws
 * polygons onto the screen.
 */

abstract class PolygonRenderer {

  protected ScanConverter scanConverter;

  protected Transform3D camera;

  protected ViewWindow viewWindow;

  protected boolean clearViewEveryFrame;

  protected Polygon3D sourcePolygon;

  protected Polygon3D destPolygon;

  /**
   * Creates a new PolygonRenderer with the specified Transform3D (camera) and
   * ViewWindow. The view is cleared when startFrame() is called.
   */
  public PolygonRenderer(Transform3D camera, ViewWindow viewWindow) {
    this(camera, viewWindow, true);
  }

  /**
   * Creates a new PolygonRenderer with the specified Transform3D (camera) and
   * ViewWindow. If clearViewEveryFrame is true, the view is cleared when
   * startFrame() is called.
   */
  public PolygonRenderer(Transform3D camera, ViewWindow viewWindow,
      boolean clearViewEveryFrame) {
    this.camera = camera;
    this.viewWindow = viewWindow;
    this.clearViewEveryFrame = clearViewEveryFrame;
    init();
  }

  /**
   * Create the scan converter and dest polygon.
   */
  protected void init() {
    destPolygon = new Polygon3D();
    scanConverter = new ScanConverter(viewWindow);
  }

  /**
   * Gets the camera used for this PolygonRenderer.
   */
  public Transform3D getCamera() {
    return camera;
  }

  /**
   * Indicates the start of rendering of a frame. This method should be called
   * every frame before any polygons are drawn.
   */
  public void startFrame(Graphics2D g) {
    if (clearViewEveryFrame) {
      g.setColor(Color.black);
      g.fillRect(viewWindow.getLeftOffset(), viewWindow.getTopOffset(),
          viewWindow.getWidth(), viewWindow.getHeight());
    }
  }

  /**
   * Indicates the end of rendering of a frame. This method should be called
   * every frame after all polygons are drawn.
   */
  public void endFrame(Graphics2D g) {
    // do nothing, for now.
  }

  /**
   * Transforms and draws a polygon.
   */
  public boolean draw(Graphics2D g, Polygon3D poly) {
    if (poly.isFacing(camera.getLocation())) {
      sourcePolygon = poly;
      destPolygon.setTo(poly);
      destPolygon.subtract(camera);
      boolean visible = destPolygon.clip(-1);
      if (visible) {
        destPolygon.project(viewWindow);
        visible = scanConverter.convert(destPolygon);
        if (visible) {
          drawCurrentPolygon(g);
          return true;
        }
      }
    }
    return false;
  }

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

/**
 * 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 getBottomBoundary() {
    return bottom;
  }

  /**
   * Gets the scan line for the specified y value.
   */
  public Scan getScan(int y) {
    return scans[y];
  }

  /**
   * Ensures this ScanConverter has the capacity to scan-convert a polygon to
   * the ViewWindow.
   */
  protected void ensureCapacity() {
    int height = view.getTopOffset() + view.getHeight();
    if (scans == null || scans.length != height) {
      scans = new Scan[height];
      for (int i = 0; i < height; i++) {
        scans[inew Scan();
      }
      // set top and bottom so clearCurrentScan clears all
      top = 0;
      bottom = height - 1;
    }

  }

  /**
   * Clears the current scan.
   */
  private void clearCurrentScan() {
    for (int i = top; i <= bottom; i++) {
      scans[i].clear();
    }
    top = Integer.MAX_VALUE;
    bottom = Integer.MIN_VALUE;
  }

  /**
   * Scan-converts a projected polygon. Returns true if the polygon is visible
   * in the view window.
   */
  public boolean convert(Polygon3D polygon) {

    ensureCapacity();
    clearCurrentScan();

    int minX = view.getLeftOffset();
    int maxX = view.getLeftOffset() + view.getWidth() 1;
    int minY = view.getTopOffset();
    int maxY = view.getTopOffset() + view.getHeight() 1;

    int numVertices = polygon.getNumVertices();
    for (int i = 0; i < numVertices; i++) {
      Vector3D v1 = polygon.getVertex(i);
      Vector3D v2;
      if (i == numVertices - 1) {
        v2 = polygon.getVertex(0);
      else {
        v2 = polygon.getVertex(i + 1);
      }

      // ensure v1.y < v2.y
      if (v1.y > v2.y) {
        Vector3D temp = v1;
        v1 = v2;
        v2 = temp;
      }
      float dy = v2.y - v1.y;

      // ignore horizontal lines
      if (dy == 0) {
        continue;
      }

      int startY = Math.max(MoreMath.ceil(v1.y), minY);
      int endY = Math.min(MoreMath.ceil(v2.y1, maxY);
      top = Math.min(top, startY);
      bottom = Math.max(bottom, endY);
      float dx = v2.x - v1.x;

      // special case: vertical line
      if (dx == 0) {
        int x = MoreMath.ceil(v1.x);
        // ensure x within view bounds
        x = Math.min(maxX + 1, Math.max(x, minX));
        for (int y = startY; y <= endY; y++) {
          scans[y].setBoundary(x);
        }
      else {
        // scan-convert this edge (line equation)
        float gradient = dx / dy;

        // (slower version)
        /*
         * for (int y=startY; y <=endY; y++) { int x =
         * MoreMath.ceil(v1.x + (y - v1.y) * gradient); // ensure x
         * within view bounds x = Math.min(maxX+1, Math.max(x, minX));
         * scans[y].setBoundary(x); }
         */

        // (faster version)
        // trim start of line
        float startX = v1.x + (startY - v1.y* gradient;
        if (startX < minX) {
          int yInt = (int) (v1.y + (minX - v1.x/ gradient);
          yInt = Math.min(yInt, endY);
          while (startY <= yInt) {
            scans[startY].setBoundary(minX);
            startY++;
          }
        else if (startX > maxX) {
          int yInt = (int) (v1.y + (maxX - v1.x/ gradient);
          yInt = Math.min(yInt, endY);
          while (startY <= yInt) {
            scans[startY].setBoundary(maxX + 1);
            startY++;
          }
        }

        if (startY > endY) {
          continue;
        }

        // trim back of line
        float endX = v1.x + (endY - v1.y* gradient;
        if (endX < minX) {
          int yInt = MoreMath.ceil(v1.y + (minX - v1.x/ gradient);
          yInt = Math.max(yInt, startY);
          while (endY >= yInt) {
            scans[endY].setBoundary(minX);
            endY--;
          }
        else if (endX > maxX) {
          int yInt = MoreMath.ceil(v1.y + (maxX - v1.x/ gradient);
          yInt = Math.max(yInt, startY);
          while (endY >= yInt) {
            scans[endY].setBoundary(maxX + 1);
            endY--;
          }
        }

        if (startY > endY) {
          continue;
        }

        // line equation using integers
        int xScaled = (int) (SCALE * v1.x + SCALE * (startY - v1.y)
            * dx / dy)
            + SCALE_MASK;
        int dxScaled = (int) (dx * SCALE / dy);

        for (int y = startY; y <= endY; y++) {
          scans[y].setBoundary(xScaled >> SCALE_BITS);
          xScaled += dxScaled;
        }
      }
    }

    // check if visible (any valid scans)
    for (int i = top; i <= bottom; i++) {
      if (scans[i].isValid()) {
        return true;
      }
    }
    return false;
  }

}

/**
 * The MoreMath class provides functions not contained in the java.lang.Math or
 * java.lang.StrictMath classes.
 */

class MoreMath {

  /**
   * Returns the sign of the number. Returns -1 for negative, 1 for positive,
   * and 0 otherwise.
   */
  public static int sign(short v) {
    return (v > 0(v < 0? -0;
  }

  /**
   * Returns the sign of the number. Returns -1 for negative, 1 for positive,
   * and 0 otherwise.
   */
  public static int sign(int v) {
    return (v > 0(v < 0? -0;
  }

  /**
   * Returns the sign of the number. Returns -1 for negative, 1 for positive,
   * and 0 otherwise.
   */
  public static int sign(long v) {
    return (v > 0(v < 0? -0;
  }

  /**
   * Returns the sign of the number. Returns -1 for negative, 1 for positive,
   * and 0 otherwise.
   */
  public static int sign(float v) {
    return (v > 0(v < 0? -0;
  }

  /**
   * Returns the sign of the number. Returns -1 for negative, 1 for positive,
   * and 0 otherwise.
   */
  public static int sign(double v) {
    return (v > 0(v < 0? -0;
  }

  /**
   * Faster ceil function to convert a float to an int. Contrary to the
   * java.lang.Math ceil function, this function takes a float as an argument,
   * returns an int instead of a double, and does not consider special cases.
   */
  public static int ceil(float f) {
    if (f > 0) {
      return (intf + 1;
    else {
      return (intf;
    }
  }

  /**
   * Faster floor function to convert a float to an int. Contrary to the
   * java.lang.Math floor function, this function takes a float as an
   * argument, returns an int instead of a double, and does not consider
   * special cases.
   */
  public static int floor(float f) {
    if (f >= 0) {
      return (intf;
    else {
      return (intf - 1;
    }
  }

  /**
   * Returns true if the specified number is a power of 2.
   */
  public static boolean isPowerOfTwo(int n) {
    return ((n & (n - 1)) == 0);
  }

  /**
   * Gets the number of "on" bits in an integer.
   */
  public static int getBitCount(int n) {
    int count = 0;
    while (n > 0) {
      count += (n & 1);
      n >>= 1;
    }
    return count;
  }
}

/**
 * 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);
  }
}

/**
 * A Rectangle3D is a rectangle in 3D space, defined as an origin and vectors
 * pointing in the directions of the base (width) and side (height).
 */

class Rectangle3D implements Transformable {

  private Vector3D origin;

  private Vector3D directionU;

  private Vector3D directionV;

  private Vector3D normal;

  private float width;

  private float height;

  /**
   * Creates a rectangle at the origin with a width and height of zero.
   */
  public Rectangle3D() {
    origin = new Vector3D();
    directionU = new Vector3D(100);
    directionV = new Vector3D(010);
    width = 0;
    height = 0;
  }

  /**
   * Creates a new Rectangle3D with the specified origin, direction of the
   * base (directionU) and direction of the side (directionV).
   */
  public Rectangle3D(Vector3D origin, Vector3D directionU,
      Vector3D directionV, float width, float height) {
    this.origin = new Vector3D(origin);
    this.directionU = new Vector3D(directionU);
    this.directionU.normalize();
    this.directionV = new Vector3D(directionV);
    this.directionV.normalize();
    this.width = width;
    this.height = height;
  }

  /**
   * Sets the values of this Rectangle3D to the specified Rectangle3D.
   */
  public void setTo(Rectangle3D rect) {
    origin.setTo(rect.origin);
    directionU.setTo(rect.directionU);
    directionV.setTo(rect.directionV);
    width = rect.width;
    height = rect.height;
  }

  /**
   * Gets the origin of this Rectangle3D.
   */
  public Vector3D getOrigin() {
    return origin;
  }

  /**
   * Gets the direction of the base of this Rectangle3D.
   */
  public Vector3D getDirectionU() {
    return directionU;
  }

  /**
   * Gets the direction of the side of this Rectangle3D.
   */
  public Vector3D getDirectionV() {
    return directionV;
  }

  /**
   * Gets the width of this Rectangle3D.
   */
  public float getWidth() {
    return width;
  }

  /**
   * Sets the width of this Rectangle3D.
   */
  public void setWidth(float width) {
    this.width = width;
  }

  /**
   * Gets the height of this Rectangle3D.
   */
  public float getHeight() {
    return height;
  }

  /**
   * Sets the height of this Rectangle3D.
   */
  public void setHeight(float height) {
    this.height = height;
  }

  /**
   * Calculates the normal vector of this Rectange3D.
   */
  protected Vector3D calcNormal() {
    if (normal == null) {
      normal = new Vector3D();
    }
    normal.setToCrossProduct(directionU, directionV);
    normal.normalize();
    return normal;
  }

  /**
   * Gets the normal of this Rectangle3D.
   */
  public Vector3D getNormal() {
    if (normal == null) {
      calcNormal();
    }
    return normal;
  }

  /**
   * Sets the normal of this Rectangle3D.
   */
  public void setNormal(Vector3D n) {
    if (normal == null) {
      normal = new Vector3D(n);
    else {
      normal.setTo(n);
    }
  }

  public void add(Vector3D u) {
    origin.add(u);
    // don't translate direction vectors or size
  }

  public void subtract(Vector3D u) {
    origin.subtract(u);
    // don't translate direction vectors or size
  }

  public void add(Transform3D xform) {
    addRotation(xform);
    add(xform.getLocation());
  }

  public void subtract(Transform3D xform) {
    subtract(xform.getLocation());
    subtractRotation(xform);
  }

  public void addRotation(Transform3D xform) {
    origin.addRotation(xform);
    directionU.addRotation(xform);
    directionV.addRotation(xform);
  }

  public void subtractRotation(Transform3D xform) {
    origin.subtractRotation(xform);
    directionU.subtractRotation(xform);
    directionV.subtractRotation(xform);
  }

}

/**
 * The Polygon3D class represents a polygon as a series of vertices.
 */

class Polygon3D implements Transformable {

  // temporary vectors used for calculation
  private static Vector3D temp1 = new Vector3D();

  private static Vector3D temp2 = new Vector3D();

  private Vector3D[] v;

  private int numVertices;

  private Vector3D normal;

  /**
   * Creates an empty polygon that can be used as a "scratch" polygon for
   * transforms, projections, etc.
   */
  public Polygon3D() {
    numVertices = 0;
    v = new Vector3D[0];
    normal = new Vector3D();
  }

  /**
   * Creates a new Polygon3D with the specified vertices.
   */
  public Polygon3D(Vector3D v0, Vector3D v1, Vector3D v2) {
    this(new Vector3D[] { v0, v1, v2 });
  }

  /**
   * Creates a new Polygon3D with the specified vertices. All the vertices are
   * assumed to be in the same plane.
   */
  public Polygon3D(Vector3D v0, Vector3D v1, Vector3D v2, Vector3D v3) {
    this(new Vector3D[] { v0, v1, v2, v3 });
  }

  /**
   * Creates a new Polygon3D with the specified vertices. All the vertices are
   * assumed to be in the same plane.
   */
  public Polygon3D(Vector3D[] vertices) {
    this.v = vertices;
    numVertices = vertices.length;
    calcNormal();
  }

  /**
   * Sets this polygon to the same vertices as the specfied polygon.
   */
  public void setTo(Polygon3D polygon) {
    numVertices = polygon.numVertices;
    normal.setTo(polygon.normal);

    ensureCapacity(numVertices);
    for (int i = 0; i < numVertices; i++) {
      v[i].setTo(polygon.v[i]);
    }
  }

  /**
   * Ensures this polgon has enough capacity to hold the specified number of
   * vertices.
   */
  protected void ensureCapacity(int length) {
    if (v.length < length) {
      Vector3D[] newV = new Vector3D[length];
      System.arraycopy(v, 0, newV, 0, v.length);
      for (int i = v.length; i < newV.length; i++) {
        newV[inew Vector3D();
      }
      v = newV;
    }
  }

  /**
   * Gets the number of vertices this polygon has.
   */
  public int getNumVertices() {
    return numVertices;
  }

  /**
   * Gets the vertex at the specified index.
   */
  public Vector3D getVertex(int index) {
    return v[index];
  }

  /**
   * Projects this polygon onto the view window.
   */
  public void project(ViewWindow view) {
    for (int i = 0; i < numVertices; i++) {
      view.project(v[i]);
    }
  }

  // methods from the Transformable interface.

  public void add(Vector3D u) {
    for (int i = 0; i < numVertices; i++) {
      v[i].add(u);
    }
  }

  public void subtract(Vector3D u) {
    for (int i = 0; i < numVertices; i++) {
      v[i].subtract(u);
    }
  }

  public void add(Transform3D xform) {
    addRotation(xform);
    add(xform.getLocation());
  }

  public void subtract(Transform3D xform) {
    subtract(xform.getLocation());
    subtractRotation(xform);
  }

  public void addRotation(Transform3D xform) {
    for (int i = 0; i < numVertices; i++) {
      v[i].addRotation(xform);
    }
    normal.addRotation(xform);
  }

  public void subtractRotation(Transform3D xform) {
    for (int i = 0; i < numVertices; i++) {
      v[i].subtractRotation(xform);
    }
    normal.subtractRotation(xform);
  }

  /**
   * Calculates the unit-vector normal of this polygon. This method uses the
   * first, second, and third vertices to calcuate the normal, so if these
   * vertices are collinear, this method will not work. In this case, you can
   * get the normal from the bounding rectangle. Use setNormal() to explicitly
   * set the normal. This method uses static objects in the Polygon3D class
   * for calculations, so this method is not thread-safe across all instances
   * of Polygon3D.
   */
  public Vector3D calcNormal() {
    if (normal == null) {
      normal = new Vector3D();
    }
    temp1.setTo(v[2]);
    temp1.subtract(v[1]);
    temp2.setTo(v[0]);
    temp2.subtract(v[1]);
    normal.setToCrossProduct(temp1, temp2);
    normal.normalize();
    return normal;
  }

  /**
   * Gets the normal of this polygon. Use calcNormal() if any vertices have
   * changed.
   */
  public Vector3D getNormal() {
    return normal;
  }

  /**
   * Sets the normal of this polygon.
   */
  public void setNormal(Vector3D n) {
    if (normal == null) {
      normal = new Vector3D(n);
    else {
      normal.setTo(n);
    }
  }

  /**
   * Tests if this polygon is facing the specified location. This method uses
   * static objects in the Polygon3D class for calculations, so this method is
   * not thread-safe across all instances of Polygon3D.
   */
  public boolean isFacing(Vector3D u) {
    temp1.setTo(u);
    temp1.subtract(v[0]);
    return (normal.getDotProduct(temp1>= 0);
  }

  /**
   * Clips this polygon so that all vertices are in front of the clip plane,
   * clipZ (in other words, all vertices have z <= clipZ). The value of clipZ
   * should not be 0, as this causes divide-by-zero problems. Returns true if
   * the polygon is at least partially in front of the clip plane.
   */
  public boolean clip(float clipZ) {
    ensureCapacity(numVertices * 3);

    boolean isCompletelyHidden = true;

    // insert vertices so all edges are either completly
    // in front or behind the clip plane
    for (int i = 0; i < numVertices; i++) {
      int next = (i + 1% numVertices;
      Vector3D v1 = v[i];
      Vector3D v2 = v[next];
      if (v1.z < clipZ) {
        isCompletelyHidden = false;
      }
      // ensure v1.z < v2.z
      if (v1.z > v2.z) {
        Vector3D temp = v1;
        v1 = v2;
        v2 = temp;
      }
      if (v1.z < clipZ && v2.z > clipZ) {
        float scale = (clipZ - v1.z(v2.z - v1.z);
        insertVertex(next, v1.x + scale * (v2.x - v1.x), v1.y + scale
            (v2.y - v1.y), clipZ);
        // skip the vertex we just created
        i++;
      }
    }

    if (isCompletelyHidden) {
      return false;
    }

    // delete all vertices that have z > clipZ
    for (int i = numVertices - 1; i >= 0; i--) {
      if (v[i].z > clipZ) {
        deleteVertex(i);
      }
    }

    return (numVertices >= 3);
  }

  /**
   * Inserts a new vertex at the specified index.
   */
  protected void insertVertex(int index, float x, float y, float z) {
    Vector3D newVertex = v[v.length - 1];
    newVertex.x = x;
    newVertex.y = y;
    newVertex.z = z;
    for (int i = v.length - 1; i > index; i--) {
      v[i= v[i - 1];
    }
    v[index= newVertex;
    numVertices++;
  }

  /**
   * Delete the vertex at the specified index.
   */
  protected void deleteVertex(int index) {
    Vector3D deleted = v[index];
    for (int i = index; i < v.length - 1; i++) {
      v[i= v[i + 1];
    }
    v[v.length - 1= deleted;
    numVertices--;
  }

  /**
   * Inserts a vertex into this polygon at the specified index. The exact
   * vertex in inserted (not a copy).
   */
  public void insertVertex(int index, Vector3D vertex) {
    Vector3D[] newV = new Vector3D[numVertices + 1];
    System.arraycopy(v, 0, newV, 0, index);
    newV[index= vertex;
    System.arraycopy(v, index, newV, index + 1, numVertices - index);
    v = newV;
    numVertices++;
  }

  /**
   * Calculates and returns the smallest bounding rectangle for this polygon.
   */
  public Rectangle3D calcBoundingRectangle() {

    // the smallest bounding rectangle for a polygon shares
    // at least one edge with the polygon. so, this method
    // finds the bounding rectangle for every edge in the
    // polygon, and returns the smallest one.
    Rectangle3D boundingRect = new Rectangle3D();
    float minimumArea = Float.MAX_VALUE;
    Vector3D u = new Vector3D();
    Vector3D v = new Vector3D();
    Vector3D d = new Vector3D();
    for (int i = 0; i < getNumVertices(); i++) {
      u.setTo(getVertex((i + 1% getNumVertices()));
      u.subtract(getVertex(i));
      u.normalize();
      v.setToCrossProduct(getNormal(), u);
      v.normalize();

      float uMin = 0;
      float uMax = 0;
      float vMin = 0;
      float vMax = 0;
      for (int j = 0; j < getNumVertices(); j++) {
        if (j != i) {
          d.setTo(getVertex(j));
          d.subtract(getVertex(i));
          float uLength = d.getDotProduct(u);
          float vLength = d.getDotProduct(v);
          uMin = Math.min(uLength, uMin);
          uMax = Math.max(uLength, uMax);
          vMin = Math.min(vLength, vMin);
          vMax = Math.max(vLength, vMax);
        }
      }
      // if this calculated area is the smallest, set
      // the bounding rectangle
      float area = (uMax - uMin(vMax - vMin);
      if (area < minimumArea) {
        minimumArea = area;
        Vector3D origin = boundingRect.getOrigin();
        origin.setTo(getVertex(i));
        d.setTo(u);
        d.multiply(uMin);
        origin.add(d);
        d.setTo(v);
        d.multiply(vMin);
        origin.add(d);
        boundingRect.getDirectionU().setTo(u);
        boundingRect.getDirectionV().setTo(v);
        boundingRect.setWidth(uMax - uMin);
        boundingRect.setHeight(vMax - vMin);
      }
    }
    return boundingRect;
  }
}

/**
 * The Transform3D class represents a rotation and translation.
 */

class Transform3D {

  protected Vector3D location;

  private float cosAngleX;

  private float sinAngleX;

  private float cosAngleY;

  private float sinAngleY;

  private float cosAngleZ;

  private float sinAngleZ;

  /**
   * Creates a new Transform3D with no translation or rotation.
   */
  public Transform3D() {
    this(000);
  }

  /**
   * Creates a new Transform3D with the specified translation and no rotation.
   */
  public Transform3D(float x, float y, float z) {
    location = new Vector3D(x, y, z);
    setAngle(000);
  }

  /**
   * Creates a new Transform3D
   */
  public Transform3D(Transform3D v) {
    location = new Vector3D();
    setTo(v);
  }

  public Object clone() {
    return new Transform3D(this);
  }

  /**
   * Sets this Transform3D to the specified Transform3D.
   */
  public void setTo(Transform3D v) {
    location.setTo(v.location);
    this.cosAngleX = v.cosAngleX;
    this.sinAngleX = v.sinAngleX;
    this.cosAngleY = v.cosAngleY;
    this.sinAngleY = v.sinAngleY;
    this.cosAngleZ = v.cosAngleZ;
    this.sinAngleZ = v.sinAngleZ;
  }

  /**
   * Gets the location (translation) of this transform.
   */
  public Vector3D getLocation() {
    return location;
  }

  public float getCosAngleX() {
    return cosAngleX;
  }

  public float getSinAngleX() {
    return sinAngleX;
  }

  public float getCosAngleY() {
    return cosAngleY;
  }

  public float getSinAngleY() {
    return sinAngleY;
  }

  public float getCosAngleZ() {
    return cosAngleZ;
  }

  public float getSinAngleZ() {
    return sinAngleZ;
  }

  public float getAngleX() {
    return (floatMath.atan2(sinAngleX, cosAngleX);
  }

  public float getAngleY() {
    return (floatMath.atan2(sinAngleY, cosAngleY);
  }

  public float getAngleZ() {
    return (floatMath.atan2(sinAngleZ, cosAngleZ);
  }

  public void setAngleX(float angleX) {
    cosAngleX = (floatMath.cos(angleX);
    sinAngleX = (floatMath.sin(angleX);
  }

  public void setAngleY(float angleY) {
    cosAngleY = (floatMath.cos(angleY);
    sinAngleY = (floatMath.sin(angleY);
  }

  public void setAngleZ(float angleZ) {
    cosAngleZ = (floatMath.cos(angleZ);
    sinAngleZ = (floatMath.sin(angleZ);
  }

  public void setAngle(float angleX, float angleY, float angleZ) {
    setAngleX(angleX);
    setAngleY(angleY);
    setAngleZ(angleZ);
  }

  public void rotateAngleX(float angle) {
    if (angle != 0) {
      setAngleX(getAngleX() + angle);
    }
  }

  public void rotateAngleY(float angle) {
    if (angle != 0) {
      setAngleY(getAngleY() + angle);
    }
  }

  public void rotateAngleZ(float angle) {
    if (angle != 0) {
      setAngleZ(getAngleZ() + angle);
    }
  }

  public void rotateAngle(float angleX, float angleY, float angleZ) {
    rotateAngleX(angleX);
    rotateAngleY(angleY);
    rotateAngleZ(angleZ);
  }

}

interface Transformable {

  public void add(Vector3D u);

  public void subtract(Vector3D u);

  public void add(Transform3D xform);

  public void subtract(Transform3D xform);

  public void addRotation(Transform3D xform);

  public void subtractRotation(Transform3D xform);

}

/**
 * A MovingTransform3D is a Transform3D that has a location velocity and a
 * angular rotation velocity for rotation around the x, y, and z axes.
 */

class MovingTransform3D extends Transform3D {

  public static final int FOREVER = -1;

  // Vector3D used for calculations
  private static Vector3D temp = new Vector3D();

  // velocity (units per millisecond)
  private Vector3D velocity;

  private Movement velocityMovement;

  // angular velocity (radians per millisecond)
  private Movement velocityAngleX;

  private Movement velocityAngleY;

  private Movement velocityAngleZ;

  /**
   * Creates a new MovingTransform3D
   */
  public MovingTransform3D() {
    init();
  }

  /**
   * Creates a new MovingTransform3D, using the same values as the specified
   * Transform3D.
   */
  public MovingTransform3D(Transform3D v) {
    super(v);
    init();
  }

  protected void init() {
    velocity = new Vector3D(000);
    velocityMovement = new Movement();
    velocityAngleX = new Movement();
    velocityAngleY = new Movement();
    velocityAngleZ = new Movement();
  }

  public Object clone() {
    return new MovingTransform3D(this);
  }

  /**
   * Updates this Transform3D based on the specified elapsed time. The
   * location and angles are updated.
   */
  public void update(long elapsedTime) {
    float delta = velocityMovement.getDistance(elapsedTime);
    if (delta != 0) {
      temp.setTo(velocity);
      temp.multiply(delta);
      location.add(temp);
    }

    rotateAngle(velocityAngleX.getDistance(elapsedTime), velocityAngleY
        .getDistance(elapsedTime), velocityAngleZ
        .getDistance(elapsedTime));
  }

  /**
   * Stops this Transform3D. Any moving velocities are set to zero.
   */
  public void stop() {
    velocity.setTo(000);
    velocityMovement.set(00);
    velocityAngleX.set(00);
    velocityAngleY.set(00);
    velocityAngleZ.set(00);
  }

  /**
   * Sets the velocity to move to the following destination at the specified
   * speed.
   */
  public void moveTo(Vector3D destination, float speed) {
    temp.setTo(destination);
    temp.subtract(location);

    // calc the time needed to move
    float distance = temp.length();
    long time = (long) (distance / speed);

    // normalize the direction vector
    temp.divide(distance);
    temp.multiply(speed);

    setVelocity(temp, time);
  }

  /**
   * Returns true if currently moving.
   */
  public boolean isMoving() {
    return !velocityMovement.isStopped() && !velocity.equals(000);
  }

  /**
   * Returns true if currently moving, ignoring the y movement.
   */
  public boolean isMovingIgnoreY() {
    return !velocityMovement.isStopped()
        && (velocity.x != || velocity.z != 0);
  }

  /**
   * Gets the amount of time remaining for this movement.
   */
  public long getRemainingMoveTime() {
    if (!isMoving()) {
      return 0;
    else {
      return velocityMovement.remainingTime;
    }
  }

  /**
   * Gets the velocity vector. If the velocity vector is modified directly,
   * call setVelocity() to ensure the change is recognized.
   */
  public Vector3D getVelocity() {
    return velocity;
  }

  /**
   * Sets the velocity to the specified vector.
   */
  public void setVelocity(Vector3D v) {
    setVelocity(v, FOREVER);
  }

  /**
   * Sets the velocity. The velocity is automatically set to zero after the
   * specified amount of time has elapsed. If the specified time is FOREVER,
   * then the velocity is never automatically set to zero.
   */
  public void setVelocity(Vector3D v, long time) {
    if (velocity != v) {
      velocity.setTo(v);
    }
    if (v.x == && v.y == && v.z == 0) {
      velocityMovement.set(00);
    else {
      velocityMovement.set(1, time);
    }

  }

  /**
   * Adds the specified velocity to the current velocity. If this
   * MovingTransform3D is currently moving, it's time remaining is not
   * changed. Otherwise, the time remaining is set to FOREVER.
   */
  public void addVelocity(Vector3D v) {
    if (isMoving()) {
      velocity.add(v);
    else {
      setVelocity(v);
    }
  }

  /**
   * Turns the x axis to the specified angle with the specified speed.
   */
  public void turnXTo(float angleDest, float speed) {
    turnTo(velocityAngleX, getAngleX(), angleDest, speed);
  }

  /**
   * Turns the y axis to the specified angle with the specified speed.
   */
  public void turnYTo(float angleDest, float speed) {
    turnTo(velocityAngleY, getAngleY(), angleDest, speed);
  }

  /**
   * Turns the z axis to the specified angle with the specified speed.
   */
  public void turnZTo(float angleDest, float speed) {
    turnTo(velocityAngleZ, getAngleZ(), angleDest, speed);
  }

  /**
   * Turns the x axis to face the specified (y,z) vector direction with the
   * specified speed.
   */
  public void turnXTo(float y, float z, float angleOffset, float speed) {
    turnXTo((floatMath.atan2(-z, y+ angleOffset, speed);
  }

  /**
   * Turns the y axis to face the specified (x,z) vector direction with the
   * specified speed.
   */
  public void turnYTo(float x, float z, float angleOffset, float speed) {
    turnYTo((floatMath.atan2(-z, x+ angleOffset, speed);
  }

  /**
   * Turns the z axis to face the specified (x,y) vector direction with the
   * specified speed.
   */
  public void turnZTo(float x, float y, float angleOffset, float speed) {
    turnZTo((floatMath.atan2(y, x+ angleOffset, speed);
  }

  /**
   * Ensures the specified angle is with -pi and pi. Returns the angle,
   * corrected if it is not within these bounds.
   */
  protected float ensureAngleWithinBounds(float angle) {
    if (angle < -Math.PI || angle > Math.PI) {
      // transform range to (0 to 1)
      double newAngle = (angle + Math.PI(* Math.PI);
      // validate range
      newAngle = newAngle - Math.floor(newAngle);
      // transform back to (-pi to pi) range
      newAngle = Math.PI * (newAngle * 1);
      return (floatnewAngle;
    }
    return angle;
  }

  /**
   * Turns the movement angle from the startAngle to the endAngle with the
   * specified speed.
   */
  protected void turnTo(Movement movement, float startAngle, float endAngle,
      float speed) {
    startAngle = ensureAngleWithinBounds(startAngle);
    endAngle = ensureAngleWithinBounds(endAngle);
    if (startAngle == endAngle) {
      movement.set(00);
    else {

      float distanceLeft;
      float distanceRight;
      float pi2 = (float) (* Math.PI);

      if (startAngle < endAngle) {
        distanceLeft = startAngle - endAngle + pi2;
        distanceRight = endAngle - startAngle;
      else {
        distanceLeft = startAngle - endAngle;
        distanceRight = endAngle - startAngle + pi2;
      }

      if (distanceLeft < distanceRight) {
        speed = -Math.abs(speed);
        movement.set(speed, (long) (distanceLeft / -speed));
      else {
        speed = Math.abs(speed);
        movement.set(speed, (long) (distanceRight / speed));
      }
    }
  }

  /**
   * Sets the angular speed of the x axis.
   */
  public void setAngleVelocityX(float speed) {
    setAngleVelocityX(speed, FOREVER);
  }

  /**
   * Sets the angular speed of the y axis.
   */
  public void setAngleVelocityY(float speed) {
    setAngleVelocityY(speed, FOREVER);
  }

  /**
   * Sets the angular speed of the z axis.
   */
  public void setAngleVelocityZ(float speed) {
    setAngleVelocityZ(speed, FOREVER);
  }

  /**
   * Sets the angular speed of the x axis over the specified time.
   */
  public void setAngleVelocityX(float speed, long time) {
    velocityAngleX.set(speed, time);
  }

  /**
   * Sets the angular speed of the y axis over the specified time.
   */
  public void setAngleVelocityY(float speed, long time) {
    velocityAngleY.set(speed, time);
  }

  /**
   * Sets the angular speed of the z axis over the specified time.
   */
  public void setAngleVelocityZ(float speed, long time) {
    velocityAngleZ.set(speed, time);
  }

  /**
   * Sets the angular speed of the x axis over the specified time.
   */
  public float getAngleVelocityX() {
    return isTurningX() ? velocityAngleX.speed : 0;
  }

  /**
   * Sets the angular speed of the y axis over the specified time.
   */
  public float getAngleVelocityY() {
    return isTurningY() ? velocityAngleY.speed : 0;
  }

  /**
   * Sets the angular speed of the z axis over the specified time.
   */
  public float getAngleVelocityZ() {
    return isTurningZ() ? velocityAngleZ.speed : 0;
  }

  /**
   * Returns true if the x axis is currently turning.
   */
  public boolean isTurningX() {
    return !velocityAngleX.isStopped();
  }

  /**
   * Returns true if the y axis is currently turning.
   */
  public boolean isTurningY() {
    return !velocityAngleY.isStopped();
  }

  /**
   * Returns true if the z axis is currently turning.
   */
  public boolean isTurningZ() {
    return !velocityAngleZ.isStopped();
  }

  /**
   * The Movement class contains a speed and an amount of time to continue
   * that speed.
   */
  protected static class Movement {
    // change per millisecond
    float speed;

    long remainingTime;

    /**
     * Sets this movement to the specified speed and time (in milliseconds).
     */
    public void set(float speed, long time) {
      this.speed = speed;
      this.remainingTime = time;
    }

    public boolean isStopped() {
      return (speed == 0|| (remainingTime == 0);
    }

    /**
     * Gets the distance traveled in the specified amount of time in
     * milliseconds.
     */
    public float getDistance(long elapsedTime) {
      if (remainingTime == 0) {
        return 0;
      else if (remainingTime != FOREVER) {
        elapsedTime = Math.min(elapsedTime, remainingTime);
        remainingTime -= elapsedTime;
      }
      return speed * elapsedTime;
    }
  }
}

/**
 * A BSPTreeTraverseListener is an interface for a BSPTreeTraverser to signal
 * visited polygons.
 */

interface BSPTreeTraverseListener {

  /**
   * Visits a BSP polygon. Called by a BSPTreeTraverer. If this method returns
   * true, the BSPTreeTraverer will stop the current traversal. Otherwise, the
   * BSPTreeTraverer will continue if there are polygons in the tree that have
   * not yet been traversed.
   */
  public boolean visitPolygon(BSPPolygon poly, boolean isBackLeaf);

}

/**
 * The BSPTreeBuilder class builds a BSP tree from a list of polygons. The
 * polygons must be BSPPolygons.
 
 * Currently, the builder does not try to optimize the order of the partitions,
 * and could be optimized by choosing partitions in an order that minimizes
 * polygon splits and provides a more balanced, complete tree.
 */

class BSPTreeBuilder {

  /**
   * The bsp tree currently being built.
   */
  protected BSPTree currentTree;

  /**
   * Builds a BSP tree.
   */
  public BSPTree build(List polygons) {
    currentTree = new BSPTree(createNewNode(polygons));
    buildNode(currentTree.getRoot());
    return currentTree;
  }

  /**
   * Builds a node in the BSP tree.
   */
  protected void buildNode(BSPTree.Node node) {

    // nothing to build if it's a leaf
    if (node instanceof BSPTree.Leaf) {
      return;
    }

    // classify all polygons relative to the partition
    // (front, back, or collinear)
    ArrayList collinearList = new ArrayList();
    ArrayList frontList = new ArrayList();
    ArrayList backList = new ArrayList();
    List allPolygons = node.polygons;
    node.polygons = null;
    for (int i = 0; i < allPolygons.size(); i++) {
      BSPPolygon poly = (BSPPolygonallPolygons.get(i);
      int side = node.partition.getSide(poly);
      if (side == BSPLine.COLLINEAR) {
        collinearList.add(poly);
      else if (side == BSPLine.FRONT) {
        frontList.add(poly);
      else if (side == BSPLine.BACK) {
        backList.add(poly);
      else if (side == BSPLine.SPANNING) {
        BSPPolygon front = clipBack(poly, node.partition);
        BSPPolygon back = clipFront(poly, node.partition);
        if (front != null) {
          frontList.add(front);
        }
        if (back != null) {
          backList.add(back);
        }

      }
    }

    // clean and assign lists
    collinearList.trimToSize();
    frontList.trimToSize();
    backList.trimToSize();
    node.polygons = collinearList;
    node.front = createNewNode(frontList);
    node.back = createNewNode(backList);

    // build front and back nodes
    buildNode(node.front);
    buildNode(node.back);
    if (node.back instanceof BSPTree.Leaf) {
      ((BSPTree.Leafnode.back).isBack&