|
/*
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.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
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.Shape;
import java.awt.Toolkit;
import java.awt.Transparency;
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.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
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 BSPTest2D extends GameCore implements BSPTreeTraverseListener {
public static void main(String[] args) {
new BSPTest2D().run();
}
protected List polygons;
protected BSPTree bspTree;
protected BSPTreeTraverser traverser;
protected int numWalls;
protected int wallID;
protected Vector3D viewLocation = new Vector3D();
protected Image overlayImage;
protected InputManager inputManager;
private GameAction exit = new GameAction("exit");
private GameAction addWall = new GameAction("addWall",
GameAction.DETECT_INITAL_PRESS_ONLY);
private GameAction removeWall = new GameAction("removeWall",
GameAction.DETECT_INITAL_PRESS_ONLY);
public void init() {
super.init();
inputManager = new InputManager(screen.getFullScreenWindow());
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
inputManager.mapToKey(addWall, KeyEvent.VK_SPACE);
inputManager.mapToKey(removeWall, KeyEvent.VK_BACK_SPACE);
traverser = new BSPTreeTraverser();
// create walls
numWalls = 1;
polygons = new ArrayList();
createPolygons();
buildTree();
// create overlay image for lines and text labels
overlayImage = screen.createCompatibleImage(screen.getWidth(), screen
.getHeight(), Transparency.BITMASK);
}
public void createPolygons() {
// The floor polygon
BSPPolygon floor = new BSPPolygon(new Vector3D[] {
new Vector3D(0, 0, 0), new Vector3D(0, 0, 600),
new Vector3D(800, 0, 600), new Vector3D(800, 0, 0) },
BSPPolygon.TYPE_FLOOR);
polygons.add(floor);
// vertices defined from left to right as the viewer
// looks at the wall
BSPPolygon wallA = createPolygon(new BSPLine(0, 150, 500, 75), 0, 300);
BSPPolygon wallB = createPolygon(new BSPLine(500, 75, 500, 300), 0, 300);
BSPPolygon wallC = createPolygon(new BSPLine(500, 300, 800, 300), 0,
300);
BSPPolygon wallD = createPolygon(new BSPLine(800, 450, 0, 450), 0, 300);
polygons.add(wallA);
polygons.add(wallB);
polygons.add(wallC);
polygons.add(wallD);
}
public BSPPolygon createPolygon(BSPLine line, float bottom, float top) {
return new BSPPolygon(new Vector3D[] {
new Vector3D(line.x1, bottom, line.y1),
new Vector3D(line.x2, bottom, line.y2),
new Vector3D(line.x2, top, line.y2),
new Vector3D(line.x1, top, line.y1) }, BSPPolygon.TYPE_WALL);
}
public void buildTree() {
BSPTreeBuilder builder = new BSPTreeBuilder();
bspTree = builder.build(polygons.subList(0, numWalls + 1));
}
public void update(long elapsedTime) {
if (exit.isPressed()) {
stop();
return;
}
if (addWall.isPressed() && numWalls < polygons.size() - 1) {
numWalls++;
buildTree();
} else if (removeWall.isPressed() && numWalls > 0) {
numWalls--;
buildTree();
}
viewLocation.x = inputManager.getMouseX();
viewLocation.z = inputManager.getMouseY();
}
public void draw(Graphics2D g) {
Graphics2D g2 = (Graphics2D) overlayImage.getGraphics();
g2.setFont(g.getFont());
// erase overlay image (set it to transparent);
Composite defaultComposite = g2.getComposite();
g2.setComposite(AlphaComposite.Clear);
g2.fillRect(0, 0, screen.getWidth(), screen.getHeight());
g2.setComposite(defaultComposite);
// draw info on overlay image
g2.setColor(Color.WHITE);
g2.drawString("Press Space/Backspace to add/remove "
+ "walls. Press Esc to exit.", 5, fontSize);
g2.drawString("The numbers represent front-to-back draw " + "order.",
5, fontSize * 2);
g2.drawString("The mouse represents the camera location.", 5,
fontSize * 3);
g2.dispose();
// erase screen
g.setColor(Color.BLACK);
g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
// draw areas
wallID = 1;
traverser.setListener(this);
traverser.traverse(bspTree, viewLocation);
// draw overlay image on the screen
g.drawImage(overlayImage, 0, 0, null);
}
public boolean visitPolygon(BSPPolygon poly, boolean isBack) {
Shape shape;
if (poly.isWall()) {
shape = drawWall(poly);
} else {
shape = drawFloor(poly, isBack);
}
// draw wall id
Graphics2D g2 = (Graphics2D) overlayImage.getGraphics();
g2.setFont(screen.getGraphics().getFont());
Rectangle2D bounds = shape.getBounds2D();
int x = (int) bounds.getCenterX() - fontSize / 4;
int y = (int) bounds.getCenterY() + fontSize / 2;
g2.setColor(Color.WHITE);
g2.drawString(wallID + ".", x, y);
wallID++;
return true;
}
public Shape drawWall(BSPPolygon wall) {
Graphics2D g = (Graphics2D) overlayImage.getGraphics();
g.setColor(Color.BLACK);
BSPLine line = wall.getLine();
g.draw(line);
g.fillRect((int) line.x1 - 2, (int) line.y1 - 2, 5, 5);
g.fillRect((int) line.x2 - 2, (int) line.y2 - 2, 5, 5);
g.dispose();
return line;
}
public Shape drawFloor(BSPPolygon floor, boolean isBack) {
Graphics2D g = screen.getGraphics();
if (isBack) {
g.setColor(Color.DARK_GRAY);
} else {
g.setColor(Color.LIGHT_GRAY);
}
GeneralPath path = new GeneralPath();
path.moveTo(floor.getVertex(0).x, floor.getVertex(0).z);
for (int i = 1; i < floor.getNumVertices(); i++) {
path.lineTo(floor.getVertex(i).x, floor.getVertex(i).z);
}
g.fill(path);
return path;
}
}
/**
* The SimpleBSPRenderer class is a renderer capable of drawing polygons in a
* BSP tree and any polygon objects in the scene. No Z-buffering is used.
*/
class SimpleBSPRenderer extends ShadedSurfacePolygonRenderer implements
BSPTreeTraverseListener {
protected Graphics2D currentGraphics2D;
protected BSPTreeTraverser traverser;
protected boolean viewNotFilledFirstTime;
/**
* Creates a new BSP renderer with the specified camera object and view
* window.
*/
public SimpleBSPRenderer(Transform3D camera, ViewWindow viewWindow) {
super(camera, viewWindow, false);
viewNotFilledFirstTime = true;
}
protected void init() {
traverser = new BSPTreeTraverser(this);
destPolygon = new TexturedPolygon3D();
scanConverter = new SortedScanConverter(viewWindow);
((SortedScanConverter) scanConverter).setSortedMode(true);
// create renderers for each texture (HotSpot optimization)
scanRenderers = new HashMap();
scanRenderers.put(PowerOf2Texture.class, new PowerOf2TextureRenderer());
scanRenderers.put(ShadedTexture.class, new ShadedTextureRenderer());
scanRenderers.put(ShadedSurface.class, new ShadedSurfaceRenderer());
}
public void startFrame(Graphics2D g) {
super.startFrame(g);
((SortedScanConverter) scanConverter).clear();
}
public void endFrame(Graphics2D g) {
super.endFrame(g);
if (!((SortedScanConverter) scanConverter).isFilled()) {
g.drawString("View not completely filled", 5, viewWindow
.getTopOffset()
+ viewWindow.getHeight() - 5);
if (viewNotFilledFirstTime) {
viewNotFilledFirstTime = false;
// print message to console in case user missed it
System.out.println("View not completely filled.");
}
// clear the background next time
clearViewEveryFrame = true;
} else {
clearViewEveryFrame = false;
}
}
/**
* Draws the visible polygons in a BSP tree based on the camera location.
* The polygons are drawn front-to-back.
*/
public void draw(Graphics2D g, BSPTree tree) {
currentGraphics2D = g;
traverser.traverse(tree, camera.getLocation());
}
// from the BSPTreeTraverseListener interface
public boolean visitPolygon(BSPPolygon poly, boolean isBack) {
draw(currentGraphics2D, poly);
return !((SortedScanConverter) scanConverter).isFilled();
}
protected void drawCurrentPolygon(Graphics2D g) {
if (!(sourcePolygon instanceof TexturedPolygon3D)) {
// not a textured polygon - return
return;
}
buildSurface();
SortedScanConverter scanConverter = (SortedScanConverter) this.scanConverter;
TexturedPolygon3D poly = (TexturedPolygon3D) destPolygon;
Texture texture = poly.getTexture();
ScanRenderer scanRenderer = (ScanRenderer) scanRenderers.get(texture
.getClass());
scanRenderer.setTexture(texture);
Rectangle3D textureBounds = poly.getTextureBounds();
a.setToCrossProduct(textureBounds.getDirectionV(), textureBounds
.getOrigin());
b.setToCrossProduct(textureBounds.getOrigin(), textureBounds
.getDirectionU());
c.setToCrossProduct(textureBounds.getDirectionU(), textureBounds
.getDirectionV());
int y = scanConverter.getTopBoundary();
viewPos.y = viewWindow.convertFromScreenYToViewY(y);
viewPos.z = -viewWindow.getDistance();
while (y <= scanConverter.getBottomBoundary()) {
for (int i = 0; i < scanConverter.getNumScans(y); i++) {
ScanConverter.Scan scan = scanConverter.getScan(y, i);
if (scan.isValid()) {
viewPos.x = viewWindow.convertFromScreenXToViewX(scan.left);
int offset = (y - viewWindow.getTopOffset())
* viewWindow.getWidth()
+ (scan.left - viewWindow.getLeftOffset());
scanRenderer.render(offset, scan.left, scan.right);
}
}
y++;
viewPos.y--;
}
}
}
/**
* The 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);
|