Subscribe to News

How to start with Mobile 3D Graphics for J2ME

Author : Equizz

From TechnologicalWiki

Jump to: navigation, search

Contents

Introduction

Mobile 3D Graphics API for J2ME is specified in JSR 184 and included with the WTK (Wireless Toolkit) from 2.2 version. This proposed JSR will provide a scalable, small-footprint, interactive 3D API for use it on mobile devices (CLDC). This API is based on OpenGL so the concepts are very similar.

This article is going to show how to use the Mobile 3D Graphics API for java, with some code examples. You can start reading this article: Mobile 3D Graphics API for J2ME if you don't know so much about this. Also you can download the API documentation if you need more information.

There are two methods for programing 3D graphics, 'retained' and 'inmediate' modes. The first one is used for make complex figures ussing an external modling tool to create m3g files. On the other hand the 'inmediate' mode is the aim of this article, we are going to show examples of code for programing graphics only with code. However, it's posible to combine the two methods, we can import a world from an m3g file, and modify with code as we need.

[edit] Set graphics

This is the first thing that we have to do. We are going to use the paint method of the Canvas class, to bind Graphics3D with the canvas to render. For those examples, we suppose to have a myWorld (World) object with content, I will explain how to fill with content this object later.

public class MyCanvas extends Canvas
{
   Graphics3D myG3D = Graphics3D.getInstance();

   public void paint(Graphics g) {
   try {
      myG3D.bindTarget(g);
      // ... update the scene ...
      // ... render the scene ...
   } finally {
      myG3D.releaseTarget();
   }
}

[edit] Update

We set the world to the currentTime (int). The fps (frames per second) that the human eye can apreciate is 12 fps, so 20 fps is a good choice if our hardware allows it. It should be into a loop that repaints every time.

myWorld.animate(currentTime);
currentTime += 50;      // assume we can handle 20 frames per second

[edit] Render

Here we are rendering all the elements (myWorld), but we can do it with any other node Group.

g3d.render(myWorld);    // render a view from the active camera
g3d.releaseTarget();    // flush the rendered image

[edit] Load 3D objects from a M3G file

M3G files can be made with a 3D modeling application, and its designed to use it with j2me. Thats the way for making complex 3D objects or scenes with an external apllication, instead of coding everything.

This is a safe way to load objects from a .m3g file. It's supposed to be a "myworld.m3g" file in the resource directory of the project. Thake care with the name of the file, because it's case sensitive. In this example the object World is loaded.

try {
    Object3D[] buffer = Loader.load("/myworld.m3g");

    for(int i = 0; i < buffer.length; i++) {
        if(buffer[i] instanceof World) {
            world = (World)buffer[i];
            break;
        }
        ...
    }

    buffer = null;
} catch(Exception e) {}

[edit] Background

Tha background is an attribute of the object "World".

Fist let's create a Background object.

   Background bg = new Background();

We can set a color for the background in ARGB format (Alpha, Red, Green, Blue).

   bg.setColor(0x00FF0000);    // red

Now we can set the background object into the world.

   myWorld.setBackground(bg);

It's posible to set an image as backgroud instead of the a plain color. The format of the image can be all the MIDP image formats (jpg, png...).

Image bgImage;
try {
    bgImage = Image.createImage("/myimage.jpg");
    Image2D bgImage2D = new Image2D(Image2D.RGB, bgImage);
    bg.setImage(bgImage2D);
} catch (Exception e) {}

Sometimes we will need to show a part of an image file as background. The setCprop method allows to define a rectamgle in the image, so the back ground image will be the image inside of this. The X and Y positions are relative to the top left corner of the image pixels.

   bg.setCrop(int x, int y, int width, int height);

When you set a crop rectangle that exceeds the pixels of the image, by default you can see the background color. You can configure it to repeat the image in the x and y axis using two modes: BORDER or REPEAT. In this example... we set the background to repeat the image in the X axis and keep the background color in the Y axis, when the crop rectangle overflows the image dimensions.

   bg.setImageMode(int modeX, int modeY);

For example, if we want to create an sky, we will need to repeat the image in the X axis. When moving the camera, we will have to change the X position of the crop to simulate the movement. The image for this example is a 200 x 50 (width x height), here is a sample of code:

...
bg.setImageMode(Background.REPEAT, Background.BORDER);
bg.setCrop(0,0,100,100);
...
// moving right
bg.setCrop(bg.getCropX()+3, bg.getCropY(), bg.getCropWidth(), bg.getCropHeight());

[edit] Create a Mesh

This is a scene graph node that represents a 3D object defined as a polygonal surface.

[edit] Defining the points

First we have to define al the points in the figure, so we are going to use a VertexArray object. As we are in a 3D context, we have 3 coordenate axis, so 3 is the number of the components of a point.

byte[] coords = {x1,y1,z1,... xn,yn,zn};    // vertex coordenates
int numComponents = 3;    // components per vertex (one by axis)
int compSize = 1;    // bytes per component, must be [1, 2]
int numVertex = coords.length/numComponents;

// define the VertexArray
VertexArray vertex = new VertexArray(numVertex, numComponents, compSize);

// set the coordenates of the points
vertex.set(0,numVertex,coords);

[edit] Defining the vertex

First, we create a VertexBuffer, then we can set the positions of the vertex stored in the VertexArray. We can scale and translate all the points according to this V' = V*sacale + bias

float scale = 1.0f;
float[] bias = null;
VertexBuffer myVertexBuffer = new VertexBuffer();

myVertexBuffer.setPositions(vertex, scale, bias);

[edit] Joining vertex

The VertexBuffer stores all the vertex of the mesh, now we need to define how to join them to crate the mesh. We are going to define the mesh by triangles, that's the only way of doing it now. The triangles are defined in groups of 3, for example: S = (2, 0, 1, 4) defines two triangles: (2, 0, 1) and (1, 0, 4). The stripLengths defines the strips of the mesh, being equal to the number of indices, for example, if there are two strips with lengths 3 and 4, and the initial index is 10, the strips are formed as follows: S1 = (10, 11, 12) and S2 = (13, 14, 15, 16).

// An example of a pyramid...
int[] indices = {2, 4, 1, 0, 2, 3, 4, 0};
int[] stripLengths = {8};

TriangleStripArray myTriangleStripArray = new TriangleStripArray(indices, stripLengths);

[edit] Creating the mesh

Now we can construct the mesh. We can create an apparience object also, but its not necessary to configure it now. Our mesh shows in white because we didn't define any colors.

Appearance myAppearance = new Appearance();
Mesh myMesh = new Mesh(myVertexBuffer, myTriangleStripArray, new Appearance());

[edit] Appearance

[edit] Color

To set a color for the mesh, we have to add a VertexArray of colors to the VertexBufer of the mesh. It's very similar to the coords. We have to set a color per vertex.

byte[] COLORS = { (byte)255,(byte)255,(byte) 0,    // vertex 1 (Yellow)
                  ...
                  (byte)R, (byte)G, (byte)B };     // vertex N

VertexArray myColors = new VertexArray(COLORS.length/3, 3, 1);
myColors.set(0, COLORES_VERTICE.length/3, COLORES_VERTICE);

myVertexBuffer.setColors(myColors);

There are two choices for polygon shading, smooth and flat. Smooth shading means that a color is computed separately for each pixel. This may be done by linear interpolation between vertex colors (also known as Gouraud shading), but implementations are also allowed to substitute a more accurate model. Flat shading means that the color computed for the third vertex of a triangle is used across the whole triangle.

PolygonMode mode = new PolygonMode();
mode.setShading(PolygonMode.SHADE_FLAT);    // or PolygonMode.SHADE_SMOOTH
myAppearance.setPolygonMode(mode);

The appearance should be bond to a mesh object.

[edit] Material

The format of the colors is in 0xAARRGGBB.

When setVertexColorTrackingEnable is enabled, the AMBIENT and DIFFUSE material colors will take on color values from the associated VertexBuffer on a per-vertex basis. The ambient and diffuse color values of this Material are ignored in that case.

Material myMaterial = new Material();
myMaterial.setColor(Material.AMBIENT,0xffffffff);
myMaterial.setColor(Material.SPECULAR,0xffff0000);
myMaterial.setColor(Material.DIFFUSE,0xffffffff);
myMaterial.setColor(Material.EMISSIVE,0xff000000);
myMaterial.setShininess(1.0f);
myMaterial.setVertexColorTrackingEnable(false);

[edit] Fog

We have two fog modes, lineal and exponential. In exponential mode we have to define the density, and int lineal mode we have to set the near and far distances.

Fog myFog = new Fog();
myFog.setColor(0xddeeff);    // RGB

myFog.setMode(Fog.EXPONENTIAL);
myFog.setDensity(0.1f);

// or

myFog.setMode(Fog.LINEAR);
myFog.setLinear(0.0f, 1.0f);    // near and far distances

myAppearance.setFog(myFog);

[edit] Texture2D

We have to define the image for the texture and set it into the Appearience.

try {
    Image2D image2D = (Image2D) Loader.load("/textureFile")[0];
    myTexture = new Texture2D(image2D);
    myTexture.setBlending(Texture2D.FUNC_DECAL);
    myAppearance.setTexture(0, myTexture);
} catch (Exception e) {
    System.out.println("Error loading image " + "textureFile");
    e.printStackTrace();
}

This is an example for a cube. We use the same VertexBuffer where we set the positions. First we have to make a VertexArray and fill it with the texture coordinates. Then we set the texture coords (VertexArray) to the VertexBuffer.

private static final byte[] VERTEX_TEXTURE_COORDINATES = {
    0, 1,   1, 1,   0, 0,   1, 0,   // front
    0, 1,   1, 1,   0, 0,   1, 0,   // back
    0, 1,   1, 1,   0, 0,   1, 0,   // right
    0, 1,   1, 1,   0, 0,   1, 0,   // left
    0, 1,   1, 1,   0, 0,   1, 0,   // top
    0, 1,   1, 1,   0, 0,   1, 0,   // bottom
  };
myVertexData = new VertexBuffer();
...
VertexArray vertexTextureCoordinates =
        new VertexArray(VERTEX_TEXTURE_COORDINATES.length/2, 2, 1);
vertexTextureCoordinates.set(0,
        VERTEX_TEXTURE_COORDINATES.length/2, VERTEX_TEXTURE_COORDINATES);

myVertexData.setTexCoords(0, vertexTextureCoordinates, 1.0f, null);

[edit] Lights

Light is needed to see the objects. Only vertexColor and texture appearances don't need lights, but light give them realism.

The render set the shadows and reflexes depending on the configuration of normals. Those normal vectors are assigned to a mesh per vertex, and all the vertex of a side should have the same normal.

There are four light types:

  • Omnidirectional: It originates in one point and shines equally in all directions.
  • Directional: It emits parallel rays in one direction.
  • Spotlight: It's comparable to a flashlight or a spot used in theater.
  • Ambient: It illuminates objects from every direction at a constant rate.

Here an example:

myLight = new Light();
myLight.setMode(Light.SPOT);
myLight.setSpotAngle(25.0f);    // Only in spot mode
myLight.setIntensity(2.0f);

Transform lightTransform = new Transform();
lightTransform.postTranslate(0.0f, 0.0f, 4.0f);

graphics3d.resetLights();    // clear the array of current lights
graphics3d.addLight(myLight, lightTransform);

[edit] Cameras

The camera is always facing towards the negative Z axis, (0 0 -1), in its local coordinate system.

[edit] Perspective camera

It constructs a perspective projection matrix and sets that as the current projection matrix.

   float fovy   = 40.0f;    // field of view in the vertical direction, in degrees
   float aspect = (float) getWidth() / (float) getHeight();    // aspect ratio of the viewport
   float near   = 1.0f;    // distance to the front clipping plane
   float far    = 1000.0f    // distance to the back clipping plane

   cameraPerspective = new Camera();
   cameraPerspective.setPerspective(fovy, aspect, near, far);

[edit] Parallel camera

Constructs a parallel projection matrix and sets that as the current projection matrix.

   float fovy   = 10.0f;    // field of view in the vertical direction, in degrees
   float aspect = (float) getWidth() / (float) getHeight();    // aspect ratio of the viewport
   float near   = 1.0f;    // distance to the front clipping plane
   float far    = 1000.0f    // distance to the back clipping plane

   cameraParallel = new Camera();
   cameraParallel.setParallel(fovy, aspect, near, far);

[edit] Setting the cammera

Here we can set cameraPerspective or cameraParallel in Graphics3D.

   graphics3d.setCamera(cameraPerspective, cameraTransform);

[edit] Animations

The animation consists in defining changes in the attributes of the objects in the time. Those atributes can be position, color, size...

[edit] Creating an animation

Here we have an example of an animated light. In this code the AnimationTracks are defined, one of them associated with the color and the other with the translation.

Light light = new Light();	// Create a light node

// Load a motion path from a stream, assuming it's the first
// object there

Object3D[] objects = Loader.load("http://www.ex.com/ex.m3g");
KeyframeSequence motion = (KeyframeSequence) objects[0];

// Create a color keyframe sequence, with keyframes at 0 ms
// and 500 ms, and a total duration of 1000 ms. The animate
// method will throw an exception if it encounters a
// KeyframeSequence whose duration has not been set or whose
// keyframes are out of order. Note that the Loader
// automatically validates any sequences that are loaded from
// a file.

KeyframeSequence blinking = new KeyframeSequence(2, 3,
                                      KeyframeSequence.LINEAR);
blinking.setKeyframe(0,   0, new float[] { 1.0f, 0.0f, 0.0f });
blinking.setKeyframe(1, 500, new float[] { 0.0f, 1.0f, 0.0f });
blinking.setDuration(1000);

AnimationTrack blink = new AnimationTrack(blinking,
                                         AnimationTrack.COLOR);
AnimationTrack move = new AnimationTrack(motion,
                                   AnimationTrack.TRANSLATION);
light.addAnimationTrack(blink);
light.addAnimationTrack(move);

// Create an AnimationController and make it control both the
// blinking and the movement of our light

AnimationController lightAnim = new AnimationController();
blink.setController(lightAnim);
move.setController(lightAnim);

// Start the animation when world time reaches 2 seconds, stop
// at 5 s.  There is only one reference point for this
// animation: sequence time must be zero at world time 2000
// ms. The animation will be running at normal speed (1.0, the
// default).

lightAnim.setActiveInterval(2000, 5000);
lightAnim.setPosition(0, 2000);

[edit] Applying the animation during renderin

Now we have to update the attributes of the object with the new time, so we will use the method animate for this. This process should be into a loop.

appTime += 30;		// advance time by 30 ms each frame
light.animate(appTime);

// Assume 'myGraphics3D' is the Graphics3D object we draw into.
// In immediate mode, node transforms are ignored, so we get
// our animated transformation into a local Transform object,
// "lightToWorld". As its name implies, the transformation is
// from the Light node's local coordinates to world space.

light.getTransform(lightToWorld);
myGraphics3D.resetLights();
myGraphics3D.addLight(light, lightToWorld);

[edit] Related articles

[edit] External links

Main Collaborators