Subscribe to News

Mobile 3D Graphics API for J2ME

Author : Equizz

From TechnologicalWiki

Jump to: navigation, search

Contents

[edit] Introduction

Mobile 3D Graphics API for J2ME is specified in JSR 184 and included with the WTK (Wireless Toolkit) from 2.2 version. This proposed JSR will provide a scalable, small-footprint, interactive 3D API for use it on mobile devices (CLDC). This API is based on OpenGL so the concepts are very similar.

[edit] Before to start

You need to have installed WTK 2.2 or later, and you can install a framework like netbeans or eclipse, to make your work easier.

You may also use 3D modeling applications also, like 3D Studio, Maya or Blender to generate your 3D objects. The last one, is a powerful 3D modeling program, and it's open source.

[edit] 3D Structure

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.

[edit] Object3D

An abstract base class for all objects that can be part of a 3D world. This includes the world itself, other scene graph nodes, animations, textures, and so on. In fact, everything in this API is an Object3D, except for Loader, Transform, RayIntersection, and Graphics3D.

Animations are applied to an object and its descendants with the animate method in this class.

Every Object3D can be assigned a user ID, either at authoring stage or at run time with the setUserID method. User IDs are typically used to find a known object in a scene loaded from a data stream.

The find method searches through all objects that are reachable from this object through a chain of references, and returns the one with the given user ID. If there are multiple objects with the same ID, the implementation may return any one of them. An object is defined to be reachable from itself and from all objects that have a chain of direct references to it. The parent reference and the alignment references in a Node do not count as direct references.

Object3D has an attribute called the user object. If an Object3D is loaded from a file by the Loader, the user object may contain a Hashtable that stores byte array values keyed by Integers. This is the case when one or more user parameters are associated with the serialized Object3D. If there are no user parameters, the user object is initially set to null. A typical example of using this type of persistent user data is to include application parameters inside a scene graph, such as in multi-level games, where a non-player character may have a series of attributes such as hit strength, armor, initial health, and so on. Although it is possible to have this information in a separate file, it is neater, easier and less error prone to associate it directly with the Object3D that represents the character.

Object3D is an abstract class, and therefore has no public constructors. When a class derived from Object3D is instantiated, the attributes inherited from Object3D will have the following default values:

   * user ID : 0
   * user object : null
   * animation tracks : none


[edit] Node

We can construct objects in a tree form. The first node is a Word object where we can add childs (more nodes), and we can add more childs to those nodes.

There are five different kinds of nodes: Camera, Mesh, Sprite3D, Light and Group.

Each node defines a local coordinate system that can be transformed relative to the coordinate system of the parent node (node transformation).

[edit] Word

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 via a common root, which is a World node. A scene graph need not be complete in order to be rendered. An example of a complete scene graph is shown in the figure below.

This is a Node, but it has two differences: A World can not be a child of any Node, and the node transformation is ignored for World objects when rendering.

The method render(World) in Graphics3D renders a World as observed by the currently active camera of that world. If the active camera is null, or the camera is not in the world, an exception is thrown. The world can still be rendered with the render(Node, Transform) method by treating the World as a Group. In that case, however, the application must explicitly clear the background and set up the camera and lights prior to rendering.

[edit] Texture

Texture2D is an Appearance component encapsulating a two-dimensional texture image and a set of attributes specifying how the image is to be applied on submeshes. The attributes include wrapping, filtering, blending, and texture coordinate transformation.

The texture image is stored as a reference to an Image2D. The image may be in any of the formats defined in Image2D. The width and height of the image must be non-negative powers of two, but they need not be equal. The maximum allowed size for a texture image is specific to each implementation, and it can be queried with Graphics3D.getProperties().

If the referenced Image2D is modified by the application, or a new Image2D is bound as the texture image, the modifications are immediately reflected in the Texture2D.

The first step in applying a texture image onto a submesh is to apply the texture transformation to the texture coordinates of each vertex of that submesh. The transformation is defined in the Texture2D object itself, while the texture coordinates are obtained from the VertexBuffer object associated with that submesh.

[edit] Nodes

[edit] Camera

It is a scene graph node that defines the position of the viewer in the scene and the projection from 3D to 2D.

The camera is always facing towards the negative Z axis, (0 0 -1), in its local coordinate system. The camera can be positioned and oriented in the same way as any other Node; that is, using the node transformations of the camera node and its ancestors.

Those are the three kind of camera types:

  • PERSPECTIVE Specifies a perspective projection matrix.
  • GENERIC Specifies a generic 4x4 projection matrix.
  • PARALLEL Specifies a parallel projection matrix.

We can set the type of the camera using those methods:

   setPerspective(float fovy,
                  float aspectRatio,
                  float near,
                  float far)

Constructs a perspective projection matrix and sets that as the current projection matrix. The rendered image will "stretch" to fill the viewport entirely (not just the visible portion of it). It is therefore recommended that the aspect ratio given here be equal to the aspect ratio of the viewport as defined in setViewport. Otherwise, the image will appear elongated in either the horizontal or the vertical direction. No attempt is made to correct this effect automatically, for example by adjusting the field of view. Instead, the adjustment is left for the application developer to handle as he or she prefers.

The perspective projection matrix P is constructed as follows.

      1/w         0            0               0
       0         1/h           0               0
       0          0      -(near+far)/d   -2*near*far/d
       0          0           -1               0

where

     h = tan(fovy/2)
     w = aspectRatio * h
     d = far - near

In the special case when the near and far distance are equal, the view volume has, in fact, no volume and nothing is rendered. Implementations must detect this rather than trying to construct the projection matrix, as that would result in a divide by zero error.

Parameters:

  • fovy - field of view in the vertical direction, in degrees
  • aspectRatio - aspect ratio of the viewport, that is, width divided by height
  • near - distance to the front clipping plane
  • far - distance to the back clipping plane
   setGeneric(Transform transform)

Sets the given 4x4 transformation as the current projection matrix. The contents of the given transformation are copied in, so any further changes to it will not affect the projection matrix.

Generic 4x4 projection matrices are needed for various rendering tricks and speed-up techniques that otherwise could not be implemented at all, or not without incurring significant processing overhead. These include, for example, viewing an arbitrarily large scene by setting the far clipping plane to infinity; rendering a large image in pieces using oblique projection; portals; TV screens and other re-projection cases; stereoscopic rendering; and some shadow algorithms.

Parameters:

  • transform - a Transform object to copy as the new projection matrix
   setParallel(float fovy,
               float aspectRatio,
               float near,
               float far)

Constructs a parallel projection matrix and sets that as the current projection matrix. All the other this are similar to the perspective camera.

Denoting the width, height and depth of the view volume by w, h and d, respectively, the parallel projection matrix P is constructed as follows.

     2/w       0          0           0
      0       2/h         0           0
      0        0        -2/d   -(near+far)/d
      0        0          0           1

where

     h = height (= fovy)
     w = aspectRatio * h
     d = far - near

Parameters:

  • fovy - height of the view volume in camera coordinates
  • aspectRatio - aspect ratio of the viewport, that is, width divided by height
  • near - distance to the front clipping plane in camera space
  • far - distance to the back clipping plane in camera space

[edit] Mesh

It defines a 3D object, consisting of triangles with associated material properties.

A Mesh is composed of one or more submeshes and their associated Appearances. A submesh is an array of triangle strips defined by an IndexBuffer object.

TriangleStripArray defines an array of triangle strips. In a triangle strip, the first three vertex indices define the first triangle. Each subsequent index together with the two previous indices defines a new triangle. The first triangle is considered even. For example, the strip S = (2, 0, 1, 4) defines two triangles: (2, 0, 1) and (0, 1, 4).

All submeshes in a Mesh share the same VertexBuffer. However, in the case of a MorphingMesh, a weighted linear combination of multiple VertexBuffers is used in place of a single VertexBuffer.

VertexBuffer holds references to VertexArrays that contain the positions, colors, normals, and texture coordinates for a set of vertices. The elements of these arrays are called vertex attributes in the rest of this documentation.

   setPositions(VertexArray positions,
                float scale,
                float[] bias)

The position of all the points int the mesh is V' = V*scale + bias

VertexArray is an array of integer vectors representing vertex positions, normals, colors, or texture coordinates.

   VertexArray(int numVertices,
                  int numComponents,
                  int componentSize)

It constructs a new VertexArray with the given dimensions. The array elements are initialized to zero. The elements can be set later with either the 8-bit or the 16-bit version of the set method, depending on the component size selected here.

Parameters:

  • numVertices - number of vertices in this VertexArray; must be [1, 65535]
  • numComponents - number of components per vertex; must be [2, 4]
  • componentSize - number of bytes per component; must be [1, 2]

Appearance is a set of component objects that define the rendering attributes of a Mesh or Sprite3D. It's attributes are grouped into component objects, each encapsulating a set of properties that are functionally and logically related to each other. This division helps applications to conserve memory by sharing component objects across multiple meshes and sprites. All components of a newly created Appearance object are initialized to null. It is completely legal for any or all of the components to be null even when rendering. Those atributes are: Material, Texture2D, PolygonMode, CompositingMode, Fog.

This class represents a conventional rigid body mesh, while the derived classes MorphingMesh and SkinnedMesh extend it with capabilities to transform vertices independently of each other.

MorphingMesh is equivalent to an ordinary Mesh, except that the vertices that are rendered are computed as a weighted linear combination of the base VertexBuffer and a number of morph target VertexBuffers. The resultant mesh is only used for rendering, and is not exposed to the application.

Vertex positions in a SkinnedMesh can be associated with multiple separately transforming Nodes, with a weight factor specified for each. This enables groups of vertices to transform independently of each other while smoothly deforming the polygon mesh "skin" with the vertices. This style of animation is highly efficient for animated characters.

[edit] Sprite3D

It defines a screen-aligned 2D image with a position in 3D space.

Sprite3D is a fast, but functionally restricted alternative to textured geometry. A Sprite3D is rendered as a screen-aligned rectangular array of pixels with a constant depth. The apparent size of a sprite may be specified directly in pixels (an unscaled sprite) or indirectly using the transformation from the Sprite3D node to the camera space (a scaled sprite).

[edit] Light

It defines the position, direction, color and other attributes of a light source. There are different kind of lights:

  • An ambient light source illuminates all objects in the scene from all directions. The intensity of light coming from an ambient light source is the same everywhere in the scene. The position and direction of an ambient light source therefore have no effect.
  • A directional light source corresponds to sunlight in the real world. It illuminates all objects in the scene from the same direction, and with a constant intensity. Similar to ambient light, the position of a directional light source has no effect. The direction of the light is along the negative Z axis of the Light node's local coordinate system.
  • An omnidirectional light source, also known as a point light, casts equal illumination in all directions from the origin of the Light node's local coordinate system. The intensity of light coming from an omnidirectional light source can be attenuated with distance. The orientation of an omnidirectional Light node has no effect; only the position matters.
  • A spot light source casts a cone of light centered around the direction of its negative Z axis. The concentration of light within the cone can be adjusted with the spot exponent. The intensity of light coming from a spot light can be attenuated with distance from the source. Both the orientation and the position of the Light node have an effect with spot lights.

The RGB intensity contributed to the lighting calculation by a Light is (IR, IG, IB), where I is the intensity of the Light and (R, G, B) is its color. Note that while 1.0 is a nominal full intensity, applications may use values higher than that for more control over highlights, for example. The intensity may also be set to negative to specify an "antilight" or "dark".

Lights can be turned on and off using Node.setRenderingEnable(boolean).

[edit] Group

Itserves as a root for scene graph branches. A scene graph node that stores an unordered set of nodes as its children.

The parent-child relationship is bidirectional in the sense that if node A is a child of node B, then B is the (one and only) parent of A. In particular, the getParent method of A will return B. Besides Group nodes, this also concerns SkinnedMesh nodes: the skeleton group is the one and only child of a SkinnedMesh.

A node can have at most one parent at a time, and cycles are prohibited. Furthermore, a World node cannot be a child of any node. These rules are enforced by the addChild method in this class, as well as the constructor of SkinnedMesh.

[edit] 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, including the rendering of World objects. There is no other way to draw anything in this API.

public class MyCanvas extends Canvas {
    Graphics3D myG3D = Graphics3D.getInstance();

    public void paint(Graphics g) {
        try {
            myG3D.bindTarget(g);
            // ... update the scene ...
            // ... render the scene ...
        } finally {
            myG3D.releaseTarget();
    }
}

[edit] Immediate mode and retained mode rendering

There are four different render methods, operating at different levels of granularity. The first method is for rendering an entire World. When this method is used, we say that the API operates in retained mode. The second method is for rendering scene graph nodes, including Groups. The third and fourth methods are for rendering an individual submesh. When the node and submesh rendering methods are used, the API is said to operate in immediate mode.

There is a current camera and an array of current lights in Graphics3D. These are used by the immediate mode rendering methods only. The retained mode rendering method render(World) uses the camera and lights that are specified in the World itself, ignoring the Graphics3D camera and lights. Instead, render(World) replaces the Graphics3D current camera and lights with the active camera and lights in the rendered World. This allows subsequent immediate mode rendering to utilize the same camera and lighting setup as the World.

[edit] Paint loop

The paint method is called by MIDP after the application has issued a repaint request. We draw a new frame on this Canvas by binding the current Graphics object as the target, then rendering, and finally releasing the target.

protected void paint(Graphics g) {
        // Get the singleton Graphics3D instance that is associated
        // with this midlet.

        Graphics3D g3d = Graphics3D.getInstance();

        // Bind the 3D graphics context to the given MIDP Graphics
        // object. The viewport will cover the whole of this Canvas.

        g3d.bindTarget(g);

        // Apply animations, render the scene and release the Graphics.

        myWorld.animate(currentTime);
        g3d.render(myWorld);    // render a view from the active camera
        g3d.releaseTarget();    // flush the rendered image
        currentTime += 50;      // assume we can handle 20 frames per second
    }
}

[edit] Loading M3G Files

There are software that can be the hard work of creation the Object3D elelemts. Instead of programing the entire scene, we can create it using a 3D program, export it into a .m3g file, and finally load it into our application.

The class Loader,downloads and deserializes scene 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.

[edit] Supported data types

The Loader can deserialize instances of any class derived from Object3D. These include scene graph nodes such as World, Group, Camera and Light; attribute classes such as Material, Appearance and Texture2D; animation classes such as AnimationTrack and KeyframeSequence; and so on.

The data to be loaded must constitute a valid M3G file. Alternatively, it may be a PNG image file, in which case a single, immutable Image2D object is returned, with the pixel format of the Image2D corresponding to the color type of the PNG.

[edit] Using the Loader

The Loader class cannot be instantiated, and its only members are the two static load methods. The methods are otherwise identical, but one of them takes in a byte array, while the other takes a named resource, such as a URI or an individual file in the JAR package. Named resources must always have an absolute path, otherwise the results are undefined.

Any external references in the given file or byte array are followed recursively. When using the load variant that takes in a URI, the references may be absolute or relative, but when using the byte array variant, only absolute references are allowed. External references are also treated as case-sensitive.

The load methods only return once the entire contents of the given file (or byte array) have been loaded, including any referenced files. This means that displaying content while downloading (progressive loading) is not supported.

   Object3D[] root = Loader.load("/myFile.m3g");

[edit] Managing the loaded objects

The load methods return an array of Object3Ds. These are the root level objects in the file; in other words, those objects that are not referenced by any other objects. The array is guaranteed not to contain any null objects, but the order of the objects in the array is undefined.

The non-root objects (often the majority) can be found by following references recursively, starting from the root objects. This can be done conveniently with the getReferences method in Object3D. Another way to find a specific object is to tag it with a known user ID at the authoring stage, and search for that among the loaded objects using the find method..

Since the root-level objects are returned in an Object3D array, the application must find out their concrete types before using their full functionality. In the typical case, when the content is developed in conjunction with the application code and deployed in the same JAR file, the application should know what the root-level objects are. If this information is not available, or there is a need to check that the objects are as expected, the application can use the run-time type information that is built into Java. For example, a simple animation player application might want to check that the downloaded object is indeed a World, and display an appropriate error message otherwise.

   World myWorld = (World) root;
   Appearance myAppear = (Appearance) myWorld.find(MY_APPEAR_ID);
   ...

[edit] Transformations

Transformable is an abstract base class for Node and Texture2D that extends Object3D, defining common methods for manipulating node and texture transformations.

Node transformations and texture transformations consist of four individual components: translation (T), orientation (R), scale (S) and a generic 4x4 matrix (M). Formally, a homogeneous vector p = (x, y, z, w), representing vertex coordinates (in Node) or texture coordinates (in Texture2D), is transformed into p' = (x', y', z', w') as follows:

     p' = T R S M p

The bottom row of M must be equal to (0 0 0 1)

When a class derived from Transformable is instantiated, the attributes inherited from it will have the following default values:

   * scale : (1,1,1)
   * translation : (0,0,0)
   * orientation : angle = 0, axis = undefined
   * matrix : identity

The transformation components are initially set to identity so that they do not affect the texture coordinates or vertex coordinates in any way. Note that the orientation axis can be left undefined because the angle is zero.

The Transform class is a generic 4x4 floating point matrix, representing a transformation. By default, all methods dealing with Transform objects operate on arbitrary 4x4 matrices. Any exceptions to this rule are documented explicitly at the method level. Even though arbitrary 4x4 matrices are generally allowed, using non-invertible (singular) matrices may produce undefined results or an arithmetic exception in some situations. Specifically, if the modelview matrix of an object is non-invertible, the results of normal vector transformation and fogging are undefined for that object. We can use it to invert, translate, rotate, scale, and more transformations in 4x4 matrices.

The basic transformation methods are those:

   * scale(float sx, float sy, float sz)
   * translate(float tx, float ty, float tz)
   * postRotate(float angle, float ax, float ay, float az)
   * preRotate(float angle, float ax, float ay, float az)
   * setTransform(Transform transform)
   * setOrientation(float angle, float ax, float ay, float az)
  • scale multiplies the current scale component by the given scale factors.
  • translate adds the given offset to the current translation component.
  • postRotate multiplies the current orientation component from the right by the given orientation.
  • preRotate multiplies the current orientation component from the left by the given orientation.
  • setTransform sets the matrix component of this Transformable by copying in the given Transform.
  • setOrientation sets the orientation component of this Transformable.

[edit] Animation

Animations are applied to an object and its descendants with the animate method in this class. The animate(int time) method, updates all animated properties in this Object3D and all Object3Ds that are reachable from this Object3D. The parameter time is the world time to update the animations to.

The objects needed for animation and their relationships are shown in the figure below.

[edit] AnimationTrack

An AnimationTrack associates a KeyframeSequence with an AnimationController and an animatable property.

If an Object3D have one or more animatable properties it is called an animatable object. Each property constitutes a unique animation target. Animatable object may reference zero or more AnimationTracks. Here a list of some of them:

  • ALPHA Specifies the alpha factor of a Node, or the alpha component of the Background color, Material diffuse color, or VertexBuffer default color as an animation target.
  • AMBIENT_COLOR Specifies the ambient color of a Material as an animation target.
  • COLOR Specifies the color of a Light, Background, or Fog, or the texture blend color in Texture2D, or the VertexBuffer default color as an animation target.
  • DENSITY Specifies the fog density in Fog as an animation target.
  • EMISSIVE_COLOR Specifies the emissive color of a Material as an animation target.
  • INTENSITY Specifies the intensity of a Light as an animation target.
  • ORIENTATION Specifies the orientation (R) component of a Transformable object as an animation target.
  • SCALE Specifies the scale (S) component of a Transformable object as an animation target.
  • SHININESS Specifies the shininess of a Material as an animation target.
  • TRANSLATION Specifies the translation (T) component of a Transformable object as an animation target.
  • VISIBILITY Specifies the rendering enable flag of a Node as an animation target.


We can create an AnimationTrack to change some property of an Object3D in the time. Once the sequence was defined, it's also needed to attach an AnimationControler to animate the object .

   MyAnimationTrack = new AnimationTrack(KeyframeSequence mySequence, int myProperty);
   MyAnimationTrack.setController(AnimationController myController);
   MyObject3D.addAnimationTrack(MyAnimationTrack);

[edit] KeyframeSequence

KeyframeSequence encapsulates animation data as a sequence of time-stamped, vector-valued keyframes. Each keyframe represents the value of an animated quantity at a specific time instant.

   KeyframeSequence(int numKeyframes, int numComponents, int interpolation)
  • numKeyframes - number of keyframes to allocate for this sequence
  • numComponents - number of components in each keyframe vector
  • interpolation - one of the interpolation modes listed above

The interpolation constants are: STEP, LINEAR and SPLINE that can be specified for any type of keyframes. On the other hand, SLERP and SQUAD can only be specified for 4-component keyframes, which are then interpreted as quaternions.

  • CONSTANT this sequence is to be played back just once and not repeated.
  • LINEAR linear interpolation between keyframes.
  • LOOP this sequence is to be repeated indefinitely.
  • SLERP spherical linear interpolation of quaternions.
  • SPLINE spline interpolation between keyframes.
  • SQUAD spline interpolation of quaternions.
  • STEP stepping from one keyframe value to the next.

Once this object is defined, let's create and attach the keyFrames.

   MyKeyframeSequence.setKeyframe(int index, int time, float[] value);
  • index - index of the keyframe to set
  • time - time position of the keyframe, in sequence time units
  • value - float array containing the keyframe value vector

[edit] AnimationControler

AnimationController controls the position, speed and weight of an animation sequence. An AnimationTrack needs to use the setController method. Here some of the more important methods:

This animation controller will subsequently be active when the world time t is such that start <= t < end, and inactive outside of that range. As a special case, if start and end are set to the same value, this animation controller is always active.

   setActiveInterval(int start, int end)

Sets a new playback speed for this animation. The speed is set as a factor of the nominal speed of the animation: 1.0 is normal playback speed (as specified by the keyframe times in the associated animation tracks), 2.0 is double speed, and -1.0 is reverse playback at normal speed. A speed of 0.0 freezes the animation.

   setSpeed(float speed, int worldTime)

Sets a new playback position, relative to world time, for this animation controller. This sets the internal reference point (twref, tsref) to (worldTime, sequenceTime) to shift the animation to the new position.

   setPosition(float sequenceTime, int worldTime)

We can get the world time using this:

   MyAnimationControler.getRefWorldTime();

[edit] Related Links

How to start with Mobile 3D Graphics for J2ME

[edit] Reference

http://jcp.org/en/jsr/detail?id=184

http://developers.sun.com/mobility/apis/articles/3dgraphics/

https://developer.sonyericsson.com/site/global/home/java_3d/p_java3d.jsp

https://developer.sonyericsson.com/community/java_me/mobile_3d

Main Collaborators