Source Code - Java Applet I
Any of today's programming languages can be used to generate 3D images of varying
degrees of performance and quality. The standard Java language has various
features which allow the creation of 3D applications. Even though a new generation
of Java API called Java3D promises improved performance and reduced coding effort
most PCs only have a copy of Java installed. Few PCs have the Java3D software
users are often unwilling to download the required megabytes of files to get the capability.
This page describes an applet called gbCube which displays a rotating cube. I've
also written a more generic 3D object viewer call gbViewer which can display 3D objects
read in from standard 3D file formats.
Overview
HTML
Source Code Discussion
Download
gbViewer
Return to top of document
gbCube Overview
If you've read my page on generating a 3D rotating cube with VB, then you'll already
have a good idea of what to expect on this page. I've written a Java Applet which will
display a 3D rotating cube. I list the code below, along with discussion to explain
how the code works and special issues or concerns that a programmer should be aware of.
Regardless of the language used to create a 3D graphic scene, the same basic set of
steps must be accomplished. The entire sequence is called a 3D graphics pipeline
and consists of the following major steps:
- Modelling
- Rotation
- Depth Sorting (Painter's Algorithm)
- Backface culling
- Projection
- Shading
There are other steps as well, such as lighting, but this page will be limited to
just those topics listed above. Future updates to the Java Applet are likely which
will expand the capabilities that are included within the applet.
Here is the current version of the applet:
Return to top of document
HTML
Whether using pure Java or Java3D, the mechanics of embedding an applet into an HTML
page are the same. This section provides a discussion of the HTML code needed to
embed the applet.
<html>
<head>
<title></title>
</head>
<body>
<h1>Java Applet Demo</h1>
<applet code=3dcubejava.class width=300 height=400>
<param name=Speed value=10>
<param name=POV value=10>
<param name=Theta value=.05>
</applet>
</body>
</html>
In this simple example a Java applet named "3dcube.class" is embedded
in the HTML page, with a window of size 300x400 pixels reserved for
the output of the applet.
Three parameters (Speed, POV, and Theta) are passed from the HTML code
to the Java applet, with values of 10, 10, and 0.05 respectively.
Return to top of document
gbCube - Pure Java Rotating 3D Cube
It is entirely possible to create 3D images using pure Java code - without using the recent
Java3D advanced features. This section examines the source code of gbCube, a Java applet
which creates a rotating 3D cube using only built-in Java features.
Program Operational Overview
The paint() method of the applet is used to call out the various
elements of the 3D graphics pipeline. The paint() method is
set for a continuous loop through the use of the repaint() method.
A 25ms time delay is used to control the speed of rotation.
The paint() method is as follows:
public void paint(Graphics screen) {
if (Continue) {
SortByDepth(screen);
BackFaceCulling(screen);
ApplyProjection(screen);
DrawCube(screen);
try { Thread.sleep(Delay); } catch _
(InterruptedException e) {} //delay
RotatePoints(screen);
repaint();
}
}
The screen object is shared between the various pipeline method. It shows as
an argument for each of the methods.
The shading used by gbCube is called flat shading - all the pixels of a triangle
are colored exactly the same, as provided by the Java fillPolygon method.
This approach is simple to use and very fast but is not very realistic.
It does not provide color gradients nor does it take into account shadows
resulting from the 3D scene's light source. Future enhancements
to gbCube will include a light source and more advanced shading algorithms,
such as Phong shading.
As was noted in the 3D math page at this site, standard trigonometric calculations or
matrix operations can be used to perform the calculations needed to animate a 3D scene.
In the example that follows matrices are not used. Code examples of matrix math are,
however, provided elsewhere at this site.
Modelling
To be consistent with discussions elsewhere on this site, the cube is modelled
using triangles. Twelve triangles are needed, 2 for each of the 6 cube faces. The
cube model requires only 8 points, with points shared by multiple triangles.
This model uses arrays to store point and triangle information. The following
initialization of variables is used:
double Theta = 0.005; //angle of rotation in radians
double L = 50; //temp variable used to love Px,Py,Pz,PPx,PPy
double POV = 500; //distance from eye to display screen
double Offset = 100; //used to center the cube in the applet window
Polygon T = new Polygon(); //Polygon object used for drawing triangles
int Delay = 25; //delay between rotations in milliseconds
double[] Px = {-L,-L, L, L,-L,-L,L, L}; //real point x-coord, 8 pts
double[] Py = {-L, L, L,-L,-L, L,L,-L}; //real point y-coord, 8 pts
double[] Pz = {-L,-L,-L,-L, L, L,L, L}; //real point z-coord, 8 pts
double[] PPx = {-L,-L, L, L,-L,-L,L, L}; //projected point x-coord, 8 pts
double[] PPy = {-L, L, L,-L,-L, L,L,-L}; //projected point y-coord, 8 pts
int V1temp, V2temp, V3temp; //temp variables used in sorting
double V4temp, V5temp; //temp variables used in sorting
double oldX, oldY, oldZ; //temp variables used in rotating
int[] V1 = {0, 0, 4, 4, 7, 7, 3, 3, 2, 2, 3, 3,}; //vertex1
int[] V2 = {3, 2, 0, 1, 4, 5, 7, 6, 6, 5, 0, 4,}; //vertex2
int[] V3 = {2, 1, 1, 5, 5, 6, 6, 2, 5, 1, 4, 7,}; //vertex3
double[] V4 = new double[12]; //Average Z of all 3 vertices
double[] V5 = new double[12]; //DotProduct of Normal and POV
double CPX1,CPX2,CPX3,CPY1,CPY2,CPY3; //temp var used in Cross Product
double CPZ1,CPZ2,CPZ3,DPX,DPY,DPZ; //temp var used in Cross/Dot Product
boolean Continue = true; //controls whether painting continues
The Px, Py, and Pz arrays contain the coordinates of the 8 points. The
PPx and PPx arrays contain the projections of those points onto the computer screen.
The x, y, and z coordinates of the vertices of the 12 triangles are kept in arrays
V1, V2, and V3. The order of the initial point assignment is made to ensure a
counter-clockwise listing. The coordinates assume the cube is centered about 0,0,0.
The average Z value of the vertices in each triangle are kept in array V4. This
value is used for sorting triangles by depth - part of the Painter's algorithm
for drawing 3D scenes.
The Dot Product of the normal to each triangle and the scene's point of view is kept
in array V5. This value is used to determine whether a triangle is facing the
viewer and should then be drawn, or facing away and does need to be drawn.
The Continue variable is used to escape the repeat of the paint() method. It is
set to false whenever the browser tells the applet to stop.
Each triangle, consisting of 3 vertices, is loaded into the Polygon object T
before drawing. The variable is loaded (with vertex coordinates) and cleared
each time a triangle needs to be drawn.
Initialization of the global variables and arrays is performed outside the init()
method only because it was simpler to write the code that way. Normally, initialization
of variables would take place within the init() method.
An alternate approach to defining 8 common points from which all triangles were made,
could have to define each triangle with 9 coordinates (no storage of point information).
That approach would have used 9 arrays, one for each coordinate value making up each triangle
(3 points x 3 axes). Either approach would work, but the common point approach results in
fewer calculations and faster speed.
Java provides the fillPolygon method for filling screen areas bounded by points.
In this case, the x,y coordinates of each triangle vertex are incorporated into
a polygon using the addPoint method, followed by use of the fillPolygon method
to render the triangles. An additional call is made to the Java method drawPolygon
to draw the edges of the triangles in a different color - improving the visibility
of the cube.
Depth Sorting
At the end of the 3D graphics pipeline, the DrawCube
subroutine draws the triangles one at a time in the order
the vertices are stored in arrays V1, V2, and V3.
To improve the realism of the drawing, gbCube
calculates the average z coordinate of each triangle
and then sorts the triangles (arrays V1, V2, and V3)
the objects farthest away are drawn first. The z-depth
of each triangle is stored in the array V4.
This approach is called the Painter's Algorithm and ensures
that the nearest objects will be in front of the farthest
objects. It works well but has limitations, such as not
working well for intersecting triangles. There are variations
of the Painter's Algorithm which address these shortcomings
but gbCube uses only the sort routine. For a simple cube
this approaches works just fine.
public void SortByDepth(Graphics screen) {
for (int w = 0; w < 12 ; w++) {
V4[w] = (Pz[V1[w]]+Pz[V2[w]]+Pz[V3[w]]) / 3;
}
for (int g = 0; g < 11 ; g++) {
for (int h = 0; h < 12; h++) {
if (V4[g] < V4[h]) {
V1temp = V1[g]; V2temp = V2[g]; V3temp = V3[g]; _
V4temp = V4[g]; V5temp = V5[g];
V1[g]=V1[h]; V2[g]=V2[h]; V3[g]=V3[h]; _
V4[g]=V4[h]; V5[g]=V5[h];
V1[h]=V1temp; V2[h]=V2temp; V3[h]=V3temp; _
V4[h]=V4temp; V5[h]=V5temp;
}
}
}
}
The sort algorithm used here is called a bubble sort. It works
well enough for a few hundred triangles to be sorted, but is not
suited for more complex 3D scenes. Other sort routines can be
written which sort up to a hundred times faster. These will be
included in future gbCube updates.
BackFaceCulling
As has been discussed, any triangle pointing away from the point
of view cannot be seen - it's on the back side of the 3D scene.
Identifying such triangles, and not displaying them, is
called backface culling and typically results in eliminating
the need to display about half of the triangles in the 3D scene.
public void BackFaceCulling(Graphics screen) {
for (int w = 0; w < 12 ; w++) {
// Cross Product
CPX1 = Px[V2[w]] - Px[V1[w]];
CPY1 = Py[V2[w]] - Py[V1[w]];
CPZ1 = Pz[V2[w]] - Pz[V1[w]];
CPX2 = Px[V3[w]] - Px[V1[w]];
CPY2 = Py[V3[w]] - Py[V1[w]];
CPZ2 = Pz[V3[w]] - Pz[V1[w]];
DPX = CPY1 * CPZ2 - CPY2 * CPZ1;
DPY = CPX2 * CPZ1 - CPX1 * CPZ2;
DPZ = CPX1 * CPY2 - CPX2 * CPY1;
// DotProduct uses POV vector 0,0,POV as x1,y1,z1
V5[w] = 0 * DPX + 0 * DPY + POV * DPZ;
}
}
This code first calculates the cross product of the first two line segments
of each triangle. Then a dot product is calculated between the resulting
normal vector and the POV vector. The result for each triangle is stored
in array V5.
When the code is executed to draw the triangles, only those facing the
viewer (positive dot product) will be drawn.
Another very key point to notice in the source code is that the cross
product equations you've seen so far assume that the vector components
represent position vectors - with starting points at the origin (0,0,0).
To calculate the cross product between two triangle edges you must use
the displacement vectors which are calculated by the difference of the
starting and ending points of the triangle line segments.
CalculatePointProjections
Until now all point coordinates have been world coordinates - the position
of the 3D cube in space. These point coordinates must now be projected
onto the computer screen - onto a 2D surface. The subroutine is as follows:
// calculate projection coordinates
for (int s = 0; s < 8 ; s++) {
PPx[s] = Px[s]+Offset;
PPy[s] = Py[s]+Offset;
}
Each point of the 3D scene must be displayed on the 2D computer screen.
The mapping of the points from the 3D scene to the computer screen is
called point projection. There are two general forms of projection,
parallel and perspective.
With parallel projection, the x-y coordinates of a 3D point simply map
one-to-one to the computer screen. The z coordinates are simply dropped.
While very simple to perform, the resulting images do not display
realistic images in that objects far away will appear to be the same
size as objects close in.
gbCube uses parallel projection only because I haven't gotten around
to adding the projection equations. This should be added soon.
Perspective projection, which uses the z dimensions to
adjust the 2D images to create more realistic images. With perspective
projection, objects farther away will appear smaller in the resulting
This simulates real life views of scenes with depth.
Shading
The final step in the 3D graphics pipeline used by gbCube is to draw the
cube on the computer screen. The x-y coordinates of the three vertices
in each triangle are used in the Java fillPolygon and drawPolygon methods
to display the cube. Flat shading is used (same color for all pixels
within a triangle). Later versions of this applet will include
improved rendering, such as Phong shading.
public void DrawCube(Graphics screen) {
screen.clearRect(0,0,getSize().width, getSize().height);
for (int w = 0; w < 12 ; w++) {
screen.setColor(Color.red);
if (V5[w] > 0) {
T.addPoint ((int)(PPx[V1[w]]+Offset), (int)(PPy[V1[w]]+Offset));
T.addPoint ((int)(PPx[V2[w]]+Offset), (int)(PPy[V2[w]]+Offset));
T.addPoint ((int)(PPx[V3[w]]+Offset), (int)(PPy[V3[w]]+Offset));
screen.fillPolygon(T);
screen.setColor(Color.blue);
screen.drawPolygon(T);
T.reset();
}
}
}
Note that both the drawPolygon and fillPolygon methods are used. This is done using
two colors so that the edges of the cube are clear. The drawing is made
using the projection x-y coordinates, not the true coordinates of the 3D cube points.
If the fillPolygon statement were commented out gbCube would display only a wire-frame image.
Following the display and shading of all triangles a 25ms time delay is
introduced, with code as follows:
try { Thread.sleep(Delay); } catch (InterruptedException e) {}
Conventional Java syntax would have resulted in spreading the timer code over
multiple lines, but it was visually convenient to have the code listed on
a single line.
Rotation
The last step in the gbCube 3D graphics pipeline is to rotate each of the eight
points through an angle of rotation, in preparation for the next cycle.
While separate angles of rotation for each axis are possible, gbCube uses the
same angle of rotation for each axis.
Rotation about each axis is calculated separately and successively applied
to get the accumulative effect of rotation about all 3 axes.
public void RotatePoints(Graphics screen) {
for (int w=0; w < 8; w++) {
oldY = Py[w]; oldZ = Pz[w];
Py[w] = oldY * Math.cos(Theta) - oldZ * Math.sin(Theta);
//rotate about X
Pz[w] = oldY * Math.sin(Theta) + oldZ * Math.cos(Theta);
//rotate about X
oldX = Px[w]; oldZ = Pz[w];
Px[w] = oldZ * Math.sin(Theta) + oldX * Math.cos(Theta);
//rotate about Y
Pz[w] = oldZ * Math.cos(Theta) - oldX * Math.sin(Theta);
//rotate about Y
oldX = Px[w]; oldY = Py[w];
Px[w] = oldX * Math.cos(Theta) - oldY * Math.sin(Theta);
//rotate about Z
Py[w] = oldX * Math.sin(Theta) + oldY * Math.cos(Theta);
//rotate about Z
}
}
|