In the first part of this series, I explained how you can create 3D scenes by using the Mobile 3D Graphics API (M3G, defined in JSR 184) immediate mode, which lets you render 3D objects immediately to the screen, hence the name. You can imagine immediate mode as the low-level access to 3D functions.
For more complex tasks, it's helpful to have an in-memory representation of your 3D world so you can manage the data in a structured way. For M3G, this is referred to as retained mode. In retained mode, you define and display an entire world of 3D objects, including information on their appearance. Imagine retained mode as a more abstract, but also more comfortable, way of displaying 3D graphics.
Retained mode is especially handy when you create your 3D scene in a modeling tool and import the data into your application. In this article, I'll show you how.
Immediate versus retained mode
To show the differences between immediate and retained mode, I'd like to translate an example of immediate mode from the first part of this series to retained mode. Do you remember the white cube? In a class that inherits from Canvas, I created a cube with eight vertices and placed its center at the coordinate system's origin. I also defined a triangle strip that told M3G how to connect the vertices to build the cube's geometry. A camera with perspective projection took a snapshot of the cube, which was finally rendered in paint(). In this method, I called Graphics3D.render() with the vertex data and the triangle strip as parameters.
The changes for retained mode occur in init() and paint(). Listing 1 shows their implementations.
Listing 1. Simple cube in retained mode
/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering and create a World.
_graphics3d = Graphics3D.getInstance();
_world = new World();
// Create vertex data.
VertexBuffer 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.
TriangleStripArray cubeTriangles = new TriangleStripArray(
TRIANGLE_INDICES, new int[] {TRIANGLE_INDICES.length});
// Create a Mesh that represents the cube.
Mesh cubeMesh = new Mesh(cubeVertexData, cubeTriangles, new Appearance());
_world.addChild(cubeMesh);
// 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);
camera.setTranslation(0.0f, 0.0f, 10.0f);
_world.addChild(camera);
_world.setActiveCamera(camera);
}
/**
* Renders the sample on the screen.
*
* @param graphics the graphics object to draw on.
*/
protected void paint(Graphics graphics)
{
_graphics3d.bindTarget(graphics);
_graphics3d.render(_world);
_graphics3d.releaseTarget();
}
|
First, I obtain the 3D graphics context and create a new World object that represents my 3D scene. The initialization of VertexBuffer and TriangleStrip is the same as for immediate mode, but instead of using the two objects for rendering, I assign them to an instance of Mesh. The world gets two children: The mesh that I just created and a camera that is initialized with a perspective projection. _world.setActiveCamera() tells M3G to use the camera I just added to the world for rendering. The paint() method becomes very simple -- it just renders the world onto the graphics context. Find the complete class listing in VerticesRetainedSample.java.
M3G operates in retained mode when you use Graphics3D.render() with a World object as a parameter. As you've seen in Listing 1, retained mode is as easy to handle as immediate mode, but each mode uses slightly different API calls. Table 1 lists the differences.
Table 1. API calls to use in immediate and retained mode
| Immediate mode | Retained mode | |
|---|---|---|
| Camera | Set the current camera in Graphics3D before render call.
| Add one or more cameras to the world as children and activate one of them.
|
| Light | Set lights in Graphics3D object before render call.
| Add lights to the world as children.
|
| Transformation | Pass Transform object in API call, for example:
| Use methods inherited from Transformable class, for example:
|
| Background | Set background with Graphics3D.clear(). Always call clear() before rendering.
| Set background in World object. If not set, the background is cleared to black automatically.
|
| Rendering | Render scene graph nodes, (Sprite3D, Mesh, Group, and their subclasses), and submeshes (VertexBuffer).
| Render complete world including its children.
|
You can combine retained mode and immediate mode by mixing the respective render commands. After using Graphics3D.render(World), the active camera and lights in the rendered world automatically replace Graphics3D's current camera and lights. This way, subsequent immediate mode rendering can easily use the same environment. You can also render a world in immediate mode with Graphics3D.render(Node, Transform) because World inherits from Node. In this case, the world's lights, cameras, and the background are ignored, but all other child nodes are rendered.
M3G's specification defines retained mode based on the type of render() call that you use for drawing. The API operates in retained mode when you use render(World). Because you can render a World object and its children as Node in immediate mode as well, there isn't much difference between either mode. However, what is commonly referred to as retained mode is the ability to handle a group of 3D objects in a data structure called a scene graph, independent from the render code.
The advantages of a scene graph become apparent when you define a more complex world. In M3G, the class World organizes 3D objects in a tree structure whose nodes are classes derived from Node. You've already seen two subclasses of Node in the previous example: Camera and Mesh. Other commonly used objects include Light, which illuminates a 3D scene, and Group, which acts as a container for other nodes. Grouped nodes can be treated as if they were a single object. World itself is a Group with additional behavior to act as the top-level container for a scene graph.
Figure 1 shows the tree of a world that includes a light, a camera, and several meshes organized in groups. I also included specific settings in the Group and Mesh nodes that I'll explain in a moment.
Figure 1. Sample world scene graph tree

In a scene graph for M3G, one node must belong to exactly one group. The graph must also be acyclic. Otherwise, you are free to model your graph in a way that suits your data structure. For my sample, I decided to have two groups, four meshes in each. One group contains blue meshes, the other one red meshes. Another group is the container for both the blue and red groups.
The user IDs that I assigned to the group nodes will later help me to identify them. Any node can have a user ID. For my purposes, it's enough to be able to find the group nodes. I added the light and the camera nodes to the root object, but the location doesn't matter. You can add many cameras to define different view points and switch among them with World.setActiveCamera(). You can also have several lights at once. (The maximum number of lights supported by an M3G implementation can be obtained with Graphics3D.getProperties().)
Listing 2 shows the initialization of the world described in Figure 1.
Listing 2. Scene graph tree, part 1: Initialization
/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering and a World.
_graphics3d = Graphics3D.getInstance();
_world = new World();
// 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);
camera.setTranslation(0.0f, 0.0f, 10.0f);
_world.addChild(camera);
_world.setActiveCamera(camera);
// Create lights.
Light light = new Light();
light.setMode(Light.OMNI);
light.setTranslation(0.0f, 0.0f, 3.0f);
_world.addChild(light);
// Create two sets of vertex data: one for blue meshes and one for red
// meshes.
VertexBuffer blueCubeVertexData = new VertexBuffer();
blueCubeVertexData.setDefaultColor(0x000000FF); // blue
VertexBuffer redCubeVertexData = new VertexBuffer();
redCubeVertexData.setDefaultColor(0x00FF0000); // red
VertexArray vertexPositions =
new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
blueCubeVertexData.setPositions(vertexPositions, 1.0f, null);
redCubeVertexData.setPositions(vertexPositions, 1.0f, null);
VertexArray vertexNormals =
new VertexArray(VERTEX_NORMALS.length/3, 3, 1);
vertexNormals.set(0, VERTEX_NORMALS.length/3, VERTEX_NORMALS);
blueCubeVertexData.setNormals(vertexNormals);
redCubeVertexData.setNormals(vertexNormals);
// Create the triangles that define the cube; the indices point to
// vertices in VERTEX_POSITIONS.
TriangleStripArray cubeTriangles = new TriangleStripArray(
TRIANGLE_INDICES, TRIANGLE_LENGTHS);
// Create material.
Material material = new Material();
material.setVertexColorTrackingEnable(true);
Appearance appearance = new Appearance();
appearance.setMaterial(material);
// Create groups to organize the cubes.
Group allMeshes = new Group();
allMeshes.setUserID(USER_ID_ALL_MESHES);
_world.addChild(allMeshes);
Group blueMeshes = new Group();
blueMeshes.setUserID(USER_ID_BLUE_MESHES);
allMeshes.addChild(blueMeshes);
Group redMeshes = new Group();
redMeshes.setUserID(USER_ID_RED_MESHES);
allMeshes.addChild(redMeshes);
// Create eight cubes in a circle.
for (int i=0; i<8; i++)
{
Mesh cubeMesh = null;
if ((i%2) == 0)
{
cubeMesh = new Mesh(blueCubeVertexData, cubeTriangles, appearance);
blueMeshes.addChild(cubeMesh);
}
else
{
cubeMesh = new Mesh(redCubeVertexData, cubeTriangles, appearance);
redMeshes.addChild(cubeMesh);
}
cubeMesh.setTranslation(1.5f * (float) Math.cos(i*Math.PI/4), 1.5f * (float)
Math.sin(i*Math.PI/4), 0.0f);
cubeMesh.setScale(0.4f, 0.4f, 0.4f);
}
}
|
The beginning is the usual setup of camera and lights, which I add to the world. For the meshes, I create two VertexBuffers, one with a default color of blue and the other with a default color of red. Although they are separate objects, they share vertex positions and normals, which saves memory. I also create a material that is necessary for lighting and enable tracking of the vertex color so the default color is used in the lighting calculation. Next, I create the group nodes and eight meshes, placed in a circle. Every even-numbered mesh is blue and added to the blue group, every odd one is red and added to the red group. Each group is assigned a user ID; Listing 3 shows how to use them.
Listing 3. Scene graph tree, part 2: Animation
/**
* When the sample is shown, it's initialized and the animation
* thread started.
*/
public void showNotify()
{
init();
Thread thread = new Thread(this);
_isRunning = true;
thread.start();
}
/**
* Drives the animation.
*/
public void run()
{
while(_isRunning)
{
// Rotate all meshes.
Group allMeshes = (Group) _world.find(USER_ID_ALL_MESHES);
allMeshes.postRotate(2, 0.0f, 0.0f, 1.0f);
_counter++;
if ((_counter%COUNTER_MAX) == 0)
{
_scale *= -1;
}
// Scale blue meshes.
Group blueMeshes = (Group) _world.find(USER_ID_BLUE_MESHES);
blueMeshes.scale(1 + _scale, 1 + _scale, 1 + _scale);
// Scale red meshes.
Group redMeshes = (Group) _world.find(USER_ID_RED_MESHES);
redMeshes.scale(1 - _scale, 1 - _scale, 1 - _scale);
repaint();
try
{
Thread.sleep(50);
}
catch (Exception e){}
}
}
|
This sample implements Thread in order to create a simple animation by transforming group nodes. Transformations work slightly differently in retained mode than in immediate mode. In immediate mode, Transform represents a single matrix that manipulates a 3D object. In retained mode, methods of the Node object inherited from Transformable are used instead.
Transformable has four transformation components: translation (T), orientation (R), scale (S), and a generic 4x4 matrix (M). All four components can be set at the same time and together build the final transformation. A vector p, such as a vertex coordinate, is transformed into p' by using the equation p'=TRSMp. The easiest way to translate a transformation from immediate mode to retained mode is to use Transformable.setTransform() and thus change the generic matrix. Otherwise, you can use the remaining three components, but note that the order of the matrix multiplication is fixed and can't be changed. Transformations also apply to Group nodes.
Grouping is an easy way to treat several nodes as one single object. In Listing 3, all meshes are given a rotation around the z axis, but blue meshes are scaled differently than red meshes. The transformation of a mesh is determined by its own transformation and that of all of its parents. Two screenshots of the animation can be seen in Figure 2; the complete source code is in SceneGraphSample.java.
Figure 2. Scene graph animation

Creating 3D objects manually is tedious, which is why I've used simple cubes up until now. In this section, I'll show you how to use a 3D modeling tool to create a 3D world and then import it into an M3G application.
Exchanging data between applications requires a common file format. Instead of inventing your own, you can use M3G's built-in binary format, which your application can read with Loader.load(). Many tools can already write M3G files or you can add this function with an exporter from a third-party tool vendor. From the many 3D tools that exist, I chose Blender, which is a powerful, open source modeling package.
Blender is free of charge, and the Web contains plenty of good documentation. Importantly, Blender can be extended with scripts written in Python. I found two scripts that write M3G's file format. I used the script written by Gerhard Völkl because it has the most features and is available under a GPL license. Resources provides links to the mentioned tools.
The scene I want to create is a 3D text that rotates around the coordinate system's origin. You can download the finished Blender project from the Sample code section, but here is a summary of the steps to create the model yourself:
- Start with a new file (Ctrl-X) and remove the cube that Blender creates by default.
- Position the cursor at the origin and snap the cursor to the grid in front view and side view (Shift-S-3). The cursor is now exactly at the origin.
- In front view, add text (Add > Text), which creates an editable font object. Type in any text you want; I used "Hello, world!".
- Switch to object mode (Tab) and display the Editing context (F9). Click the Center New button in the Curve and Surface panel. The text is now centered horizontally around the origin.
- In the same panel, enter 0.100 in the Ext1 field, which extrudes the depth of the text and gives it a 3D look.
- In top view, add a Bezier circle (Add > Curve > Bezier Circle) that's orthogonal to the text. I'll use the circle later to bend the text so it follows a circular path.
- The circle is selected by default; scale it to make it bigger (type S for scaling).
- Blender creates a light and a camera by default. Leave both unchanged.
At this point, you'll have the model in shown in Figure 3.
Figure 3. Blender project after adding text (camera view)

Because the exporter script can only handle meshes, I have to convert the font object. Afterwards, I'll bend the resulting mesh around the circle.
- In object mode (switch with Tab), select the text with a right click and Alt-C converts the object type. First from font to curve, then a second conversion from curve to mesh.
- The text still selected, go to the Editing context (F9) and create a new material with the default values in the Links and Materials panel with the New button. A mesh must have a material; otherwise, the exporter script will report an error.
- Select the text, shift select the circle, and create a parent-child relationship with Ctrl-P. A list of options opens, choose curve deform.
- With only the text selected, jump to the Object context (F7). Select Track X in the Anim settings panel. This selects the x axis for alignment between the circle and the text. The text bends nicely in a circular shape now.
- The text will now stay deformed by the circle. Transform the text to your liking (type G for translation and R for rotation).
- Use Ctrl-Shift-A on the text to apply the deformation on the mesh and delete the circle because it's not needed any more. If the text looks strangely deformed after the circle is deleted, just press Tab twice to switch between object and edit mode.
- The background color can be changed in the World panel (switch to Shading context with F5 and select World buttons).
- In the Links and Materials panel of the Editing context (F9), rename the text's object name to Font#01. The number after the hash sign will be the user ID of the mesh.
Figure 4 shows the result. If you want to have a preview of the model, Blender can render it for you with F12.
Figure 4. Blender project after bending the text in a circular shape (camera view)

The final step is to export the 3D model into an M3G file.
- Install the exporter script by copying it into the .blender\scripts folder of your Blender installation.
- The script uses some imports that Blender's default Python installation doesn't have. You can solve this by installing a separate Python interpreter. Instead, I just commented out the offending import statements and the script worked fine.
- After restarting Blender, load your 3D model and run the script by selecting File > Export > M3G in J2ME.
- Select Export into *.m3g binary file. The script can also produce the Java™ language source equivalent of your 3D model. This might be handy if you want to make manual changes. For my purpose, the binary file is a good choice.
- Select the name and directory of the M3G file that you want to store and click the button Save M3G J2ME.
That's it. You've exported your model. Let's put it to use in an application. I'll use a structure similar to the previous example. Listing 4 shows the changes in init() and run().
Listing 4. Reading M3G binary files
/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering.
_graphics3d = Graphics3D.getInstance();
try
{
// Load World from M3G binary file.
Object3D[] objects = Loader.load(M3G_FILE_NAME);
_world = (World) objects[0];
// Change the camera's properties to match the current device.
Camera camera = _world.getActiveCamera();
float aspect = (float) getWidth() / (float) getHeight();
camera.setPerspective(60.0f, aspect, 1.0f, 1000.0f);
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* Drives the animation.
*/
public void run()
{
while(_isRunning)
{
// Find the Mesh and rotate it.
Mesh text = (Mesh) _world.find(USER_ID_TEXT);
text.postRotate(-2.0f, 0.0f, 0.0f, 1.0f);
repaint();
try
{
Thread.sleep(50);
}
catch (Exception e){}
}
}
|
The application became really simple. In init(), I read the M3G file that was exported from Blender and cast it into a World object. I can do this because I know the structure that the exporter script produces. The only manual change is the camera setup. The aspect ratio depends on the phone's screen size and must be calculated at run time. In the run() method, I find the text's mesh by its user ID; for this reason, I named the font object accordingly in Blender. The rotation is applied to the mesh object because World itself can't be transformed.
You have to be careful with the axes here. If you take your right hand and point the x axis (your thumb) to the right, Blender's y axis (the index finger) points away from you and the z axis (the middle finger) up. M3G's x axis is the same, but the y axis points up and the z axis towards you. (See Part 1 of the series for a description of M3G's coordinate system.) Both coordinate systems are right-handed, but 90 degree displaced on a rotation around the x axis. Because the camera and the text mesh were exported using Blender's coordinate system, the text displays fine. However, if you want to apply your own transformations, you have to change the axes. That's why in Listing 4, the rotation is around the z axis.
Figure 5 shows a screenshot of the finished application; BinaryWorldSample.java shows the full source code.
Figure 5. The Blender model imported into an M3G application

The big advantage of files exported from 3D tools is that you can split the work between developers and artists. When you create a more complex model in Blender, you'll soon realize that this needs a complementary skill set to software development. In commercial games for consoles and PC, the majority of manpower and money is spent on the artwork. For mobile phones, the model does not only have to look good, but also has to be small.
The file exported from Blender is 48 KB; most of the space is used for vertices, normals, and triangle strip definitions. If you analyze the file, you can see that the model has 3,392 vertices with 1,364 triangles defined in 1,014 triangle strips. Together with the enabled lighting, this is heavy work for your average mobile phone. If you switch off lighting, you don't need the normals and can save file size and processing power. M3G's file format supports compression, but you are better off without it because the M3G file will be compressed anyway when it's added to the application's Java Archive (JAR) file. In the sample application, the M3G file decreases to 16 KB as part of the archive. Blender also contains tools that let you reduce the number of vertices of your mesh with minimal impact on the shape (use the Decimator tool in the Mesh panel of the Editing context).
To use Blender for a commercial project, you'd have to put some time into improving the exporter script, which doesn't support all of M3G's features and is choosy in the way you model your 3D scenes. This might make investing in a non-free package worthwhile. On the other hand, the Blender script is available under GPL, so anyone can improve it. The author of the script recently added support for textures. Fortunately, someone has already invested time in providing a free tool.
After the model is imported into your application, you can apply all the features that M3G offers. I'll show you two common ones used in conjunction with retained mode -- aligning nodes and picking.
When you tee off in a golf game, you'd want the camera to always show the ball in the middle of the screen on its way to the ground. With Node.setAlignment(), a node, such as the camera, can be automatically oriented toward a reference, such as the golf ball. In the next example, I'll use this to transform the text so it's directly facing the camera.
Another common problem in games is selecting an object, which is called picking. When you design a first person shooter and fire a bullet, you have to check whether it hits its target. Group.pick() casts a ray that you supply with an origin and a direction. If the ray intersects with a mesh, the method returns true and reports the struck mesh object. I'll use this method to indicate when the text is below a crosshair that I'll draw in the middle of the screen. Listing 5 shows how to use alignment and picking.
Listing 5. Picking and alignment
/**
* Initializes the sample.
*/
protected void init()
{
// Get the singleton for 3D rendering.
_graphics3d = Graphics3D.getInstance();
try
{
// Load World from M3G binary file.
Object3D[] objects = Loader.load(M3G_FILE_NAME);
_world = (World) objects[0];
// Change the camera's properties to match the current device.
Camera camera = _world.getActiveCamera();
float aspect = (float) getWidth() / (float) getHeight();
camera.setPerspective(60.0f, aspect, 1.0f, 1000.0f);
// Align the text with the camera.
Mesh text = (Mesh) _world.find(USER_ID_TEXT);
text.setAlignment(camera, Node.Y_AXIS, null, Node.NONE);
text.align(null);
// Load crosshair images.
try
{
_crossHairImageOn = Image.createImage(CROSS_HAIR_ON);
_crossHairImageOff = Image.createImage(CROSS_HAIR_OFF);
}
catch (Exception e) {}
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* Renders the sample on the screen.
*
* @param graphics the graphics object to draw on.
*/
protected void paint(Graphics graphics)
{
_graphics3d.bindTarget(graphics);
_graphics3d.render(_world);
_graphics3d.releaseTarget();
// Draw crosshair.
if ((_crossHairImageOff != null) && (_crossHairImageOn != null))
{
graphics.drawImage(_crossHairOn ? _crossHairImageOn : _crossHairImageOff,
getWidth()/2, getHeight()/2, Graphics.VCENTER | Graphics.HCENTER);
}
drawMenu(graphics);
}
/**
* Handles key presses.
*
* @param keyCode key code.
*/
protected void keyPressed(int keyCode)
{
Mesh text = (Mesh) _world.find(USER_ID_TEXT);
switch (getGameAction(keyCode))
{
case LEFT:
text.postRotate(2.0f, 0.0f, 0.0f, 1.0f);
break;
case RIGHT:
text.postRotate(-2.0f, 0.0f, 0.0f, 1.0f);
break;
case FIRE:
init();
break;
// no default
}
// Check whether ray cast by crosshair intersects with the text.
_crossHairOn = isHit();
repaint();
}
/**
* Checks whether a ray that originates in the middle of the viewport and has
* the same direction as the active camera intersects with the text.
*
* @return true if there's an intersection.
*/
private boolean isHit()
{
boolean isHit = false;
RayIntersection rayIntersection = new RayIntersection();
Mesh mesh = (Mesh) _world.find(USER_ID_TEXT);
if (_world.pick(-1, 0.5f, 0.5f, _world.getActiveCamera(), rayIntersection))
{
if (rayIntersection.getIntersected() == mesh)
{
isHit = true;
}
}
return isHit;
}
|
After loading the M3G file, the call to setAlignment() in init() sets the alignment information for the text mesh. The first pair of parameters defines the z axis alignment; the second pair defines that of the y axis. I orient the text's z axis to the camera's y axis and leave the text's y axis unchanged. The choice of axes takes the difference in Blender's and M3G's coordinate system into account. The actual transformation on the text mesh is performed with a call to align(). If you wanted to continually track an object, you'd call align() each time the reference moves. init() also creates two images, which I use to draw a crosshair in paint().
Depending on the boolean value _crossHairOn, the crosshair is black (_crossHairImageOff) or white (_crossHairImageOn). You can easily mix calls to Graphics and Graphics3D. Just make sure that calls to Graphics3D are done between bindTarget() and releaseTarget() and calls to Graphics outside. The state of the crosshair is determined in keyPressed().
keyPressed() rotates the text either to the left or to the right when the corresponding key is pressed. At the same time, isHit() checks whether the crosshair is aiming at the text. In this method, I use Group.pick(), which comes in two variants -- one that takes an arbitrary ray origin and destination and one that defines the ray in viewport coordinates.
The viewport is the plane onto which M3G projects the graphics. Its origin is in the upper-left corner and the coordinates range from 0 to 1. Because I'm interested in picking a mesh based on the current camera, the latter variant is most convenient. The coordinates, (0.5, 0.5), denote the middle of the screen because the viewport is the same size as the screen by default. If an intersection is reported, I change the value of _crossHairOn and the respective crosshair image is shown as depicted in Figure 6. AlignmentPickingSample.java is this sample's source code in its entirety.
Figure 6. Text aligned with camera: a) Crosshair doesn't intersect with text, b) Crosshair intersects

In the two articles of this series, I've laid the foundation of developing 3D applications for mobile phones. Immediate mode renders 3D objects directly on the screen. Retained mode, on the other hand, lets you build a scene graph that you can manipulate and render at a later time. For immediate mode, it's important to understand how to manually create a 3D scene. Alternatively, you can import a model from a 3D tool using M3G's binary file format. This enables you to create complex 3D worlds and helps separating the work between developers and artists.
Still, I haven't mentioned anything about animation sequences, fog, layers, 3D sprites, morphing meshes, or meshes that represent a skeletally animated polygon. I'll leave that for you to explore.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code | wi-mobile2source.zip | 136KB | HTTP |
Information about download methods
Learn
- The first part of this series introduces M3G's immediate mode and how you can work with lights, cameras, and materials.
-
Understand the details of the devices that support M3G on the manufacturer's Web sites from Sony Ericsson, Nokia, or Motorola.
-
The four-part series J2ME 101 (developerWorks, November 2003) provides an introduction to the Java 2 Platform, Micro Edition and the Mobile Information Device Profile (MIDP).
Get products and technologies
-
Download the open source tool Blender to create your own 3D models. Tutorials and documentation can be found on BlenderWiki.
-
I found two exporter scripts for Blender that write M3G files: One by MIKlabs and one by Gerhard Völkl. I used the latter for the samples in this article.
-
Download the latest specification of the Mobile 3D Graphics API for J2ME (JSR 184).
-
Use Sun's Java Wireless Toolkit version 2.2 or higher to develop your own M3G applications.
-
The Java Wireless Toolkit can also be embedded into the Eclipse IDE by using the EclipseME plug-in.
- NetBeans with the Mobility Pack is another open source IDE that supports developing applications with the Java Wireless Toolkit.
-
Build your next development project with IBM trial software, available for download directly from developerWorks.
-
Visit the IBM Press Bookstore for a comprehensive listing of technical books.
Discuss
-
Games and 3D-related questions around Java can be discussed in Sun's Java Games Forums.
- Get involved in the developerWorks community with
developerWorks blogs.
Claus Höfele is a wireless applications expert who has extensive experience working in the telecommunications industry. He is a follower of Java technology in all its incarnations. Claus lives in Tokyo and can be contacted at Claus.Hoefele@gmail.com.




