M3G 1.1 -- Jun 22, 2005

Package javax.microedition.m3g

Defines an API for rendering three-dimensional (3D) graphics at interactive frame rates, including a scene graph structure and a corresponding file format for efficient management and deployment of 3D content.

See:
          Description

Class Summary
AnimationController Controls the position, speed and weight of an animation sequence.
AnimationTrack Associates a KeyframeSequence with an AnimationController and an animatable property.
Appearance A set of component objects that define the rendering attributes of a Mesh or Sprite3D.
Background Defines whether and how to clear the viewport.
Camera A scene graph node that defines the position of the viewer in the scene and the projection from 3D to 2D.
CompositingMode An Appearance component encapsulating per-pixel compositing attributes.
Fog An Appearance component encapsulating attributes for fogging.
Graphics3D A singleton 3D graphics context that can be bound to a rendering target.
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.
IndexBuffer An abstract class defining how to connect vertices to form a geometric object.
KeyframeSequence Encapsulates animation data as a sequence of time-stamped, vector-valued keyframes.
Light A scene graph node that represents different kinds of light sources.
Loader Downloads and deserializes scene graph nodes and node components, as well as entire scene graphs.
Material An Appearance component encapsulating material attributes for lighting computations.
Mesh A scene graph node that represents a 3D object defined as a polygonal surface.
MorphingMesh A scene graph node that represents a vertex morphing polygon mesh.
Node An abstract base class for all scene graph nodes.
Object3D An abstract base class for all objects that can be part of a 3D world.
PolygonMode An Appearance component encapsulating polygon-level attributes.
RayIntersection A RayIntersection object is filled in by the pick methods in Group.
SkinnedMesh A scene graph node that represents a skeletally animated polygon mesh.
Sprite3D A scene graph node that represents a 2-dimensional image with a 3D position.
Texture2D An Appearance component encapsulating a two-dimensional texture image and a set of attributes specifying how the image is to be applied on submeshes.
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 TriangleStripArray defines an array of triangle strips.
VertexArray An array of integer vectors representing vertex positions, normals, colors, or texture coordinates.
VertexBuffer VertexBuffer holds references to VertexArrays 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.
 

Package javax.microedition.m3g Description

Defines an API for rendering three-dimensional (3D) graphics at interactive frame rates, including a scene graph structure and a corresponding file format for efficient management and deployment of 3D content.

The function of this API is to provide Java application programmers with an efficient and flexible means to display animated 3D graphics in real time on embedded devices. To cater for the needs of different types of applications, both an easy-to-use scene graph structure and an immediate mode interface are provided. All animation and rendering features are available for scene graph objects and individually rendered objects alike. The developer therefore does not need to choose between the immediate mode and the scene graph, but rather can mix and match both within the same application.

Besides the API itself, a corresponding file format for efficient storage and transfer of all necessary data is also defined. This data includes meshes, textures, scene hierarchies, material properties, animation keyframes, and so on. Data is written into a file by content creation tools on a PC, and loaded into the API through the Loader class.

Getting Started

The example applications at the end of this page provide a good means to get a quick overview of this API. Of the individual classes, Graphics3D is perhaps the most important, because all rendering is done there. The World class is crucial because it 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, and also the place where animations are applied. We also recommend you to read the rest of this package description.

Package Discovery

Because of its optional nature, this API may not always be available on every platform. Each profile and platform may have their own methods for J2ME package discovery as there is no universal method existing at this time. An additional method for package discovery of the Mobile 3D Graphics API is by using a system properties query. To discover this package, call System.getProperty with a key of microedition.m3g.version. If the API is present, the value returned is the version of the API (this version is "1.1", and the previous version was "1.0"). If the API is not present then the key is also not present and null will be returned.

Documentation Conventions

The following general conventions are observed in the documentation of this API.

General Implementation Requirements

Rasterization

By default, vertices, indices, triangles, and fragments are processed as in OpenGL. In particular, triangle rasterization is done as specified in section 3.5.1 of the OpenGL specification.

The reference geometry and fragment pipelines are shown below. A rough mapping of Mesh components and other objects to the pipeline stages is also shown. Note that the ordering of the stages is the same as in OpenGL. Implementations may optimize their operation by doing things in a different order, but only if the result is exactly the same as it would be with the reference pipelines.

Numeric Range and Accuracy

The floating point format used for input and output is the standard IEEE float, having an 8-bit exponent and a 24-bit mantissa normalized to [1.0, 2.0). To facilitate efficient operation without floating point hardware, implementations are allowed to substitute more constrained representations internally. The internal format, and conversion from the input format to the internal format, must satisfy the following:

These requirements also apply to elementary arithmetic operations, which include addition, subtraction and multiplication. The operands are then taken to be in the internal format rather than the input format, and the value against which the precision is measured is taken to be the mathematically correct result, rounded to the nearest representable value. In addition, elementary arithmetic operations must satisfy the following:

These requirements apply to all operations in this API, except rasterization and per-fragment operations, such as depth buffering, blending, and interpolation of colors and texture coordinates. In particular, the requirements do apply to node transformations in the scene graph; vertex coordinate, texture coordinate and normal vector transformations; picking; keyframe interpolation; mesh morphing; skinning; and all methods in the Transform class.

Blending, interpolation, comparisons and other operations on color, alpha and (screen-space) depth values must have a numeric range, minimum absolute value, and precision at least equivalent to the corresponding channel in the frame buffer. For example, an 8-bit color channel has R = [0, 1], d = 1/255, and p = 8. Within that domain, the rules are as specified above, with two additional requirements:

Loss of precision is allowed when converting the result of the operation into the frame buffer format, which is commonly fixed-point. The higher-precision internal value may be rounded to either of the two closest representable values in the frame buffer format. Note that the final precision will get progressively worse as the intermediate result approaches zero. In the worst case, all significant bits except the leading zero or one will be lost.

Correspondence of Getters and Setters

When querying the value of some property in the API, the returned value does not need to be exactly the same that was set with the corresponding set method. Instead, it may be any value that produces an equivalent result. The returned values are also not required to be in any "canonical" or "normalized" form. In the case of node orientation, for example, there are a number of different axis-angle combinations that specify the same orientation, and any one of them may be returned.

The returned value may also be an approximation of the original value, as long as the accuracy constraints for the particular type of data are satisfied.

References to Objects

Object3D instances are always held by reference rather than copied in. Changes to an Object3D therefore have immediate effect in any referring Object3D. For example, changes to an Image2D attached to a Background take effect without having to call the Background.setImage method again.

Objects that are not instances of Object3D are copied in by default. Any exceptions to this rule are clearly documented in the individual method descriptions. Note that arrays are Objects in Java, and are therefore copied in rather than held by reference. Also note that the Transform class, although defined in this API, is not derived from Object3D.

To clarify the handling of arrays, consider a hypothetical class X that takes in an Object3D array in its constructor. The constructor copies in the array, but stores the elements of the array by reference. Thus, replacing one Object3D in the array with another will have no effect on the instance of X that was just created. Indeed, the application may freely reuse the array or leave it for garbage collection. By contrast, any modifications to the actual Object3D instances that were contained in the array will automatically be reflected in the new instance of X.

Deferred exceptions

The scene graph as well as individual objects are allowed to remain in an incomplete or invalid state for as long as their contents are not actually needed by the implementation (for rendering or some other purpose). An IllegalStateException is thrown only when the objects really must be valid. This kind of deferred error checking is necessary for aggregate objects, whose validity depends on other objects that the application can add, remove or change at any time. There are four operations in this API that can throw these deferred exceptions: the render methods in Graphics3D, the pick methods in Group, the align method in Node, and the animate method in Object3D.

The fact that deferred exceptions may or may not be thrown, depending on whether the implementation actually needs the offending data, can cause varying behavior between different implementations. For example, some implementations may use visibility culling to remove objects from further processing without having to check their vertex arrays, while others may use a brute-force approach and push all objects through the rendering pipeline. To reduce this variability without restricting innovation, implementations must obey the following rules when rendering or picking:

  1. Objects that are out of scope or disabled must not be validated.
  2. Objects that are not rendered or picked, even though they are enabled and within scope, may be validated.
  3. Any data that are required in order for rendering or picking to produce meaningful results must be validated.

A Node can be disabled by clearing its rendering and picking enable flags. A submesh can be disabled by setting its Appearance to null. By definition, all objects are disabled when rendering from a Camera that has zero view volume.

Thread Safety

Implementations must not crash or throw an exception as a result of being accessed from multiple threads at the same time. However, the results of the requested operation in that case may be unpredictable.

No method in this API is allowed to block waiting for a resource, such as a rendering target, to be released. This is to guarantee that no deadlock situations will occur. Also, any resources required by a method must be released upon return. No method is allowed to leave its host object or other resources locked.

Pixel Format Conversion

Several different pixel formats are supported in rendering targets, textures, sprites, and background images. Depending on the case, a mismatch between the source and destination pixel formats may require a format conversion to be done. The general rules that are obeyed throughout the API are as follows:

More specific rules related to pixel formats are specified on a case-by-case basis in classes dealing with images and the frame buffer. These include Graphics3D, Image2D, Texture2D, CompositingMode and Background.

Example Applications

Two example MIDlets using the API are presented below. The first MIDlet is a pure immediate mode application that displays a rotating, texture-mapped cube. It shows how to initialize a 3D graphics context, bind it to a MIDP Canvas, and render some simple content with it. It also illustrates how to create a Mesh object "manually", that is, how to set up the coordinates, triangle connectivity, texture maps, and materials. In practice, this is usually not done programmatically, but with a 3D modeling tool. Loading a ready-made Mesh object with all the necessary attributes is a simple matter of calling the load method in Loader.

The other example MIDlet is a retained mode application that plays back a ready-made animation that it downloads over http.

Examples:
(1) Immediate mode example MIDlet: Class MyCanvas.
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 and enable mipmapping
        // 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(0xf54588); // 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 frame
                       1.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();
    }
}
(2) Immediate mode example MIDlet: Class MIDletMain.
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;
    }
 
    /**
     * Our timer task for providing animation.
     */
    class MyTimerTask extends TimerTask {
    	public void run() {
            if( displayable != null ) {
                displayable.repaint();
            }
        }
    }
}
(3) Retained mode example MIDlet.
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("http://www.example.com/m3g/samples/simple.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() { }
 
        /**
         * Cleanup and destroy.
         */
        void destroy() { }
 
        /**
         * Ask myTestlet to paint itself
         */
        protected void paint(Graphics g) { myTestlet.paint(g); }
    }
}

M3G 1.1 -- Jun 22, 2005

Copyright © 2005 Nokia Corporation. See the Copyright Notice for details.