|
/*
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 BSPMapTest extends ShooterCore {
public static void main(String[] args) {
new BSPMapTest(args).run();
}
protected BSPTree bspTree;
protected String mapFile;
public BSPMapTest(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() {
float ambientLightIntensity = .2f;
List lights = new LinkedList();
lights.add(new PointLight3D(-100, 100, 100, .3f, -1));
lights.add(new PointLight3D(100, 100, 0, .3f, -1));
MapLoader loader = new MapLoader();
loader.setObjectLights(lights, ambientLightIntensity);
try {
bspTree = loader.loadMap(mapFile);
} catch (IOException ex) {
ex.printStackTrace();
}
gameObjectManager = new SimpleGameObjectManager();
gameObjectManager.addPlayer(new GameObject(new PolygonGroup("Player")));
((BSPRenderer) polygonRenderer).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 = (PolygonGroup) i.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 draw(Graphics2D g) {
polygonRenderer.startFrame(g);
// draw polygons in bsp tree (set z buffer)
((BSPRenderer) polygonRenderer).draw(g, bspTree);
// draw game object polygons (check and set z buffer)
gameObjectManager.draw(g, (GameObjectRenderer) polygonRenderer);
polygonRenderer.endFrame(g);
super.drawText(g);
}
}
/**
* 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 = 1 << 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 - 1 > 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[i] = new 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.y) - 1, 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) ? 1 : (v < 0) ? -1 : 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) ? 1 : (v < 0) ? -1 : 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) ? 1 : (v < 0) ? -1 : 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) ? 1 : (v < 0) ? -1 : 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) ? 1 : (v < 0) ? -1 : 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 (int) f + 1;
} else {
return (int) f;
}
}
/**
* 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 (int) f;
} else {
return (int) f - 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) / (float) Math.tan(angle / 2);
}
/**
* Sets the horizontal view angle for this ViewWindow.
*/
public void setAngle(float angle) {
this.angle = angle;
distanceToCamera = (bounds.width / 2) / (float) Math.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 = con
|