Dev 145 – JSR184 in Retained Mode – part 1 of 2

M3G’s Retained Mode allows you to work on the scenes of a game through a hierarchical representation of the structure of the 3D world and its components

In the September article we explained how it is possible to create 3D scenes using the Immediate Mode, which allows you to draw objects directly on the screen. It is correct to think of this method of using M3G as an access to low-level functions.

It is obvious that this approach is ill-suited to purposes where the complexity of the worlds to be managed represents a strongly determining component. Instead of rendering every single object and changing the scene settings at each frame, it can be very useful to have in memory all the structured data in some way.

As we shall see, this mode is also particularly useful when drawing programm entire scenes with the 3D modeling such as 3D Studio Max or Blender.

Organization of a scene

In Retained Mode it is possible to store via a tree structure all the components of the 3D world including objects, cameras, lights and (even) animation information.

Each element of the tree is made up starting from a base class called Node. These classes are

  • Camera, which represents an observation point within the scene
  • Mesh, defines an object consisting of a set of triangles with associated material properties
  • Sprite3D, defines an object consisting of a 2D image that can occupy any position within the 3D space. The image is always oriented with the face in the direction of the observation point.
  • Light, defines the position, orientation, color and attributes of a light source
  • Group, serves as a container to create a collection of nodes

Each node defines an own system of local coordinates and it can be transformed in relation to that. Already this concept clearly shows the usefulness of having a tree structure for storing objects.

For example, using a Group it is possible to organize a series of objects that can move in space with a simple transformation of the coordinates of this parent object.

There is also a special class derived from Node which is the World class. This represents the root of any 3D world you want to create.

In Figure 1 is an example of the structure of a 3D world. Notice how through such a structure, to render a scene it is enough to pass the World object to the Render() method of the Graphics3D class as an argument .

Figure 1 – The outline of the tree structure that will constitute the scene

Let’s now take a look at the main differences between the two modes. For this purpose is shown below a summarizing table which compares the calls to methods

ImmediateRetained
RoomSet the current camera

Graphics3D.setCamera(Camera camera, Transform transform)
Add one or more cameras in the world as a child node and activate one

World.addChild(Node child)
World.setActiveCamera(Room room)
LightSet a light source

Graphics3D.addLight(Light light, Transform transform)
Graphics3D.setLight(int index, Light, Transform transform)
Graphics3D.resetLights()
Adds a light source to the world as child node

World.addChild(Node child)
TransformationPass a transformation object to the render function

Graphics3D.render(Node node, Tranform transform)
Set a transformation to a particular node of the tree that makes up the world 

Node.setTransform (Transform transform)
RenderingRender a scene 

Graphics3D.render(Node node, Transform transform)
Graphics3D.render(VertexBuffer vertices, IndexBuffer triangles, Appearance appearance, Transform transform, int scope)
Graphics3D.render(VertexBuffer vertices, IndexBuffer triangles, Appearance appearance, Transform transform) 
Render the world including all child nodes 

Graphics3D.render (World world)

Immediate Mode and Retained Mode are not mutually exclusive; in fact it is always possible to mix them by properly using the Render() methods.

Finally bit of code

Now put into practice the concepts just seen using part of the code of the examples proposed in the previous articles. It is time to create the scene according to the structure in Figure 1.

First of all, within the Init() method of the GameCanvas class it is necessary to add all the instructions that generate the scene in the World object to be rendered. The method code is reported in full in Listing 1 .

// Create a new node of World type
world = new World();

// Create a new camera with perspective correction
Camera camera = new Camera();
float aspectRatio = (float) getWidth() / (float) getHeight();
camera.setPerspective(30.0f, aspectRatio, 1.0f, 1000.0f);

// Reposition the camera
camera.setTranslation(0.0f, 0.0f, 10.0f);

// Adds the camera as a child of the World node
world.addChild(camera);

// Activate the camera
world.setActiveCamera(camera);

// Create a new OMNI light source
Light light = new Light();
light.setMode(Light.OMNI);

// Reposition the light source
light.setTranslation(0.0f, 0.0f, 3.0f);

// Adds the light source as a child of the World node
world.addChild(light);

// Create a new VertexBuffer to store the vertices of the Blue cube
VertexBuffer blueCubeVerticesData = new VertexBuffer();

// Set vertices color to Blue
blueCubeVerticesData.setDefaultColor(0x000000FF); 

// Create a new VertexBuffer to store the vertices of the Red cube
VertexBuffer redCubeVerticesData = new VertexBuffer();

// Set vertices color to Red
redCubeVerticesData.setDefaultColor(0x00FF0000);  

// Create the VertexArray that will contain the coordinates of the cube's vertices
VertexArray verticesPositions = new VertexArray(VERTICES_POSITIONS.length/3, 3, 1);

// Stores the data in the VertexArray
verticesPositions.set(0, VERTICES_POSITIONS.length/3, VERTICES_POSITIONS);

// Set vertex data on each of the two VertexBuffers
blueCubeVerticesData.setPositions(verticesPositions, 1.0f, null);
redCubeVerticesData.setPositions(verticesPositions, 1.0f, null);

// Create a new VertexArray to store the normals data
VertexArray verticesNormals = new VertexArray(VERTICES_NORMALS.length/3, 3, 1);

// Store the normals data
verticesNormals.set(0, VERTICES_NORMALS.length/3, VERTICES_NORMALS);

// Inserts the normals' data in the VertexBuffer of the two cubes
blueCubeVerticesData.setNormals(verticesNormals);
redCubeVerticesData.setNormals(verticesNormals);

// Create the chain of triangles that make up the cube
TriangleStripArray cubeTriangles = new TriangleStripArray(TRIANGLES_INDICES, TRIANGLES_LENGHTS);

// Creo un nuovo materiale
Material material = new Material();
material.setVertexColorTrackingEnable(true);

// Create a new material
Appearance appearance = new Appearance();
appearance.setMaterial(material);

// Creates the hierarchical structure of objects 
// in the scene through a general group to which 
// the two subgroups of cubes will be linked
Group allCubes = new Group();
allCubes.setUserID(ID_ALL_CUBES);

// Adds the group as a child of the World object
world.addChild(allCubes);

// Create the sub-group of Blue cubes
Group blueCubes = new Group();
blueCubes.setUserID(ID_BLUE_CUBES);

// Snap the subgroup as a child node of the allCubes group
allCubes.addChild(blueCubes);

// Create the subgroup of the Red cubes
Group redCubes = new Group();
redCubes.setUserID(ID_RED_CUBES);

// Snap the subgroup as a child node of the All Cubes group
allCubes.addChild(redCubes);

// Create the mesh nodes of the cubes arranged in a circle
for (int i=0; i<8; i++)
{
    Mesh cubeMesh = null;

    // Alternatively generate a Blue and a Red cube
    if ((i%2) == 0)
    {
        // Create a new Mesh object with the settings of the Blue cubes
        cubeMesh = new Mesh(blueCubeVerticesData, cubeTriangles, appearance);
        
        // Snap the Mesh object as a child node of the blueCubes subgroup
        blueCubes.addChild(cubeMesh);
    }
    else
    {
        // Create a new Mesh object with the settings of the Red cubes
        cubeMesh = new Mesh(redCubeVerticesData, cubeTriangles, appearance);
        
        // Snap the Mesh object as a child of the redCubes subgroup
        redCubes.addChild(cubeMesh);
    }

    // Apply the appropriate transformations to the newly created cube
    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);                                     
}

First we create a new World node

world = new World();

let us now proceed by adding a camera to the scene, also setting the perspective correction

Camera room = new Camera();

float aspectRatio = (float) getWidth() / (float) getHeight();
camera.setPerspective(30.0f, aspectRatio, 1.0f, 1000.0f);

it is necessary to apply a translation transformation to the camera to move it away from the center of the scene. In this manner its possible to see all the objects that will make it up

camera.setTranslation(0.0f, 0.0f, 10.0f );

the camera must be added to the scene as a child node of the world

world.addChild(room);

after this operation, through the next call, this camera becomes the active one. I want to remember you that an active camera determines the position and orientation of the observation point.

world.setActiveCamera(room);

We also add an OMNI light source

Light light = new Light();
light.setMode(Light.OMNI);

I also apply a translation to the light source, moving it away from the center of the scene, in the direction of the Z axis.

light.setTranslation(0.0f, 0.0f, 3.0f);

Even the newly created light source becomes a node hooked directly to the root World

world.addChild(light);

Now let’s proceed with creating the hierarchical structure of the various groups that will contain two series of cubes, oriented in a circle with respect to the center of the scene. The first group that is created will contain two subgroups; one for a series of Blue cubes, while the other for the Red cubes.

Group allCubes = new Group();

Now associate an ID to the main group. The ID is necessary to be able to later retrieve the instance of a particular node object.

allCubes.setUserID(ID_ALL_CUBES);

like any other object, the main group must be added to the scene structure 

world.addChild(allCubes);

We create a subgroup, to which all blue cubes will be attached, and we associate a specific ID to it

Group blueCubes = new Group();
blueCubes.setUserID(ID_BLUE_CUBES);

at this point the newly created group must become a child node of the main group

allCubes.addChild(blueCubes);

we carry out the same steps with the group for the red cubes

Group redCubes = new Group();

redCubes.setUserID(ID_CUBI_ROSSI);
allCubes.addChild(redCubes);

It’s time to create cube objects. The procedure is identical to that followed in the previous articles; that is, the usual VertexBuffer object is created – in this case for the blue cube

VertexBuffer bluCubeVerticesData = new VertexBuffer();

the following statement allows you to specify a default color for all specified vertices

blueCubeVerticesData.setDefaultColor(0x000000FF); 

we also create the VertexBuffer for the data of the red cube

VertexBuffer redCubeVerticesData = new VertexBuffer();

also in this case we set the default color for the vertices 

redCubeVerticesData.setDefaultColor(0x00FF0000);  

Through the next two lines a VertexArray is created and in it the vertex values ​​are stored

VertexArray positionsVertices = new VertexArray (VERTICES_POSITIONS.length / 3, 3, 1);

verticesPositions.set(0, VERTICES_POSITIONS.length / 3, VERTICES_POSITIONS);

at this point the data contained in the array are passed to the VertexBuffers of the two cubes

blueCubeVerticesData.setPositions(verticesPositions, 1.0f, null);
redCubeVerticesData.setPositions(verticesPositions, 1.0f, null);

In the same way it is necessary to load the data of the normals of each vertex into a VertexArray

VertexArray verticesNormals = new VertexArray (VERTICES_NORMALS.length / 3, 3, 1);

verticesNormals.set(0, VERTICES_NORMALS.length / 3, VERTICES_NORMALS);

and pass them to the respective VertexBuffers 

blueCubeVerticesData.setNormals(verticesNormals);
redCubeVerticesData.setNormals(verticesNormals);

create a TriangleStrip for the triangles that make up the cube

TriangleStripArray cubeTriangles = new TriangleStripArray(TRIANGLES_INDICES, TRIANGLES_LENGTHS);

when activating the lighting it become mandatory to create an Appearance object to set the characteristics of the material that reflects the light

Material material = new Material();
material.setVertexColorTrackingEnable(true);

Appearance appearance = new Appearance();
appearance.setMaterial(material);

finally we create a certain number of cubes in a circle around the center of the scene. Each cube is a Mesh object that is added to the structure in its subgroup, depending on whether it is a blue cube

cubeMesh = new Mesh(blueCubeVerticesData, cubeTriangles, appearance);
blueCubes.addChild(meshCubo);

or red

cubeMesh = new Mesh(redCubeVerticesData, cubeTriangles, appearance);
redCubes.addChild(cubeMesh); 

Running the program we would get an image like the one in Figure 2, but we simply made a static scene. Let’s try to apply some transformations to the various groups of objects to see what happens.

Figure 2 – The example in execution with a rotation applied to the main group

To get this result we insert the following lines of code in the frame() method of the GameCanvas class

Group allCubes = (Group) world.find(ID_ALL_CUBES);

This finds the reference to the Group object that represents the main group, searching for it using the ID we previously assigned to it.

allCubes.postRotate(1.0f, 0.0f, 1.0f, 0.0f ); 

Which applies the transformation to the main group. By running the program we would see the whole set of cubes rotated around the Y axis, while adding the following lines

Group blueCubes = (Group) world.find(ID_BLUE_CUBES);
blueCubes.postRotate(1.0f, 0.0f, 0.0f, 1.0f );

Group redCubes = (Group) world.find(ID_RED_CUBES);
redCubes.postRotate(-1.0f, 1.0f, 0.0f, 0 .0f );

we would see the cubes rotate according to the rotation of the group of membership, as shown in Figure 3 .

Figure 3 – The example in execution where a rotation is applied also to the two subgroups of colored cubes

Conclusions

With a simple example we were able to experiment the facilities offered by the Retained Mode of M3Gs. Obviously I suggest to always refer to the documentation for any further information on the subject.

The documentation is available on the FTP site of Infomedia in the file associated with this number.

Bibliography

  1. Sun Corporation, “Mobile 3D Graphics API Specification “, www.sun.com

Original article

Downloads

Leave a Reply

Your email address will not be published. Required fields are marked *