Dev 143 – JSR184 – Using 3D in J2ME – part 1 of 3

The doors are opening to a new world: programming of games for mobile phones. In this journey we will learn the essential techniques to obtain the most from these new potentials.

Playing with mobile phones has become a very common pastime. The number of games available grows exponentially month after month simultaneously with the growth of the devices that support Java and the continuous evolution of their hardware features.

Until now the performance of the hardware of these devices has allowed the creation of games that followed the style of several years ago. These were exclusively programs with two-dimensional graphics and settings composed of objects and sprites drawn pixel by pixel.

The next step will obviously be to conquer the 3D universe. Although most mobile phones on the market today have the dedicated hardware necessary to accelerate 3D graphics operations, it is obvious that these devices are positioned in a performance range well below the now very famous portable consoles such as the Playstation Portable or the Nintendo DS .

Despite all there are a lot of reasons to believe that in the immediate future we will see a notable spread of 3D games for mobile phones, considering also the speed with which the power of the phones on the market are improved.

For this reason the Mobile 3D Graphics API, called for the sake of M3G and defined with the name of JSR-184, was created. It is an effort to establish standard 3D API for devices that support J2ME .

Anyone had any experience with programming API for J2ME will certainly have learned to live with their limitations. These are due to the need to create suitable standards to embrace the widest possible range of devices that have very limited hardware features.

Unfortunately the JSR-184 must also meet this requirement. As a result having all the features available in other contexts such as OpenGL or DirectX represents, at least for the moment, a dream that is impossible to realize.

Despite this, the programmer can count on the availability of a complete set of useful classes for vertex transformations, light management, texture mapping, 3D sprites, transparency management, materials, fog effect and cameras.

As we will see, however, if on one hand this standard only encompasses what is essential in order to obtain an acceptable 3D, on the other hand it is very simple and sometimes immediate to exploit functionality. The Image 1 shows a frame taken from a 3D demo application downloaded from the site www.forum.nokia.com.

Method of use 

M3G can be sharply divided into two parts: immediate modes and retained mode. In immediate mode each 3D object that composes a scene is processed and drawn individually. This modality can be considered as a low level access to the 3D functions of the API, in the same way as OpenGL does.

The programmer, therefore, has the responsibility to manually set the vertices of the objects, make the transformations, activate the lights, load the textures and so on.

In retained mode, on the other hand, it is possible to use a type of file in m3g format in which all the data can be stored to describe an entire world made up of 3D objects, cameras, lights and animations.

To display an animated scene in an application, using m3g filesfew of lines of code are sufficient, so it is very convenient and quick to use.

This aspect obviously enhances its ease of use. However due to the fact our intention is to explore the functionalities that are at the base of the creation of the 3D scenes, in this article, we will deal exclusively with the immediate mode, referring to another article (editing permitting) the treatment of this interesting topic.

The basics

In both cases in order to access the M3G functionalities it is necessary to import the following package:

javax.microedition.m3g.*

Panel 1 lists all the classes within that package . You will immediately notice their particularly small number.

AnimationTrack
Appearance
Background
Camera
CompositingMode
Fog
Graphics3D
Group
Image2D
IndexBuffer
KeyframeSequence
Light
Loader
Material
Mesh
MorphingMesh
Node
Object3D
PolygonMode
RayIntersection
SkinnedMesh
Sprite3D
Texture2D
Transform
Transformable
TriangleStripArray
VertexArray
VertexBuffer
World

Panel 1 – The M3G API class list

In addition is necessary to import the usual  

javax.microedition.lcdui.*

so our applications must extend the Canvas class or even GameCanvas. In order to view the rendered scenes it is necessary that M3G can work directly with the Graphics object. The latter, as many already know, has direct access to the low-level functions that manage the graphic context of the device.

So whenever it is necessary to show the result of a rendering – commonly in the case of the execution of the paint method – we will have to indicate to the API M3G the instance of the Graphics object to use.

Therefore, we prepare the skeleton of a generic class in order to have a clearer view of how the code will work

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

public class Application3D extends Canvas {

    public Application3D() {
        init();
    }

}

protected void init() {
    // The initialization code will be entered here
}

protected void paint (Graphics g) {
    // The display code is entered here
}

When everything is ready the first thing to do is create an object of type Graphics3D. We will write the following statement just after the one in the GameCanvas class

Graphics3D g3d;

To use the object we need to retrieve the instance of the singleton associated with our application via

d3d = Graphics3D.getInstance();

The Graphics3D class, as we have said, is a singleton that can be linked to the graphic context of the device. All rendering operations are performed with the call to the render() method of this class.

To understand how to use it we can observe the piece of code inserted in the paint() method of our class.

protected void paint(Graphics g) {
    g3d.bindTarget(g);
    g3d.clear(null);

    // Here is inserted the code that draws the scene

    g3d.releaseTarget();
}

The operation mechanism is extremely simple. In practical terms the first line of code

g3d.bindTarget(g);

does nothing more than bind the Graphics3D object with the reference to the Graphics class passed as an argument by the system.

The more experienced and attentive will surely have noticed that it is possible to hook the Graphics3D object to any image of mutable type to obtain rendering in memory instead of directly in the display.

There is also another definition of this method which allows you to enable/disable the depth buffer and specify a hint , which is a hint about the quality of the rendered output.

In fact in certain situations the quality of image rendering speed may be more important. This is why M3G has three options: anti-aliasingditchering and true color rendering .

Is necessary to keep in mind that these options are nothing more than simple “suggestions” and some implementations can gracefully ignore them.

After the graphic context was hooked, the following statement

g3d.clear(null);

does nothing but clean the contents of the graphic context just hooked.

At this point it is possible to carry out all the operations necessary to create the scene to be rendered; you can draw and transform solids, add lights, create and move cameras and so on.

The method closes the design process

g3d.releaseTarget();

which releases the graphic context. 

Before the bindTarget call and after the releaseTarget you can normally use the Graphics object as in a classic 2D application.

In this way without the need for further operations we can easily combine 2D and 3D graphics, add text or even modify the image produced. 

Using 2D graphics before the bindTarget may not help much since all drawing operations are canceled by the call clear(null). We could think of eliminating this call, manually deleting the screen and inserting a background image, but this operation can be performed from M3G directly.

For this reason we add the two functions draw3D() and draw2D() to the class and we insert these calls in the paint() method in this way:

protected void paint(Graphics g) {
    g3d.bindTarget(g);
    g3d.clear(null);
    draw3D();
    g3d.releaseTarget();
    draw2D();
}

The skeleton of the application

The Listing1 and


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

public class templateMIDlet extends MIDlet 
{
    templateGameCanvas template2DCanvas;
	
    private boolean init = false;
    private boolean pause = false;

    public templateMIDlet(){	     
        Instances.midlet = this;
    } 
	
    public void startApp()  
    {	   
        if (init == false) init();
        
        Display.getDisplay(this)
               .setCurrent(template2DCanvas);
                
	try
        {                        
            if (pausa == false) 
            {                        
                template2DCanvas.start();
            } else {
                template2DCanvas.restart();
	        pause = false;
            }       
        } catch(Exception e) {                                           
            gameFramework.utils.Error.Show(e);
        }        
    }

    public void pauseApp()
    {
	template2DCanvas.pause();
	pause = true;
    }	      
        
    public void destroyApp(boolean unconditional)
    {
        template2DCanvas = null;
        System.gc();
        notifyDestroyed();
    }
			
    private synchronized void init()
    {
        if (!init)
	{
            try {
                template2DCanvas = new templateGameCanvas(this);
	        init = true;
            } catch (Exception e) {
                init = false;
            }
        }
    }
}

Listing2 are, respectively, the code contained in files templateMIDlet and templateCanvas you can download from the FTP site.

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.media.*;
import java.io.*;
import java.util.*;

public class HelloWorldCanvas extends GameCanvas implements Runnable
{
    Graphics g;        
    HelloWorld           midlet_instance;

    private final int    MILLIS_PER_FRAME = 40;      
    private int     	 DISPLAY_WIDTH;
    private int          DISPLAY_HEIGHT;          
    private boolean      DISPLAY_COLOR;
    private int          COLOR_DEPTH;
    private int          ALPHA_DEPTH;    

    private volatile     Thread aniThread = null;

    private boolean             graphicsSet = false;             
    private boolean             keyPressed = false;
    private boolean             keyReleased = false;
    private int                 prevoiusKeyPressed = 0;  
    private int                 keyStates;
                        
    HelloWorldCanvas (HelloWorld HelloWorldMIDlet) {
        super(true);
                
        // Store the reference to the MIDlet class
        midlet_instance = MIDletInterface;
                		
        // Enables the full screen mode
        setFullScreenMode(true);
                
        // Gets the display size values
        DISPLAY_WIDTH = getWidth();
        DISPLAY_HEIGHT = getHeight();

        Display d = Display.getDisplay(istanza_midlet);
                
        // Determines if the display can show colors
        // or is monochromatic
        DISPLAY_COLOR = d.isColor();

        // Determines the number of colors the
        // display can show
        COLOR_DEPTH = d.numColors();

        // Determines the number of transparency 
        // levels that the app can use
        ALPHA_DEPTH = d.numAlphaLevels();

        g = getGraphics();
    }
        
    // Method called by the runtime when the 
    // application is started	        
    public void start() {
        // New thread creation
        aniThread = new Thread(this);
        aniThread.start();

        // Run the main loop
        run();
    }

    // Method called by the runtime when the 
    // application is restarted after an interruption
    public void restart()
    {
        aniThread.notify();
    }

    // Method called by the runtime when the 
    // application is suspended 
    public void pause()
    {
        try {
            aniThread.wait();
        } catch (Exception e) {
                    
        }
    }	

    // Method called by the runtime when the 
    // application is about to be killed
    public void stop()
    {
        aniThread = null;
    }
	
    public void run()
    {
        Thread cThread = Thread.currentThread();
		
	try {
	    while (cThread == aniThread) {
	        long startTime = System.currentTimeMillis();
				
	        if (isShown()) {	
                    keyboard();
		    frame();
		    draw();	
		    flushGraphics();
		}
				
		long endTime = System.currentTimeMillis() - startTime;
				
	        if (endTime < MILLIS_PER_FRAME) {
		    synchronized(this) {
	  	        wait(MILLIS_PER_FRAME - endTime);
		    }
		} else {
		    cThread.yield();
		}
	    }
	} catch (InterruptedException ex) {
	    // Nothing to do 
	}
    }
	
    void frame()
    {

    }
        
    void keyboard()
    {       
        int keyStates = getKeyStates();

        if (!keyPressed)
        {
            keyPressed = true;
        }

        if ((keyStates & LEFT_PRESSED) != 0) {

        } else if ((keyStates & RIGHT_PRESSED) != 0) {

        } else if ((keyStates & FIRE_PRESSED) != 0) {

        } else if ((keyStates & UP_PRESSED) != 0) {

        } else if ((keyStates & DOWN_PRESSED) != 0) {

        } else if (keyStates == 0) {
            keyPressed = false;
        } else {

        }
    }
        
    private void draw()
    {            
        if (graphicsSet == false)
        {
            graphicsSet = true;
            g2d = g;
                        
            // Recupero delle dimensioni del display
            DISPLAY_WIDTH = getWidth();
            DISPLAY_HEIGHT = getHeight();                
        }

        g3d.bindTarget(g2d);
        g3d.clear(null);

	Draw3D();

	g3d.releaseTarget(); 	 
                
        Draw2D();                
    }
        
    private void Draw3D() {            
    }
        
    private void Draw2D() {            
    }
}

Even if this application displays practically nothing it represents the starting point, the code is ready to be filled.

If you use NetBeans for programming you will find useful the projects contained in the same source directory.

Conclusions

So far everything is quite simple and immediate. We have seen how to initialize the API for the realization of 3D. It does not require particularly long, tedious or cryptic procedures disseminated with parameters that are difficult to remember. 

From the next article, however, we will begin to see topics that are easily understood by those who already have a certain mastery of the concepts of 3D programming, such as matrix operations, vertices, and transformations.

Bibliography

  1. Sun Corporation, “Mobile 3D Graphics API Specification “, www.sun.com

Original article

Downloads

Leave a Reply

Your email address will not be published. Required fields are marked *