A Simple 3D Pipeline
At this point, you can outline a simple 3D pipeline for drawing a polygon:
- Apply the transform.
- Project it onto the view window.
- Draw.
The Simple3DTest1 class in Listing 7.6 implements this 3D pipeline. It transforms, projects, and draws two polygons that make up a tree.
Listing 7.6 Simple3DTest1.java
import com.brackeen.javagamebook.test.GameCore; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.geom.GeneralPath; import com.brackeen.javagamebook.input.*; import com.brackeen.javagamebook.math3D.*; /** A 3D test to demonstrate drawing polygons. */ public class Simple3DTest1 extends GameCore { public static void main(String[] args) { new Simple3DTest1().run(); } // create solid-colored polygons private SolidPolygon3D treeLeaves = new SolidPolygon3D( new Vector3D(-50, -35, 0), new Vector3D(50, -35, 0), new Vector3D(0, 150, 0)); private SolidPolygon3D treeTrunk = new SolidPolygon3D( new Vector3D(-5, -50, 0), new Vector3D(5, -50, 0), new Vector3D(5, -35, 0), new Vector3D(-5, -35, 0)); private Transform3D treeTransform = new Transform3D(0,0,-500); private Polygon3D transformedPolygon = new Polygon3D(); private ViewWindow viewWindow; private GameAction exit = new GameAction("exit"); private GameAction zoomIn = new GameAction("zoomIn"); private GameAction zoomOut = new GameAction("zoomOut"); public void init() { super.init(); InputManager inputManager = new InputManager( screen.getFullScreenWindow()); inputManager.setCursor(InputManager.INVISIBLE_CURSOR); inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE); inputManager.mapToKey(zoomIn, KeyEvent.VK_UP); inputManager.mapToKey(zoomOut, KeyEvent.VK_DOWN); // make the view window the entire screen viewWindow = new ViewWindow(0, 0, screen.getWidth(), screen.getHeight(), (float)Math.toRadians(75)); // give the polygons color treeLeaves.setColor(new Color(0x008000)); treeTrunk.setColor(new Color(0x714311)); } public void update(long elapsedTime) { if (exit.isPressed()) { stop(); return; } // cap elapsedTime elapsedTime = Math.min(elapsedTime, 100); // rotate around the y-axis treeTransform.rotateAngleY(0.002f*elapsedTime); // allow user to zoom in/out if (zoomIn.isPressed()) { treeTransform.getLocation().z += 0.5f*elapsedTime; } if (zoomOut.isPressed()) { treeTransform.getLocation().z -= 0.5f*elapsedTime; } } public void draw(Graphics2D g) { // erase background g.setColor(Color.black); g.fillRect(0, 0, screen.getWidth(), screen.getHeight()); // draw message g.setColor(Color.white); g.drawString("Press up/down to zoom. Press Esc to exit.", 5, fontSize); // draw the tree polygons trandformAndDraw(g, treeTrunk); trandformAndDraw(g, treeLeaves); } /** Projects and draws a polygon onto the view window. */ private void trandformAndDraw(Graphics2D g, SolidPolygon3D poly) { transformedPolygon.setTo(poly); // translate and rotate the polygon transformedPolygon.add(treeTransform); // project the polygon to the screen transformedPolygon.project(viewWindow); // convert the polygon to a Java2D GeneralPath and draw it GeneralPath path = new GeneralPath(); Vector3D v = transformedPolygon.getVertex(0); path.moveTo(v.x, v.y); for (int i=1; i<transformedPolygon.getNumVertices(); i++) { v = transformedPolygon.getVertex(i); path.lineTo(v.x, v.y); } g.setColor(poly.getColor()); g.fill(path); } }
Simple3DTest1 draws a tree that constantly spins around the y-axis. You can move the tree closer to or farther from the camera by pressing the up/down keys. See the screenshot in Screenshot.
Screenshot Screenshot of Simple3DTest1.
In Simple3DTest1, instead of using a plain Polygon3D, you use the SolidPolygon3D subclass. The 3D polygons are drawn in the transformAndDraw() method. This method transforms and projects the polygon. To draw a polygon, you first convert it to a GeneralPath (part of the java.awt.geom package). The Graphics2D interface provides methods to fill GeneralPaths, so the polygon drawing is done for you. Notice that the tree polygons are never actually transformed. They are copied to a scratch polygon, which is then transformed. If the actual tree polygons were transformed, over time they would eventually accumulate floating-point errors. One transform isn't enough to create any noticeable error, but after many transforms, errors might start to creep in and could result in distorted polygons. So, copy the polygon to the scratch polygon and then transform the scratch polygon. A few problems crop up with Simple3DTest1. Perhaps you noticed a few of them:
- The camera is fixed at (0,0,0). Even though the tree can move toward or away from this point along the z-axis, you want to be able to move the camera around freely.
- When the tree is very close to the camera, the polygons are drawn in strange ways. Also, if you zoom in too far (past the tree), the tree appears upside down! This occurs because the polygons aren't clipped to the view frustum.
- You can barely see it, but sometimes the polygons don't exactly line up with one another-you can sometimes see cracks or spaces between them. There are two reasons for this: First, Graphics2D, by default, fills the GeneralPath with its vertices rounded to integer coordinates instead of keeping the vertices at their floating-point coordinates. Second, two abutting polygons against each other creates unwanted T-junctions because they don't share common vertices. Forget about T-junctions for now; you'll fix them later in , "3D Scene Management Using BSP Trees."
- Finally, the tree is flat, which is kind of boring. Solid trees and other solid shapes would be more interesting.
Let's get started on fixing these problems! First up: free camera movement.