Your first 3D object

Your first 3D object

As a first example, you'll create a cube as displayed in Figure 1.


Figure 1. Sample cube: a) Front view with vertex indices, b) Side view with clipping planes (Front, Side)
Sample cube: (a) front view with vertex indices (b) side view with clipping planes

The cube lives in what M3G defines as a right-handed coordinate system. If you take your right hand and stretch out your thumb, index finger, and middle finger so that each finger is in a right angle to the other two fingers, then the thumb is your x axis, the index finger the y axis, and the middle finger the z axis. Try to align your thumb and index finger with the x and y axis in Figure 1a; your middle finger should then point toward you. I've used eight vertices (the cube's corner points) and placed the cube's center at the coordinate system's origin.

As you can see in Figure 1, the camera that shoots the 3D scene looks toward the negative z axis, facing the cube. The camera's position and properties define what the screen later displays. Figure 1b shows a side view of the same scene so you can easily see what part of the 3D world is visible to the camera. One limiting factor is the viewing angle, which is comparable to using a camera lens: a telephoto lens has a narrower view than a wide-angle lens. The viewing angle thus determines what you can see to the sides. Unlike the real world, 3D computing gives you two more view boundaries: near and far clipping planes. Together, the viewing angle and the clipping planes define what is called the view frustum. Everything inside the view frustum is visible, everything outside is not.

This is all implemented in the VerticesSample class, whose members you can see in Listing 1.




Listing 1. Sample displaying a cube, Part 1: Class members

package m3gsamples1;

import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;


/**
* Sample displaying a cube defined by eight vertices, which are connected
* by triangles.
*
* @author Claus Hoefele
*/
public class VerticesSample extends Canvas implements Sample
{
/** The cube's vertex positions (x, y, z). */
private static final byte[] VERTEX_POSITIONS = {
-1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1,
-1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1
};

/** Indices that define how to connect the vertices to build
* triangles. */
private static int[] TRIANGLE_INDICES = {
0, 1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1
};

/** The cube's vertex data. */
private VertexBuffer _cubeVertexData;

/** The cube's triangles defined as triangle strips. */
private TriangleStripArray _cubeTriangles;

/** Graphics singleton used for rendering. */
private Graphics3D _graphics3d;



VerticesSample inherits from Canvas to be able to draw directly to the display. It also implements Sample, which I defined to help organize this article's different source code samples. VERTEX_POSITIONS defines eight vertices in the same order, as shown in Figure 1a. For example, vertex 0 is defined as position (-1, -1, 1). Remember that I placed the cube's center at the coordinate system's origin; therefore, the cube's edges will be two units long. The camera's position and viewing angle will later define the amount of pixels a unit will occupy on the screen.

The vertex positions are not enough, however; you also have to say what geometry you want to build. Like in a dot-to-dot painting, you have to connect vertices with lines until you see the resulting drawing. M3G poses one restriction though: you must build the geometry from triangles. Triangles are popular with 3D implementations because you can define any polygon as a set of triangles. A triangle is a basic drawing operation on which you can build more abstract operations.

Unfortunately, if you had to describe the cube with triangles alone, you would need 6 sides * 2 triangles * 3 vertices = 36 vertices; this would be a waste of memory as many vertices are duplicated. To reduce memory, you first separate the vertices from their triangle definitions. TRIANGLE_INDICES defines the geometry using the indices of the VERTEX_POSITIONS array and can thus reuse vertices. Next, you reduce the number of indices by using triangle strips instead of triangles. With a triangle strip, new triangles reuse the preceding triangle's last two indices. For example, the triangle strip (0, 1, 2, 3) will translate to two triangles (0, 1, 2) and (1, 2, 3). When you follow the triangle definitions in TRIANGLE_INDICES with Figure 1a, where I marked each corner with the respective index, you'll see that the triangles jump wildly between sides. This is just a pattern to avoid the definition of several strips. I managed with eight vertices and one single triangle strip with 14 indices for the cube.

You use the rest of the class members to draw the cube. Listing 2 shows their initialization.




Listing 2. Sample displaying a cube, Part 2: Initialization
/**
* Called when this sample is displayed.
*/
public void showNotify()
{
init();
}


/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering.
_graphics3d = Graphics3D.getInstance();

// Create vertex data.
_cubeVertexData = new VertexBuffer();

VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
_cubeVertexData.setPositions(vertexPositions, 1.0f, null);

// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS.
_cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
new int[] {TRIANGLE_INDICES.length});

// Create a camera with perspective projection.
Camera camera = new Camera();
float aspect = (float) getWidth() / (float) getHeight();
camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
Transform cameraTransform = new Transform();
cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
_graphics3d.setCamera(camera, cameraTransform);
}



As a first step in init(), you obtain the graphics context for 3D drawing. Graphics3D is a singleton and _graphics3d holds a reference for later use. Next, you create a VertexBuffer to hold the vertex data. As you'll see later, you can assign several kinds of information to a vertex, and VertexBuffer contains all of them. Currently, the only information you need is vertex positions in a VertexArray that is set using _cubeVertexData.setPositions(). The constructor of VertexArray takes the number of vertices (eight), the number of components per vertex (x, y, z), and the size of each component (one byte). Because the cube is so small, one byte is enough to hold one coordinate. You can also create a VertexArray using short values (two bytes) if you want to create larger objects. However, you can't use real numbers, only integers. Next, you initialize a TriangleStripArray with the indices from TRIANGLE_INDICES.

The final part of the initialization code is the camera setup. In setPersective(), you set the viewing angle, the aspect ratio, and the clipping planes. Notice that the aspect ratio and the values for the clipping planes are float values. M3G requires floating point support, which has been available since CLDC 1.1, from the Java Virtual Machine (JVM). After setting the perspective, you move the camera away from the cube so you have a full view of the object. You do this with a translation, a topic I'll explain in more detail in the section about Transformations. For now, trust me that postTranslate() with a positive third parameter moves the camera along the z axis.

After the initialization, you render the scene to the screen. Listing 3 shows this.




Listing 3. Sample displaying a cube, Part 3: Drawing
/**
* Renders the sample on the screen.
*
* @param graphics the graphics object to draw on.
*/
protected void paint(Graphics graphics)
{
_graphics3d.bindTarget(graphics);
_graphics3d.clear(null);
_graphics3d.render(_cubeVertexData, _cubeTriangles,
new Appearance(), null);
_graphics3d.releaseTarget();
}




In paint(), bindTarget() assigns the Canvas's graphics context to Graphics3D. This enables rendering 3D objects until releaseTarget() is called. After a call to clear() erases the background, you draw the cube by using the vertex data and triangles created in init(). Many of Graphics3D's methods throw unchecked exceptions, but I decided to do without a try/catch block because most of the errors are unrecoverable. You can find this sample's complete source code in VerticesSample.java.

To display the sample, I wrote a simple MIDlet. You can get it from the download section together with the article's entire source code. You can see the result of running the sample in Figure 2.


Figure 2. The sample cube
The sample cube

It's difficult to tell that the rectangle on the screen is a cube because I placed the camera directly in front of it, which is like standing too close to a white wall. Why is it white? Because I haven't yet assigned any colors and white is the default. The next section will fix that.

(picked from IBM developer site)

Comments

Popular posts from this blog

JDBC comes to J2ME finally?

JSR-184 a.k.a. Micro3D a.k.a. Java Mobile 3D