This tutorial introduces the Mobile 3D Graphics API for the Java 2 Platform, Micro Edition (J2ME), defined by the Java Community Process (JCP) in JSR 184. The API is an optional package to be used with profiles like the Mobile Information Device Profile (MIDP). After an overview of the API, the article discusses potential application areas, the differences between JSR 184 and two related APIs, the classes in the new optional package, the programming model, the reference implementation, and some programming examples.
JSR is the first Java-specific standard for three-dimensional graphics on mobile devices. The JSR's 26-member expert group includes all the major players in the mobile arena, including Sun Microsystems, Sony Ericsson, Symbian, Motorola, ARM, Cingular Wireless, and specification lead Nokia. The API takes the form of an optional package expected to be used with MIDP and version 1.1 of the Connected Limited Device Configuration (CLDC). It defines low- and high-level programming interfaces that bring efficient, interactive 3D graphics to devices with little memory and processing power, and with no hardware support for 3D graphics or floating-point operations. As new phones with diverse functionality appear, however, the API can scale up to higher-end devices that have color displays, 3D graphics hardware, and support for floating-point operations.
Application areas that will benefit from a 3D graphics API include games, map visualization, user interfaces, animated messages, and screen savers. Each of these areas requires simple content creation, some require high polygon throughput, and others require high-quality still images with special effects. To meet this wide spectrum of needs, the API supports both high-level and low-level graphics features, with a footprint of only 150 KB. In the high-level implementation (called retained mode), the developer works with scene graphs, and the world renders itself based on the positions of virtual cameras and lights. The low-level access ( immediate mode) allows applications to draw objects directly. You can use either mode, or both at the same time, depending on the task at hand.
The features of immediate mode are aligned with OpenGL ES standardization by Khronos. OpenGL ES (from "OpenGL for Embedded Systems") is a low-level, lightweight API for advanced embedded graphics using well-defined subset profiles of OpenGL. It provides a low-level interface between applications and hardware or software graphics engines. This standard makes it easy and inexpensive to offer a variety of advanced 3D graphics and games across all major mobile and embedded platforms. Because OpenGL ES is based on OpenGL, no new technologies are needed, which ensures synergy with, and a migration path to, the most widely adopted cross-platform graphics API, full OpenGL.
The JSR 184 expert group has agreed on a set of capabilities that the API must support:
float
data type of the Java programming language, not introduce a custom type.JSR 184 requires version 1.1 of CLDC for its floating-point capability. Because most mobile devices do not actually have floating-point hardware, the API designers struck a balance between the speed obtained through integer operations and the ease of programming provided by floating-point operations. Calculations that require the fastest processing accept 8- or 16-bit integer parameters, and other calculations are performed with floating-point math for easier application programming. Figure 1 shows how the relevant APIs relate:
Figure 1: The Mobile 3D Graphics API Software Stack
Newcomers to the Mobile 3D Graphics API for J2ME might find it all too easy to confuse it with two other interfaces. Note these distinctions:
The Mobile 3D Graphics API for J2ME is defined in the package javax.microedition.m3g
, which provides an easy-to-use API for rendering 3D graphics in retained mode and immediate mode.
In addition to the APIs, the package defines a scene graph structure and a corresponding file format for managing and deploying 3D content efficiently, along with all other necessary data: meshes, scene hierarchies, material properties, textures, animation keyframes, and so on. This data is written to a file using content-creation tools and loaded into the API through the Loader
class. The most important class is Graphics3D
, because all rendering is done there. The World
class serves as the root of the scene graph structure. Object3D
is the base class of all objects that can be rendered or loaded from a file, as well as the place where animations are applied. Table 1 briefly describes all the classes in the javax.microedition.m3g
package.
Table 1: Classes in the m3g
Package
Class | Description |
---|---|
AnimationController |
Controls the position, speed, and weight of an animation sequence. For example, it can be used to control the blinking and movement of a light in an animation application. |
AnimationTrack |
Associates a KeyframeSequence with an AnimationController and an animatable property, which is a scalar or vector variable that the animation system can update directly. |
Appearance |
A set of component objects that define the rendering attributes of a Mesh or a Spring3D . |
Background |
Defines how to clear the viewport. In retained mode, or when rendering a World , the Background object associated with the World is used. In immediate mode, a Background object is given a parameter to clear. If a Background object is not present, the default values specified in the constructor are used. |
Camera |
A scene graph node that defines the position of the viewer in the scene and the projection from 3D to 2D. The camera always faces towards the negative end of the Z axis (0, 0, -1). |
CompositingMode |
An Appearance component that encapsulates per-pixel compositing attributes. |
Fog |
An Appearance component that encapsulates attributes for fogging. |
Graphics3D |
A singleton 3D graphics context that can be bound to a rendering target. All rendering is done through the render() methods in this class. |
Group |
A scene graph node that stores an unordered set of nodes as its children. |
Image2D |
A two-dimensional image that can be used as a texture, background, or sprite image. There are two types: mutable images can be updated at any time; immutable images are fixed at construction time and cannot be changed later. |
IndexBuffer |
Defines how to connect vertices to form a geometric object. |
KeyframeSequence |
Encapsulates animation data as a sequence of time-stamped, vector-valued keyframes, each of which represents the value of an animated quantity at a specified instant. Can be associated with multiple animation targets. |
Light |
A scene graph node that represents different kinds of light sources, which are used to determine the color of each object, according to its Material attributes. |
Loader |
Downloads and deserializes graph nodes and node components, as well as entire scene graphs. Downloading ready-made pieces of 3D content from an M3G file is generally the most convenient way for an application to create and populate a 3D scene. |
Material |
An Appearance component encapsulating material attributes for lighting computations. Other attributes for lighting are defined in Light , PolygonMode , and VertexBuffer . |
Mesh |
A scene graph node that represents a 3D object defined as a polygonal surface. It represents a conventional rigid body mesh, and its subclasses MorphingMesh and SkinnedMesh extend it with capabilities to transform vertices independently of each other. |
MorphingMesh |
A scene graph node that represents a vertex-morphing polygon mesh. |
Node |
An abstract class for all scene graph nodes. There are five different kinds: Camera defines the projection from 3D to 2D as well as the position of the viewer in the scene. Mesh defines a 3D object consisting of triangles with associated material properties. Sprite3D defines a screen-aligned 2D image with a position in 3D space. Light defines the position, direction, colors, and other attributes of a light source. Group serves as a root for scene graph branches. |
Object3D |
An abstract base class for all objects that can be part of a 3D world. These include the world itself, other scene graph nodes, animations, textures, and so on. Everything in the API is an Object3D except Graphics3D , Loader , RayIntersection , and Transform . |
PolygonMode |
An Appearance component encapsulating polygon-level attributes, including settings related to back/front face culling, polygon winding, lighting computations, perspective correction, and shading. |
RayIntersection |
Stores a reference to an intersected Mesh or Sprite3D , and information about the intersection point. Strictly a run-time object, a RayIntersection is filled in by the pick() methods in the Group class, and cannot be loaded from a file by a Loader . |
SkinnedMesh |
A scene graph node that represents a skeletally animated polygon mesh. |
Sprite3D |
A scene graph node that represents a 2D image with a 3D position. This is a fast but functionally restricted alternative to textured geometry. It is rendered as a screen-aligned rectangular array of pixels with a constant depth. |
Texture2D |
An Appearance component encapsulating a 2D texture image and a set of attributes specifying how the image is to be applied on sub-meshes. The attributes include wrapping, filtering, blending, and texture coordinate transformation. |
Transform |
A generic 4x4 floating-point matrix representing a transformation. |
Transformable |
An abstract base class for Node and Texture2D , defining common methods for manipulating node and texture transformations. |
TriangleStripArray |
Defines an array of triangle strips. In a triangle strip, the first three vertex indices defines the first triangle. Each subsequent index together with the two previous indices defines a new triangle. For example, the strip S = (2, 0, 1, 4) defines two triangles: (2, 0, 1) and (0, 1, 4). |
VertexArray |
An array of integer vectors representing vertex positions, normals, colors, or texture coordinates. |
VertexBuffer |
Holds references to VertexArray s that contain the positions, colors, normals, and texture coordinates for a set of vertices. |
World |
A special Group node that is a top-level container for scene graphs. A scene graph is constructed from a hierarchy of nodes. In a complete scene graph, all nodes are ultimately connected to each other by a common root, which is a World node. |
An application that uses the Mobile 3D Graphics API for J2ME must be a valid MIDP application that follows the conventional MIDlet life-cycle. Use existing MIDP user interface classes such as Canvas
and CustomItem
to access the screen. To render the user interface, follow these steps:
Graphics3D
object.Graphics3D
object to a 2D buffer.World
, Node
, or sub-mesh.Complete functional examples appear later in this article, but this quick sample shows you the basics:
public class MyCanvas extends Canvas Graphics3D g3d;
World world; int currentTime = 0;
public MyCanvas() { g3d = Graphics3D.create();
Object root[] = Loader.load("world.m3g");
world = root[0];
} protected void paint(Graphics g) { g3d.bindTarget(g);
world.animate(currentTime);
currentTime += 50;
g3d.render(world);
g3d.releaseTarget();
} }
Version 2.2 of the J2ME Wireless Toolkit provides support for mobile 3D graphics by implementing JSR 184, including three demonstration MIDlets. To try these out, open the Demo3D
project and run it. You'll see three samples listed, as in Figure 2:
Life3D
implements the Game of Life in three dimensions.PogoRoo
shows a kangaroo bouncing up and down on a pogo stick.retainedmode
plays back a scene file that shows a skateboarder.
Figure 2: Sample Mobile 3D Graphics MIDlets in the J2ME Wireless Toolkit
Figure 3 is a snapshot of PogoRoo
in action.
Figure 3: The PogoRoo Example
The JSR 184 Reference Implementation (RI) was developed by Nokia. To get access to it and use it you must sign a limited license agreement with Nokia. The process is fairly straightforward and fast – it took me just a couple of days.
The RI, which is based on CLDC 1.1 and MIDP 2.0, comes in binary format. Note that you don't need to install CLDC or MIDP, as the RI includes them. It uses the open source Mesa 3D graphics library for rasterization. Mesa is a 3D graphics library similar to OpenGL. The RI has been developed and tested on Windows 2000 only, and doesn't come with the DLL files needed for Windows 2000. You'll need to download and build the Mesa DLL libraries yourself, especially because the RI works only with Mesa version 5.0.2, which was the release available at the time the RI was developed. Basically you need two DLL files: MesaGL.dll
and osmesa.dll
. You can either download the source code and build the libraries from scratch – you'll need Visual C++ – or build it following these steps:
MesaLib-5.0.2.zip
and MesaDemos-5.0.2.zip
.readme.win32
file in the MesaLib package to build the DLLs. The process will generate MesaGL.dll
and osmesa.dll
and write them to the lib
directory of your Mesa installation.MesaGL.dll
and osmesa.dll
, to the system folder c:\winnt\system32
.Once you have installed the RI and copied the DLL files to the right directory, change to the bin
directory of your reference implementation and type midp
. If all goes well, you'll see the emulator as in Figure 4.
Figure 4: Nokia's Mobile 3D Graphics API Emulator
The RI comes with a sample rendering MIDlet. To run it, use the following command:
midp -classpath ..\examples\jar\RenderTarget.jar RenderTargetMIDlet
Figure 5 is a snapshot of the running animation.
Figure 5: The RI's Sample Rendering MIDlet
1. Retained Mode
In the first full-length example, a MIDlet uses the high-level API (retained mode) to play back a ready-made animation that it downloads over an HTTP connection. Note that the application plays the pogoroo.m3g
file that comes with the J2ME Wireless Toolkit 2.2, and can be found at \WTK-HOME\Demo3D\res\com\superscape\m3g\wtksamples\pogoroo\content
. Once you've located it, you'll need to modify the parameter to the call Loader.load("pogoroo.m3g")
, in the following code.
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.CommandListener;
import java.util.Timer; import java.util.TimerTask;
import javax.microedition.m3g.*;
public class JesterTestlet extends MIDlet implements CommandListener {
private Display myDisplay = null;
private JesterCanvas myCanvas = null;
private Timer myRefreshTimer = new Timer();
private TimerTask myRefreshTask = null;
private Command exitCommand = new Command("Exit", Command.ITEM, 1);
private World myWorld = null;
/** * JesterTestlet - default constructor.
*/ public JesterTestlet() {
// Set up the user interface. myDisplay = Display.getDisplay(this);
myCanvas = new JesterCanvas(this);
myCanvas.setCommandListener(this);
myCanvas.addCommand(exitCommand);
} /** * startApp()
*/ public void startApp() throws MIDletStateChangeException { myDisplay.setCurrent(myCanvas);
try { // Load a file. Object3D[] roots = Loader.load("pogoroo.m3g");
// Assume the world is the first root node loaded. myWorld = (World)roots[0];
// Force a repaint so that we get the update loop started. myCanvas.repaint();
} catch(Exception e) { e.printStackTrace();
} } /** * pauseApp()
*/ public void pauseApp() {
// Release resources. myWorld = null;
}
/** * destroyApp()
*/ public void destroyApp(boolean unconditional) throws MIDletStateChangeException {
myRefreshTimer.cancel();
myRefreshTimer = null; // Release resources. myWorld = null;
} /** * MIDlet paint method.
*/ public void paint(Graphics g) { // We are not fully initialised yet;
just return.
if(myCanvas == null || myWorld == null) return;
// Delete any pending refresh tasks. if(myRefreshTask != null) { myRefreshTask.cancel();
myRefreshTask = null;
} // Get the current time. long currentTime = System.currentTimeMillis();
// Update the world to the current time. int validity = myWorld.animate((int)currentTime);
// Render to our Graphics. Graphics3D myGraphics3D = Graphics3D.getInstance();
myGraphics3D.bindTarget(g);
myGraphics3D.render(myWorld);
myGraphics3D.releaseTarget();
// Subtract time taken to do the update.
validity -= System.currentTimeMillis() - currentTime;
if(validity < 1) {
// The validity is too small;
allow a minimum of 1ms. validity = 1;
} // If the validity is not infinite schedule a refresh task. if(validity
< 0x7fffffff) { // Create a new refresh task. myRefreshTask = new RefreshTask();
// Schedule an update. myRefreshTimer.schedule(myRefreshTask, validity);
} } /** * Handle commands.
*/ public void commandAction(Command cmd, Displayable disp)
{ if (cmd == exitCommand) { try { destroyApp(false);
notifyDestroyed();
} catch(Exception e) { e.printStackTrace();
} } }
/** * Inner class for refreshing the view.
*/ private class RefreshTask extends TimerTask { public void run() {
// Get the canvas to repaint itself. myCanvas.repaint();
} } /** * Inner class for handling the canvas.
*/ class JesterCanvas extends Canvas { JesterTestlet myTestlet;
/** * Construct a new canvas.
*/ JesterCanvas(JesterTestlet Testlet) { myTestlet = Testlet; }
/** * Initialize self. */ void init() { }
/** * Clean up and destroy. */ void destroy() { }
/** * Ask myTestlet to paint itself.
*/ protected void paint(Graphics g) { myTestlet.paint(g); \
} } }
To experiment with this MIDlet, create two subdirectories under your JSR 184 RI installation directory. You can call them whatever you like; for this illustration I name them mysrc
and myclasses
. All .java
and .class
files will be written to mysrc
, and all verified .class
files will be written to myclasses
. Now do the following:
mysrc
.javac -classpath ..\classes.zip JesterTeslet.java
.class
files using the preverify
command that comes with the RI. From the bin
directory:
preverify -classpath ..\classes.zip -d ..\myclasses ..\mysrc
.
This command verifies all classes in the mysrc
directory and writes the verified classes to myclasses
.
bin
: midp -classpath ..\myclasses JesterTestlet
You can also test JesterTestlet
in the J2ME Wireless Toolkit 2.2; it should work just fine.
2. Immediate Mode
The second full example uses the low-level interface (immediate mode) to display a rotating, texture-mapped cube. The MIDlet initializes a 3D graphics context and binds it to a MIDP canvas. It then illustrates how to create a Mesh
object manually, and set up the coordinates, triangle connectivity, texture maps, and materials. In normal practice, however, you'll create Mesh
objects with a 3D modeling tool rather than programmatically, then retrieve them using the load()
method.
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
public class MyCanvas extends Canvas { private Graphics3D iG3D;
private Camera iCamera;
private Light iLight;
private float iAngle = 0.0f;
private Transform iTransform = new Transform();
private Background iBackground = new Background();
private VertexBuffer iVb;
// positions, normals, colors, texcoords private IndexBuffer iIb;
// indices to iVB, forming triangle strips private Appearance iAppearance;
// material, texture, compositing, ... private Material iMaterial = new Material();
private Image iImage;
/** * Construct the Displayable.
*/ public MyCanvas() {
// set up this Displayable to listen to command events setCommandListener(new CommandListener()
{ public void commandAction(Command c, Displayable d) {
if (c.getCommandType() == Command.EXIT) {
// exit the MIDlet MIDletMain.quitApp();
} } });
try { init();
} catch(Exception e) { e.printStackTrace(); } }
/** * Component initialization.
*/ private void init() throws Exception {
// add the Exit command addCommand(new Command("Exit", Command.EXIT, 1));
// get the singleton Graphics3D instance iG3D = Graphics3D.getInstance();
// create a camera iCamera = new Camera();
iCamera.setPerspective( 60.0f,
// field of view (float)getWidth()/ (float)getHeight(),
// aspectRatio 1.0f,
// near clipping plane 1000.0f );
// far clipping plane
// create a light iLight = new Light(); iLight.setColor(0xffffff);
// white light iLight.setIntensity(1.25f);
// overbright
// init some arrays for our object (cube)
// Each line in this array declaration represents a triangle strip
// for one side of a cube. The only primitive we can draw with is the
// triangle strip so if we want to make a cube with hard edges we
// need to construct one triangle strip per face of the cube.
// 1 * * * * * 0
// * * *
// * * *
// * * *
// 3 * * * * * 2
// The ASCII diagram above represents the vertices in the first line
// (the first tri-strip) short[] vert = { 10, 10, 10, -10, 10, 10, 10,-10, 10, -10,-10, 10,
// front-10, 10,-10, 10, 10,-10, -10,-10,-10, 10,-10,-10,
// back-10, 10, 10, -10, 10,-10, -10,-10, 10, -10,-10,-10,
// left 10, 10,-10, 10, 10, 10, 10,-10,-10, 10,-10, 10,
// right 10, 10,-10, -10, 10,-10, 10, 10, 10, -10, 10, 10,
// top 10,-10, 10, -10,-10, 10, 10,-10,-10, -10,-10,-10 }; // bottom
// create a VertexArray to hold the vertices for the object VertexArray vertArray =
new VertexArray(vert.length / 3, 3, 2); vertArray.set(0, vert.length/3, vert);
// The per-vertex normals for the cube; these match with the vertices
// above. Each normal is perpendicular to the surface of the object at
// the corresponding vertex. byte[] norm = { 0, 0, 127, 0, 0, 127, 0, 0, 127,
0, 0, 127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127,-127, 0, 0,
-127, 0, 0, -127, 0, 0, -127, 0, 0, 127, 0, 0, 127, 0, 0,
127, 0, 0, 127, 0, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0,
0, 127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0 };
// create a vertex array for the normals of the object VertexArray normArray = new VertexArray(norm.length / 3, 3, 1);
normArray.set(0, norm.length/3, norm);
// per vertex texture coordinates short[] tex = { 1, 0, 0, 0, 1, 1, 0, 1, 1, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0,
0, 0, 1, 1, 0, 1 };
// create a vertex array for the texture coordinates of the object VertexArray texArray = new VertexArray(tex.length / 2, 2, 2);
texArray.set(0, tex.length/2, tex);
// the length of each triangle strip int[] stripLen = { 4, 4, 4, 4, 4, 4 };
// create the VertexBuffer for our object VertexBuffer vb = iVb = new VertexBuffer();
vb.setPositions(vertArray, 1.0f, null);
// unit scale, zero bias vb.setNormals(normArray);
vb.setTexCoords(0, texArray, 1.0f, null);
// unit scale, zero bias
// create the index buffer for our object (this tells how to
// create triangle strips from the contents of the vertex buffer). iIb = new TriangleStripArray( 0, stripLen );
// load the image for the texture iImage = Image.createImage( "/texture.png" );
// create the Image2D (we need this so we can make a Texture2D) Image2D image2D = new Image2D( Image2D.RGB, iImage );
// create the Texture2D
// texture color is to be modulated with the lit material color Texture2D texture = new Texture2D( image2D );
texture.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST);
texture.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP);
texture.setBlending(Texture2D.FUNC_MODULATE);
// create the appearance iAppearance = new Appearance();
iAppearance.setTexture(0, texture);
iAppearance.setMaterial(iMaterial);
iMaterial.setColor(Material.DIFFUSE, 0xFFFFFFFF);
// white iMaterial.setColor(Material.SPECULAR, 0xFFFFFFFF);
// white iMaterial.setShininess(100.0f);
iBackground.setColor(0xffffcc);
// set the background color }
/** * Paint the scene. */ protected void paint(Graphics g) {
// Bind the Graphics of this Canvas to our Graphics3D. The
// viewport is automatically set to cover the entire clipping
// rectangle of the Graphics object. The parameters indicate
// that z-buffering, dithering, and true color rendering are
// enabled, but antialiasing is disabled. iG3D.bindTarget(g, true, Graphics3D.DITHER | Graphics3D.TRUE_COLOR);
// clear the color and depth buffers iG3D.clear(iBackground);
// set up the camera in the desired position Transform transform = new Transform();
transform.postTranslate(0.0f, 0.0f, 30.0f);
iG3D.setCamera(iCamera, transform);
// set up a "headlight": a directional light shining
// from the direction of the camera iG3D.resetLights();
iG3D.addLight(iLight, transform);
// update our transform (this will give us a rotating cube) iAngle += 1.0f; iTransform.setIdentity();
iTransform.postRotate(iAngle, // rotate 1 degree per frame1.0f, 1.0f, 1.0f);
// rotate around this axis
// Render our cube. We provide the vertex and index buffers
// to specify the geometry; the appearance so we know what
// material and texture to use; and the transform to tell
// where to render the object iG3D.render(iVb, iIb, iAppearance, iTransform);
// flush iG3D.releaseTarget(); } }
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*; public class MIDletMain extends MIDlet { static MIDletMain instance;
MyCanvas displayable = new MyCanvas();
Timer iTimer = new Timer();
/** * Construct the midlet.
*/ public MIDletMain() { this.instance = this; }
/** * Main method.
*/ public void startApp() { Display.getDisplay(this).setCurrent(displayable);
iTimer.schedule( new MyTimerTask(), 0, 40 );
} /** * Handle pausing the MIDlet.
*/ public void pauseApp() { }
/** * Handle destroying the MIDlet.
*/ public void destroyApp(boolean unconditional) { }
/** * Quit the MIDlet.
*/ public static void quitApp() { instance.destroyApp(true);
instance.notifyDestroyed();
instance = null; } /** * Timer task for providing animation.
*/class MyTimerTask extends TimerTask {
public void run() { if( displayable != null ) { displayable.repaint();
} } } }
To play with this example, you can follow the same steps as with the retained-mode example – or you can run it using the J2ME Wireless Toolkit 2.2:
3d
– but make sure the name of the MIDlet is MIDletMain
. After you click on the Create Project button, in the Settings
window be sure you include the Mobile 3D Graphics API.MyCanvas
and MIDletMain
, from this article and save them as .java
files in \WTK-HOME\apps\3d\src
.texture.png
and save it in \WTK-HOME\apps\3d\res
.
< span class="dkcaption1">Figure 6: Rendering in Immediate Mode
The emerging Mobile 3D Graphics API for J2ME provides an efficient interface for rendering 3D graphics on CLDC/MIDP devices. It supports devices with little memory and processing power and no hardware support for 3D graphics or floating-point operations, but the API can scale up to higher-end devices that have a color display, floating-point unit, and 3D graphics hardware, as they come along. The applications of this API are many, including games, screen savers, map visualization, and animated messages.
The code samples in this article demonstrate how easy it is to integrate 3D graphics into your MIDlets – but remember that this API is an optional package, and may not be supported on all devices.
Special thanks to Nicholas Lorain and Vincent Hardy of Sun Microsystems, whose feedback helped me improve this article.
Qusay H. Mahmoud provides Java consulting and training services. He has published dozens of articles on Java, and is the author of Distributed Programming with Java (Manning Publications, 1999) and Learning Wireless Java (O'Reilly, 2002).