Coordinates in JSR 184: A very simple example

I love the way the JavaDoc for Graphics3D encouragingly starts with “Using the Graphics3D is very straightforward” just before launching into the precisions about the different types of rendering modes and targets, viewports, antialiasing, dithering, etc. 😉

JSR 184 gives an incredibly rich API for creating 3-D animations (and especially games) for a MIDP handset. It’s designed to allow you to import complex computer-generated 3-D objects as well as to create your own 3-D objects by hand, with a fantastic array of options in terms of lighting, surface textures, etc. It also comes with an equally rich assortment of new terms (Mesh, IndexBuffer, TriangleStripArray, CompositingMode…) that can look like a foreign language to those new to 3-D programming. And just understanding how all of the different coordinate systems work and interact with each other is not trivial.

Not to be discouraging or anything — it’s not so daunting if you start simple. So my programming challenge that I assigned myself for today was to write the simplest 3-D example possible that illustrates the basics of how the coordinate system works and how to define a 3-D polygon.

My example is a pyramid with a square base viewed from above.

This is a two-part example: In part one, I start with just the square base with two of the triangular sides (opposite each other) attached. So picture a square paper with a triangle attached along the right edge and the left edge, and the two triangles folded up to meet at a point at the top. In part two, I start with this same incomplete pyramid and glue on a third side. From there, completeing the pyramid should be no problem. 😀

I’m going to start by posting the code so you can have a look, and then I’ll explain in a little more detail, particularly how the VertexArray works and how the TriangleStripArray defines how to piece the vertices together to form a polygon.

DemoCanvas.java:


package net.frog_parrot.test;

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

/**
* This is a very simple example class to illustrate 3-D coordinates.
*/
public class DemoCanvas extends Canvas {

/**
* The information about where the scene is viewed from.
*/
private Camera myCamera;

/**
* The background. (self-explanatory 😉 )
*/
private Background myBackground = new Background();

/**
* The set of vertices.
*/
private VertexBuffer myVertexBuffer;

/**
* The object that defines how to map the set of vertices into
* a polygon.
*/
private IndexBuffer myIndexBuffer;

/**
* Information on how the polygon should look in terms of
* color, texture, shading, etc..
*/
private Appearance myAppearance;

/**
* The list of vertices for the first example pyramid.
*/
private short[] myVertices1 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
};
/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip1 = { 6 };

/**
* The list of vertices for the second example pyramid.
*/
private short[] myVertices2 = {
0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10,
0, -10, 0, 10, 0, 0, 0, 0, 10
};
/**
* The rule for how to piece together the vertices into a polygon.
*/
private int[] myTriangleStrip2 = { 6, 3 };

/**
* Initialize everything.
*/
public DemoCanvas() {
try {
// Create the camera object to define where the polygon is being
// viewed from and in what way:
myCamera = new Camera();
// Set the camera so that it will project the 3-D picture onto the
// screen in perspective, with a vanishing point in the distance:
myCamera.setPerspective(60.0f, (float)getWidth() / (float)getHeight(),
1.0f, 1000.0f);

// Here we construct the VertexArray, which is a generic data
// structure for storing collections of coordinate points:
int numVertices = myVertices1.length / 3;
// specify how many vertices, plus the fact that each vertex has
// three coordinates, and each coordinate is coded on two bytes:
VertexArray va = new VertexArray(numVertices, 3, 2);
// set the data, starting from index 0:
va.set(0, numVertices, myVertices1);

// Now create a 3-D object of it.
// Here we could group a set of different VertexArrays, one
// giving positions, one, giving colors, one giving normals,
// but for simplicity we're only setting position coordinates:
myVertexBuffer = new VertexBuffer();
myVertexBuffer.setPositions(va, 1.0f, null);
// Color the polygon white:
myVertexBuffer.setDefaultColor(0xffffff);

// Here we define how to piece together the vertices into
// a polygon:
myIndexBuffer = new TriangleStripArray(0, myTriangleStrip1);

// We want the appearance as simple as possible, so set the
// appearance to polygon mode:
PolygonMode pm = new PolygonMode();
pm.setShading(PolygonMode.SHADE_FLAT);

myAppearance = new Appearance();
myAppearance.setPolygonMode(pm);

// color the background black:
myBackground.setColor(0x000000);
} catch(Exception e) {
e.printStackTrace();
}
}

/**
* Paint the graphics onto the screen.
*/
protected void paint(Graphics g) {
try {
// Start by getting a handle to the Graphics3D
// object which does the work of projecting the
// 3-D scene onto the 2-D screen (rendering):
Graphics3D g3d = Graphics3D.getInstance();
// Bind the Graphics3D object to the Graphics
// instance of the current canvas:
g3d.bindTarget(g);
// Clear the screen by painting it with the
// background image:
g3d.clear(myBackground);

// Now set where we're viewing the scene from:
Transform cameraTransform = new Transform();
// We set the camera's X position and Y position to 0
// so that we're looking straight down at the origin
// of the x-y plane. The Z coordinate tells how far
// away the camera is -- increasing this value takes
// you farther from the polygon, making it appear
// smaller. Try changing these values to view the
// polygon from different places:
cameraTransform.postTranslate(0.0f, 0.0f, 100.0f);
g3d.setCamera(myCamera, cameraTransform);

// Now set the location of the object.
// if this were an animation we would probably
// translate or rotate it here:
Transform objectTransform = new Transform();
objectTransform.setIdentity();

// Now render: (Yay!!! finally!!!)
g3d.render(myVertexBuffer, myIndexBuffer, myAppearance, objectTransform);

// Done, the canvas graphics can be freed now:
g3d.releaseTarget();

} catch(Exception e) {
e.printStackTrace();
}
}

}

***

The first example (using myVertices1 and myTriangleStrip1) gives a result that looks like this:

pyramid1.png

The second example (using myVertices2 and myTriangleStrip2) gives a result that looks like this:

pyramid2.png

In the first example, I constructed my TriangleStripArray with the arguments 0 and a one-element array: { 6 }. That means start from the first vertex in the vertex array (actually the zeroth element — you know what I mean), and then make one strip of triangles from the six vertices. The triangles are defined by taking all sets of three consecutive vertices, the first one starting from the first vertex, the second one starting from the second vertex, etc. So each triangle in the triangle strip shares a side with the next triangle and shares another side with the previous triangle as you can see from this example:

Vertex array #1 looks like this: { 0, 0, 10, 10, 0, 0, 0, 10, 0, 0, -10, 0, -10, 0, 0, 0, 0, 10 },– where every three values together form one vertex and three vertices form a triangle — so the corresponding strip is made of the following set of trianges: { (0, 0, 10), (10, 0, 0), (0, 10, 0) }, { (10, 0, 0), (0, 10, 0), (0, -10, 0) }, { (0, 10, 0), (0, -10, 0), (-10, 0, 0) }, { (0, -10, 0), (-10, 0, 0), (0, 0, 10) }. (Here I’ve surrounded the xyz-coordinates of each vertex in parentheses and each triangle in brackets so you can see the list of triangles more easily — this doesn’t represent any syntax that appears in the code.) Note that the first (and last) vertex is the top point and the middle two triangles together form the square base of the pyramid.

Even though the square base is part of the polygon, it turned out black on the screen. That’s because by default the inside faces are invisible; not rendered.  If you’d like the inside faces to be visible, then set your PolygonMode’s culling to CULL_NONE. The computer determines which side is “inside” and which side is “outside” by whether the vertices of the triangle are defined in clockwise or counter-clockwise order. I’d explain how to figure out which side is which if I weren’t dyslexic — normally in such cases I guess, then compile and run, then invert the values if I guessed wrong. 😉

In the second example, I constructed my TriangleStripArray with the arguments 0 and a two-element array: { 6, 3 }. This creates the same strip of triangles as the first one, and then makes another strip of triangles from the next three vertices it finds. Three vertices make one triangle, so using the larger second example array we get the additional triangle { (0, -10, 0), (10, 0, 0), (0, 0, 10) } giving one additional side. This side is white the way I’ve defined it, but if I’d defined it in the wrong order (i.e. attached it backwards), it would appear black from my camera’s angle. (Note that I could actually have used myVertices2 for both examples and the result would have been the same.)

For completeness, I’ll post the simple MIDlet class that goes with this. Also note that this program uses the type float, and I found I had to set my project to CLDC-1.1 (under Settings > API Selection in ktoolbar) to get it to compile:


package net.frog_parrot.test;

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

/**
* A simple 3D example.
*/
public class TestMIDlet extends MIDlet implements CommandListener {

private Command myExitCommand = new Command("Exit", Command.EXIT, 1);
private DemoCanvas myCanvas = new DemoCanvas();

/**
* Initialize the Displayables.
*/
public void startApp() {
myCanvas.addCommand(myExitCommand);
myCanvas.setCommandListener(this);
Display.getDisplay(this).setCurrent(myCanvas);
myCanvas.repaint();
}

public void pauseApp() {
}

public void destroyApp(boolean unconditional) {
}

/**
* Change the display in response to a command action.
*/
public void commandAction(Command command, Displayable screen) {
if(command == myExitCommand) {
destroyApp(true);
notifyDestroyed();
}
}

}

Advertisements

2 comments so far

  1. carolhamer on

    Okay, I’m just kidding about being dyslexic — I’m not. But I’m not kiding about the fact that a lot of times if there are exactly two orientations for something in a program (i.e. you’ve got it right or it’s in backwards), it’s faster to figure out the right way through experimentation and then — once it’s right — figure out why it worked the way it did.

    Not everything in programming works that way, of course. With threading and synchronization questions, for example, you absolutely can’t rely on a quick compile-and-run to see if you’ve got it right.

    Still, to make up for the sloppiness of not going into detail on how to decide which side of your triangle strip is the inside and which is the outside, allow me to direct your attention to this quick explanation.

    In a nutshell, the triangle strip array defines the order of the vertices of the triangle. If going from the first to the second to the third vertex of the triangle means going around the triangle counter-clockwise, then you’re looking at the outside.

    Unfortunately — since each triangle in the triangle strip shares two vertices (in order) with the next triangle — it turns out that if one triangle is given counter-clockwise, the next one will be clockwise. So your triangle strip logically would alternate between showing you the inside face of the triangle and the outside face of the next triangle, which isn’t what you really want in a reasonable surface. So the inside and outside of the entire triangle strip is determined by the inside and outside of the first triangle.

  2. […] As an example, I’ve taken my pyramid from my previous example and I’ve given it some normal vectors. Here’s the set of vertices: […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: