Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

3D graphics for Java mobile devices, Part 1: M3G's immediate mode

Create 3D scenes with JSR 184


Return to article


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

package m3gsamples1;

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

/**
 * Sample displaying a cube that can be transformed.
 *
 * @author Claus Hoefele
 */
public class TransformationsSample extends Canvas implements Sample
{
  /** 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,
    -1, -1, -1,    1, -1, -1,   -1,  1, -1,    1,  1, -1
  };

  /** The cube"s vertex colors (R, G, B). */
  private static final byte[] VERTEX_COLORS = {
    0, 0, (byte) 128,             0, 0, (byte) 255,
    0, (byte) 255, 0,             0, (byte) 255, (byte) 255,
    (byte) 255, 0, 0,             (byte) 255, 0, (byte) 255,
    (byte) 255, (byte) 255, 0,    (byte) 255, (byte) 255, (byte) 255
  };

  /** Indices that define how to connect the vertices to build 
   * triangles. */
  private static int[] TRIANGLE_INDICES = {
    0, 1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1
  };

  /** The cube"s vertex data. */
  private VertexBuffer _cubeVertexData;

  /** The cube"s triangles defined as triangle strips. */
  private TriangleStripArray _cubeTriangles;

  /** The cube"s transformation. */
  private Transform _cubeTransform;

  /** Transform using x-axis. */
  private static final int TRANSFORMATION_X_AXIS    = 0;
  /** Transform using y-axis. */
  private static final int TRANSFORMATION_Y_AXIS    = 1;
  /** Transform using z-axis. */
  private static final int TRANSFORMATION_Z_AXIS    = 2;

  /** Rotate. */
  private static final int TRANSFORMATION_ROTATE    = 0;
  /** Translate. */
  private static final int TRANSFORMATION_TRANSLATE = 1;
  /** Scale. */
  private static final int TRANSFORMATION_SCALE     = 2;

  /** Current transformation. */
  private int _transformation = TRANSFORMATION_ROTATE;

  /** Graphics singleton used for rendering. */
  private Graphics3D _graphics3d;

  /**
   * Called when this sample is displayed.
   */
  public void showNotify()
  {
    init();
  }

  /**
   * Initializes the sample.
   */
  protected void init()
  {
    // Get the singleton for 3D rendering.
    _graphics3d = Graphics3D.getInstance();

    // Create vertex data.
    _cubeVertexData = new VertexBuffer();

    VertexArray vertexPositions =
        new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
    vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
    _cubeVertexData.setPositions(vertexPositions, 1.0f, null);

    VertexArray vertexColors =
        new VertexArray(VERTEX_COLORS.length/3, 3, 1);
    vertexColors.set(0, VERTEX_COLORS.length/3, VERTEX_COLORS);
    _cubeVertexData.setColors(vertexColors);

    // Create the triangles that define the cube; the indices point to
    // vertices in VERTEX_POSITIONS.
    _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
        new int[] {TRIANGLE_INDICES.length});

    // 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);
    Transform cameraTransform = new Transform();
    cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
    _graphics3d.setCamera(camera, cameraTransform);

    // Reset the cube"s transformations.
    _cubeTransform = new Transform();
    _transformation = TRANSFORMATION_ROTATE;
  }

  /**
   * Renders the sample on the screen.
   *
   * @param graphics the graphics object to draw on.
   */
  protected void paint(Graphics graphics)
  {
    _graphics3d.bindTarget(graphics);
    _graphics3d.clear(null);
    _graphics3d.render(_cubeVertexData, _cubeTriangles,
        new Appearance(), _cubeTransform);
    _graphics3d.releaseTarget();

    drawMenu(graphics);
  }

  /**
   * Draws a menu on the screen.
   *
   * @param graphics graphics context.
   */
  protected void drawMenu(Graphics graphics)
  {
    graphics.setColor(0xFFFFFFFF);
    int fontHeight = graphics.getFont().getHeight();

    // Upper half menu.
    int height = 0;
    graphics.drawString(getKeyName(getKeyCode(FIRE)) + ": Reset", 0,
        height, 0);
    height += fontHeight;
    graphics.drawString(
        getKeyName(getKeyCode(UP)) + "/" +
        getKeyName(getKeyCode(DOWN)) + ": x " +
        getKeyName(getKeyCode(LEFT)) + "/" +
        getKeyName(getKeyCode(RIGHT)) + ": y " +
        getKeyName(getKeyCode(GAME_A)) + "/" +
        getKeyName(getKeyCode(GAME_B)) + ": z ", 0, height, 0);

    // Lower half menu.
    height = getHeight() - fontHeight;
    if (_transformation == TRANSFORMATION_ROTATE)
    {
      graphics.drawString(getKeyName(getKeyCode(GAME_C)) +
          ": Transformation: rotate", 0, height, 0);
    }
    else if (_transformation == TRANSFORMATION_TRANSLATE)
    {
      graphics.drawString(getKeyName(getKeyCode(GAME_C)) +
          ": Transformation: translate", 0, height, 0);
    }
    else if (_transformation == TRANSFORMATION_SCALE)
    {
      graphics.drawString(getKeyName(getKeyCode(GAME_C)) +
          ": Transformation: scale", 0, height, 0);
    }
  }

  /**
   * Handles key presses.
   *
   * @param keyCode key code.
   */
  protected void keyPressed(int keyCode)
  {
    switch (getGameAction(keyCode))
    {
      case UP:
        transform(_transformation, TRANSFORMATION_X_AXIS, false);
        break;

      case DOWN:
        transform(_transformation, TRANSFORMATION_X_AXIS, true);
        break;

      case LEFT:
        transform(_transformation, TRANSFORMATION_Y_AXIS, false);
        break;

      case RIGHT:
        transform(_transformation, TRANSFORMATION_Y_AXIS, true);
        break;

      case GAME_A:
        transform(_transformation, TRANSFORMATION_Z_AXIS, false);
        break;

      case GAME_B:
        transform(_transformation, TRANSFORMATION_Z_AXIS, true);
        break;

      case FIRE:
        init();
        break;

      case GAME_C:
        _transformation++;
        _transformation %= 3;
        break;

      // no default
    }

    repaint();
  }

  /**
   * Transforms the cube with the given parameters.
   *
   * @param transformation transformation (rotate, translate, scale)
   * @param axis axis of translation (x, y, z)
   * @param positiveDirection true for increase, false for decreasing 
   *                          value.
   */
  protected void transform(int transformation, int axis,
      boolean positiveDirection)
  {
    if (transformation == TRANSFORMATION_ROTATE)
    {
      float amount = 10.0f * (positiveDirection ? 1 : -1);

      switch (axis)
      {
        case TRANSFORMATION_X_AXIS:
          _cubeTransform.postRotate(amount, 1.0f, 0.0f, 0.0f);
          break;

        case TRANSFORMATION_Y_AXIS:
          _cubeTransform.postRotate(amount, 0.0f, 1.0f, 0.0f);
          break;

        case TRANSFORMATION_Z_AXIS:
          _cubeTransform.postRotate(amount, 0.0f, 0.0f, 1.0f);
          break;

        // no default
      }
    }
    else if (transformation == TRANSFORMATION_SCALE)
    {
      float amount = positiveDirection ? 1.2f : 0.8f;

      switch (axis)
      {
        case TRANSFORMATION_X_AXIS:
          _cubeTransform.postScale(amount, 1.0f, 1.0f);
          break;

        case TRANSFORMATION_Y_AXIS:
          _cubeTransform.postScale(1.0f, amount, 1.0f);
          break;

        case TRANSFORMATION_Z_AXIS:
          _cubeTransform.postScale(1.0f, 1.0f, amount);
          break;

        // no default
      }
    }
    else if (transformation == TRANSFORMATION_TRANSLATE)
    {
      float amount = 0.2f * (positiveDirection ? 1 : -1);

      switch (axis)
      {
        case TRANSFORMATION_X_AXIS:
          _cubeTransform.postTranslate(amount, 0.0f, 0.0f);
          break;

        case TRANSFORMATION_Y_AXIS:
          _cubeTransform.postTranslate(0.0f, amount, 0.0f);
          break;

        case TRANSFORMATION_Z_AXIS:
          _cubeTransform.postTranslate(0.0f, 0.0f, amount);
          break;

        // no default
      }
    }
  }

  /**
   * Handles key repeat events for phones that support them. Redirects
   * all key input to <code>keyPressed()</code>.
   *
   * @param keyCode key code.
   */
  protected void keyRepeated(int keyCode)
  {
    keyPressed(keyCode);
  }

  /**
   * 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 "Transformations";
  }
}

Return to article