Skip to main content

3D graphics for Java mobile devices, Part 2: M3G's retained mode

Easily manage 3D objects in scene graphs with JSR 184


Return to article


/*
 * Sample code for M3G article on IBM developerWorks.
 * http://www.ibm.com/developerworks/
 */

package m3gsamples2;

import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;

/**
 * Uses a scene graph tree to structure a hierarchy of 3D objects.
 *
 * @author Claus Hoefele
 */
public class SceneGraphSample extends Canvas implements Sample, Runnable
{
  /** The cube's vertex positions (x, y, z). */
  private static final byte[] VERTEX_POSITIONS = {
    -1, -1,  1,    1, -1,  1,   -1,  1,  1,    1,  1,  1, // front
     1, -1, -1,   -1, -1, -1,    1,  1, -1,   -1,  1, -1, // back
     1, -1,  1,    1, -1, -1,    1,  1,  1,    1,  1, -1, // right
    -1, -1, -1,   -1, -1,  1,   -1,  1, -1,   -1,  1,  1, // left
    -1,  1,  1,    1,  1,  1,   -1,  1, -1,    1,  1, -1, // top
    -1, -1, -1,    1, -1, -1,   -1, -1,  1,    1, -1,  1  // bottom
  };

  /** The cube's normals. */
  private static final byte[] VERTEX_NORMALS = {
    0, 0,  127,   0, 0,  127,   0, 0,  127,   0, 0,  127,   // front
    0, 0, -128,   0, 0, -128,   0, 0, -128,   0, 0, -128,   // back
     127, 0, 0,    127, 0, 0,    127, 0, 0,    127, 0, 0,   // right
    -128, 0, 0,   -128, 0, 0,   -128, 0, 0,   -128, 0, 0,   // left
    0,  127, 0,   0,  127, 0,   0,  127, 0,   0,  127, 0,   // top
    0, -128, 0,   0, -128, 0,   0, -128, 0,   0, -128, 0,   // bottom
  };

  /** Indices that define how to connect the vertices to build 
   * triangles. */
  private static final int[] TRIANGLE_INDICES = {
     0,  1,  2,  3,   // front
     4,  5,  6,  7,   // back
     8,  9, 10, 11,   // right
    12, 13, 14, 15,   // left
    16, 17, 18, 19,   // top
    20, 21, 22, 23    // bottom
  };

  /** Lengths of triangle strips in TRIANGLE_INDICES. */
  private static int[] TRIANGLE_LENGTHS = {
    4, 4, 4, 4, 4, 4
  };
  
  /** User ID for all meshes (1).*/
  private static final int USER_ID_ALL_MESHES  = 1;
  /** User ID for blue meshes (2).*/
  private static final int USER_ID_BLUE_MESHES = 2;
  /** User ID for red meshes (3).*/
  private static final int USER_ID_RED_MESHES  = 3;
  
  /** Number of frames used for one direction of the animation.*/
  private static int COUNTER_MAX = 20;
  /** Frame counter. */
  private int _counter = COUNTER_MAX/2;
  /** Scaling factor per frame.*/
  private float _scale = 0.02f;
  
  /** Object that represents the 3D world. */
  private World _world;
  
  /** Flag for stopping the Thread.*/
  private boolean _isRunning;
  
  /** Graphics singleton used for rendering. */
  private Graphics3D _graphics3d;

  /**
   * When the sample is shown, it's initialized and the animation
   * thread started.
   */
  public void showNotify()
  {
    init();
    
    Thread thread = new Thread(this);
    _isRunning = true;
    thread.start();
  }
  
  /**
   * Stops the animation thread.
   */
  public void hideNotify()
  {
    _isRunning = false;
  }

  /**
   * Initializes the sample.
   */
  protected void init()
  {
    // Get the singleton for 3D rendering and a World.
    _graphics3d = Graphics3D.getInstance();
    _world = new World();
    
    // Create a camera with perspective projection.
    Camera camera = new Camera();
    float aspect = (float) getWidth() / (float) getHeight();
    camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
    camera.setTranslation(0.0f, 0.0f, 10.0f);
    _world.addChild(camera);
    _world.setActiveCamera(camera);
    
    // Create lights.
    Light light = new Light();
    light.setMode(Light.OMNI);
    light.setTranslation(0.0f, 0.0f, 3.0f);
    _world.addChild(light);
    
    // Create two sets of vertex data: one for blue meshes and one for red
    // meshes.
    VertexBuffer blueCubeVertexData = new VertexBuffer();
    blueCubeVertexData.setDefaultColor(0x000000FF); // blue
    VertexBuffer redCubeVertexData = new VertexBuffer();
    redCubeVertexData.setDefaultColor(0x00FF0000);  // red

    VertexArray vertexPositions = 
        new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
    vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
    blueCubeVertexData.setPositions(vertexPositions, 1.0f, null);
    redCubeVertexData.setPositions(vertexPositions, 1.0f, null);
    
    VertexArray vertexNormals = 
        new VertexArray(VERTEX_NORMALS.length/3, 3, 1);
    vertexNormals.set(0, VERTEX_NORMALS.length/3, VERTEX_NORMALS);
    blueCubeVertexData.setNormals(vertexNormals);
    redCubeVertexData.setNormals(vertexNormals);

    // Create the triangles that define the cube; the indices point to
    // vertices in VERTEX_POSITIONS.
    TriangleStripArray cubeTriangles = new TriangleStripArray(
        TRIANGLE_INDICES, TRIANGLE_LENGTHS);
    
    // Create material.
    Material material = new Material();
    material.setVertexColorTrackingEnable(true);
    Appearance appearance = new Appearance();
    appearance.setMaterial(material);

    // Create groups to organize the cubes.
    Group allMeshes = new Group();
    allMeshes.setUserID(USER_ID_ALL_MESHES);
    _world.addChild(allMeshes);
    Group blueMeshes = new Group();
    blueMeshes.setUserID(USER_ID_BLUE_MESHES);
    allMeshes.addChild(blueMeshes);
    Group redMeshes = new Group();
    redMeshes.setUserID(USER_ID_RED_MESHES);
    allMeshes.addChild(redMeshes);
        
    // Create eight cubes in a circle.
    for (int i=0; i<8; i++)
    {
      Mesh cubeMesh = null;
      
      if ((i%2) == 0)
      {
        cubeMesh = new Mesh(blueCubeVertexData, cubeTriangles, appearance);
        blueMeshes.addChild(cubeMesh);
      }
      else
      {
        cubeMesh = new Mesh(redCubeVertexData, cubeTriangles, appearance);
        redMeshes.addChild(cubeMesh);
      }

      cubeMesh.setTranslation(1.5f * (float) Math.cos(i*Math.PI/4), 1.5f * (float) Math.sin(i*Math.PI/4), 0.0f);
      cubeMesh.setScale(0.4f, 0.4f, 0.4f);
    }
  }
  
  /**
   * Drives the animation.
   */
  public void run()
  {
    while(_isRunning)
    {
      // Rotate all meshes.
      Group allMeshes = (Group) _world.find(USER_ID_ALL_MESHES);
      allMeshes.postRotate(2, 0.0f, 0.0f, 1.0f);
      
      _counter++;
      if ((_counter%COUNTER_MAX) == 0)
      {
        _scale *= -1;
      }

      // Scale blue meshes.
      Group blueMeshes = (Group) _world.find(USER_ID_BLUE_MESHES);
      blueMeshes.scale(1 + _scale, 1 + _scale, 1 + _scale);
      
      // Scale red meshes.
      Group redMeshes = (Group) _world.find(USER_ID_RED_MESHES);
      redMeshes.scale(1 - _scale, 1 - _scale, 1 - _scale);
      
      repaint();
      
      try
      {
        Thread.sleep(50);
      }
      catch (Exception e){}
    }
  }
  
  /**
   * Renders the sample on the screen.
   *
   * @param graphics the graphics object to draw on.
   */
  protected void paint(Graphics graphics)
  {
    _graphics3d.bindTarget(graphics);
    _graphics3d.render(_world);
    _graphics3d.releaseTarget();
  }

  /**
   * Returns the <code>Displayable</code> used to display this sample.
   *
   * @return display
   */
  public Displayable getDisplayable()
  {
    return this;
  }

  /**
   * Returns the display name of this sample.
   *
   * @return name
   */
  public String getName()
  {
    return "Scene Graph";
  }
}


Return to article