|
/*
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;
import java.awt.*;
import java.util.List;
import java.util.ArrayList;
public class BSPTest3D extends GameCore3D {
public static void main(String[] args) {
new BSPTest3D().run();
}
protected BSPTree bspTree;
public void init() {
init(LOW_RES_MODES);
}
public void createPolygons() {
ShadedTexture floorTexture = (ShadedTexture)
Texture.createTexture("../images/roof1.png", true);
ShadedTexture ceilingTexture = (ShadedTexture)
Texture.createTexture("../images/roof2.png", true);
ShadedTexture wallTexture = (ShadedTexture)
Texture.createTexture("../images/wall1.png", true);
// The floor/ceiling polygons
Polygon3D floor = new BSPPolygon(new Vector3D[] {
new Vector3D(0,0,150), new Vector3D(0,0,450),
new Vector3D(800,0,450), new Vector3D(800,0,300),
new Vector3D(500,0,300), new Vector3D(500,0,75),
}, BSPPolygon.TYPE_FLOOR);
Polygon3D ceiling = new BSPPolygon(new Vector3D[] {
new Vector3D(0,300,450), new Vector3D(0,300,150),
new Vector3D(500,300,75), new Vector3D(500,300,300),
new Vector3D(800,300,300), new Vector3D(800,300,450),
}, BSPPolygon.TYPE_FLOOR);
polygons.add(floor);
polygons.add(ceiling);
setTexture(floor, floorTexture);
setTexture(ceiling, ceilingTexture);
// 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);
BSPPolygon wallE = createPolygon(
new BSPLine(0, 450, 0, 150), 0, 300);
BSPPolygon wallF = createPolygon(
new BSPLine(800, 300, 800, 450), 0, 300);
polygons.add(wallA);
polygons.add(wallB);
polygons.add(wallC);
polygons.add(wallD);
polygons.add(wallE);
polygons.add(wallF);
setTexture(wallA, wallTexture);
setTexture(wallB, wallTexture);
setTexture(wallC, wallTexture);
setTexture(wallD, wallTexture);
setTexture(wallE, wallTexture);
setTexture(wallF, wallTexture);
BSPTreeBuilder builder = new BSPTreeBuilder();
bspTree = builder.build(polygons);
// build surfaces
ArrayList lights = new ArrayList();
lights.add(new PointLight3D(400, 200, 100, 1, 300));
lights.add(new PointLight3D(700, 200, 400, .5f, 1000));
lights.add(new PointLight3D(65, 200, 385, 1, 100));
bspTree.createSurfaces(lights);
}
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 setTexture(Polygon3D poly, Texture texture) {
Vector3D origin = poly.getVertex(1);
Vector3D dv = new Vector3D(poly.getVertex(0));
dv.subtract(origin);
Vector3D du = new Vector3D();
du.setToCrossProduct(poly.getNormal(), dv);
Rectangle3D texBounds = new Rectangle3D(origin, du, dv,
texture.getWidth(), texture.getHeight());
((TexturedPolygon3D)poly).setTexture(texture, texBounds);
}
public void createPolygonRenderer() {
// make the view window the entire screen
viewWindow = new ViewWindow(0, 0,
screen.getWidth(), screen.getHeight(),
(float)Math.toRadians(75));
Transform3D camera = new Transform3D(400,100,300);
polygonRenderer = new SimpleBSPRenderer(
camera, viewWindow);
}
public void draw(Graphics2D g) {
// draw polygons
polygonRenderer.startFrame(g);
((SimpleBSPRenderer)polygonRenderer).draw(g, bspTree);
polygonRenderer.endFrame(g);
super.drawText(g);
}
}
/**
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
|