JSR-184 Public Review Draft - Apr. 30, 2003.

Package javax.microedition.m3d

This package contains the whole Mobile 3D Graphics API (M3D), comprising about 250 methods in about 30 classes.

See:
          Description

Class Summary
AnimationController A set of methods for controlling multiple AnimationTracks as one.
AnimationTrack AnimationTrack associates a KeyframeSequence with an animatable property, and an AnimationController that controls the sequence.
Appearance Appearance defines attributes that are needed for rendering a Mesh or Sprite.
Background Attributes for clearing the color buffer.
Camera Camera defines parameters for projecting the 3D world to the display.
CompositingMode An Appearance component encapsulating per-pixel compositing attributes.
Fog An Appearance component encapsulating attributes for fogging.
GeometryMode An Appearance component encapsulating polygon-level attributes.
Graphics3D A singleton 3D graphics context that can be bound to a rendering target.
Group The Group node stores an unordered set of nodes as its children in the scene graph.
Image2D A two-dimensional image that can be used as a texture, background or sprite image.
IndexBuffer IndexBuffer defines how to connect vertices in a VertexBuffer to form 3D surface primitives, such as triangles.
KeyframeSequence KeyframeSequence encapsulates animation data as a sequence of time-stamped, vector-valued keyframes.
Light The Light node represents different kinds of light sources.
Loader A synchronous loader (deserializer) for entire scene graphs, individual branches, and attribute objects.
Material An Appearance component encapsulating material attributes for lighting computations.
Mesh Mesh is a renderable 3D geometry object, defined as a polygonal surface.
MorphingMesh A vertex morphing mesh.
Node An abstract base class for scene graph nodes, that is, instances of objects in a scene graph.
Object3D An abstract base class for all objects that can be part of a 3D world.
RayIntersection A RayIntersection object is filled in by the pick methods in Group and Camera.
SkinnedMesh A skeletally animated mesh.
Sprite Sprite is a scene graph node representing a 2-dimensional image that has 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.
TriangleStripArray TriangleStripArray defines a set 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 top-level container for scene graphs.
 

Package javax.microedition.m3d Description

This package contains the whole Mobile 3D Graphics API (M3D), comprising about 250 methods in about 30 classes.

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.

In addition to the API itself, JSR-184 also defines a corresponding file format for efficient storage and transfer of all necessary data. This includes meshes, textures, scene hierarchies, material properties, animation data, 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.

A good place to start reading the specification is in the example applications at the end of this overview page. 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 short section about documentation conventions below.

Documentation Conventions

The following general conventions are observed in the documentation of this API.
Null parameters. By default, all methods will throw a NullPointerException if a null object is passed in, either by itself or as an array element. NullPointerException is therefore not listed in any of the method descriptions. In cases where the treatment of null objects is different from the default, the behavior is documented explicitly at the level of individual methods.

Deferred exceptions. Most exceptions are thrown immediately upon method invocation, when erroneous input is detected. However, the application is in some cases allowed to bring an object, or an aggregate of objects, into an invalid state. There are two operations that may throw an exception as a result of their input being in an invalid state: the render methods in Graphics3D and the pick methods in Group and Camera.

Matrix notation. Matrices are denoted as upper case bold letters, and vectors as lower case bold letters. For example, M denotes a matrix and v a vector. Matrices have 4x4 and vectors 4 elements, unless stated otherwise. Vectors are always column vectors, and are consequently multiplied from the left: v' = M × v.

Numeric ranges. Closed numeric ranges are denoted with square brackets and open ranges with round brackets. For example, [0,10) denotes the values from zero to ten, including zero but not including ten. Depending on the context, a numeric range may include all real numbers or only integers.

OpenGL references. All references to OpenGL in this documentation are to version 1.3. The OpenGL 1.3 specification is available here.

Coordinate systems. All 2D coordinate systems follow the MIDP convention where the origin is in the upper left corner and integer coordinates are at pixel boundaries. By default, the 3D coordinate systems are right-handed, and all rotations obey the right-hand rule: looking down the axis of rotation, positive angles are clockwise. However, the application is free to set up a left-handed 3D coordinate system by use of transformation matrices.

Diagram notation. The following common notation is used in diagrams that involve scene graph nodes and node components.

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 JSR-184 API is by using a system properties query. To discover this package, call System.getProperty with a key of microedition.m3d.version. If the API is present, the value returned is the version of the API (currently "1.0"). If the API is not present then the key is also not present and null will be returned.
 

General implementation requirements

Numeric ranges 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 representation must satisfy the following requirements:
The input range must be at least [2-64, 263]. Any floating-point value in this range is legal as input to a method that requires floating point data. If the input value is not within that range, or is not a floating-point number, the results are undefined.

Elementary operations must have a range of at least [2-64, 263]. If the operands and the result of an elementary arithmetic operation are within this range, the operation must not overflow or underflow. If the operands or the result are not within that range, the result is undefined.

The precision must be at least 16 significant bits. Provided that the operands and the result of an elementary arithmetic operation are within the range specified above, the result must be accurate to at least 16 significant bits.

These requirements apply to all arithmetic operations in this API, unless explicitly specified otherwise. In particular, they apply to node transformations in the scene graph, all vertex coordinate transformations, and all methods in the Transform class. These requirements do not apply to triangle rasterization and per-fragment operations, such as blending.

All blending arithmetic is done component-wise and saturated to 1.0. Precision is not specified strictly, but the implementation must guarantee that the following holds true for all values of alpha and s between [0, 1]:

alpha × s + (1 - alpha) × s = s

Thread safety

Implementations must not crash when accessed from multiple threads at the same time. The results in that case, however, may be unpredictable.
 

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 never 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.m3d.*;

public class MyCanvas extends Canvas {

    private Graphics3D      iG3D;
    private Camera          iCamera;
    private float           iAngle = 0.0f;
    private Transform       iTransform = new Transform();
    private Background      iBackground = new Background();
    private VertexBuffer    iVb;    // vertex positions, normals, colors, texcoords
    private IndexBuffer     iIb;    // indices to VertexBuffer, forming tri-strips
    private Appearance      iAppearance; // material, texture, compositing, etc.
    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));

        // set up the Graphics3D
        iG3D = Graphics3D.createGraphics3D();
        
        // 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 transform for the camera
        Transform transform = new Transform();
        transform.translate( 0.0f, 0.0f, 30.0f );

        // set the camera for the Graphics3D and transform it to the desired position
        iG3D.setCamera( iCamera, transform );

        // create a light to light the scene
        Light light = new Light();
        light.setDirectionalEnable( true ); // the light is directional
        light.setColor( 0xffffff );         // white light
        light.setIntensity(1.25f);          // set the intensity of the light

        // set the zeroth light for the Graphics3D
        iG3D.setLight( 0, light, transform ); // same position as the camera

        // 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, 1,    0, 0, 1,    0, 0, 1,    0, 0, 1,
                0, 0,-1,    0, 0,-1,    0, 0,-1,    0, 0,-1,
               -1, 0, 0,   -1, 0, 0,   -1, 0, 0,   -1, 0, 0,
                1, 0, 0,    1, 0, 0,    1, 0, 0,    1, 0, 0,
                0, 1, 0,    0, 1, 0,    0, 1, 0,    0, 1, 0,
                0,-1, 0,    0,-1, 0,    0,-1, 0,    0,-1, 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 );

        int[] stripLen = { 4, 4, 4, 4, 4, 4 };  // the length of each triangle strip
        
        // 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 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.FORMAT_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 ); // add the Texture2D to the Appearance
        iAppearance.setMaterial(iMaterial);
        iMaterial.setVertexColorTrackingEnable( true );     // track per-vertex colors
        iMaterial.setColor(Material.SPECULAR, 0xFFFFFFFF);  // specular = white
        iMaterial.setShininess(100.0f);

        iBackground.setColor( 0xf54588 ); // set the background color
    }
                   
    // paint the scene
    protected void paint(Graphics g) {
    
        iG3D.setTrueColorEnable( true );      // true color rendering
        iG3D.setDitheringEnable( true );      // combined with dithering
        iG3D.setAntialiasingEnable( false );  // but no antialiasing
        
        // Bind the Graphics of this Canvas to our Graphics3D. The viewport
        // is automatically set to cover the entire clipping rectangle of the
        // Graphics object.
        
        iG3D.bindTarget( g );
        
        // clear the color and depth buffers
        iG3D.clear( Graphics3D.BUF_COLOR | Graphics3D.BUF_DEPTH, iBackground );

        // update our transform (this will give us a rotating cube)
        iAngle += 1.0f;
        iTransform.setIdentity();
        iTransform.rotate( iAngle,          // rotate 1 degree
                       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.m3d.*;

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 Graphics3D myGraphics3D = null;
    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/m3d/samples/simple.m3d");
                
            // Assume the world is the first root node loaded.
            myWorld = (World)roots[0];

            // Create the Graphics3D instance and set rendering quality hints.
            myGraphics3D = Graphics3D.createGraphics3D();
            myGraphics3D.setTrueColorEnable(false);
            myGraphics3D.setDitheringEnable(false);
            myGraphics3D.setAntialiasingEnable(false);

            // 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;
        myGraphics3D = null;
    }

    /**
     * destroyApp()
     */
    public void destroyApp(boolean unconditional) throws MIDletStateChangeException
    {
        myRefreshTimer.cancel();
        myRefreshTimer = null;

        // Release resources.
        myGraphics3D = null;
        myWorld = null;
    }

    /**
     * MIDlet paint method.
     */
    public void paint(Graphics g)
    {
        // We are not fully initialised yet; just return.
        if(myCanvas == null || myGraphics3D == 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 and blit to our Graphics.
        myGraphics3D.bindTarget(g);
        myGraphics3D.clear(Graphics3D.BUF_COLOR | Graphics3D.BUF_DEPTH, null);
        myGraphics3D.render(myWorld);
        myGraphics3D.releaseTarget();

        // Subtract time taken to do the update.
        validity -= System.currentTimeMillis() - currentTime;

        if(validity < 1)
        {    // The validity 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); }
    }
}

JSR-184 Public Review Draft - Apr. 30, 2003.

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