Skip to main content

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