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.*;

/**
 * Alignment of nodes and picking.
 *
 * @author Claus Hoefele
 */
public class AlignmentPickingSample extends Canvas implements Sample
{
  /** Name of the M3G file. */
  private static final String M3G_FILE_NAME = "/blender.m3g";
  
  /** User ID to identify the text mesh (1).*/
  private static final int USER_ID_TEXT = 1;
  
  /** Crosshair image file name (off).*/
  private static String CROSS_HAIR_OFF = "/crosshair_off.png";
  /** Crosshair image file name (on).*/
  private static String CROSS_HAIR_ON  = "/crosshair_on.png";
  
  /** State of crosshair.*/
  private boolean _crossHairOn;
  
  /** Image object for crosshair (on).*/
  private Image _crossHairImageOn;
  /** Image object for crosshair (off).*/
  private Image _crossHairImageOff;
  
  /** Object that represents the 3D world. */
  private World _world;
  
  /** 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();

    try
    {
      // Load World from M3G binary file.
      Object3D[] objects = Loader.load(M3G_FILE_NAME);
      _world = (World) objects[0];
      
      // Change the camera's properties to match the current device.
      Camera camera = _world.getActiveCamera();
      float aspect = (float) getWidth() / (float) getHeight();
      camera.setPerspective(60.0f, aspect, 1.0f, 1000.0f);

      // Align the text with the camera.
      Mesh text = (Mesh) _world.find(USER_ID_TEXT);
      text.setAlignment(camera, Node.Y_AXIS, null, Node.NONE);
      text.align(null);
      
      // Load crosshair images.
      try
      {
        _crossHairImageOn = Image.createImage(CROSS_HAIR_ON);
        _crossHairImageOff = Image.createImage(CROSS_HAIR_OFF);
      }
      catch (Exception e) {}
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
  
  /**
   * 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();
    
    // Draw crosshair.
    if ((_crossHairImageOff != null) && (_crossHairImageOn != null))
    {
      graphics.drawImage(_crossHairOn ? _crossHairImageOn : _crossHairImageOff, 
              getWidth()/2, getHeight()/2, Graphics.VCENTER | Graphics.HCENTER);
    }

    drawMenu(graphics);
  }
  
  /**
   * Draws a menu on the screen.
   *
   * @param graphics graphics context.
   */
  private void drawMenu(Graphics graphics)
  {
    graphics.setColor(0xFFFFFFFF);
    int fontHeight = graphics.getFont().getHeight();
    int height = getHeight() - fontHeight;
    graphics.drawString(getKeyName(getKeyCode(LEFT)) + ": Rotate left", 0, 
            height, 0);
    height -= fontHeight;
    graphics.drawString(getKeyName(getKeyCode(RIGHT)) + ": Rotate right", 0, 
            height, 0);
    height -= fontHeight;
    graphics.drawString(getKeyName(getKeyCode(FIRE)) + ": Reset", 0, 
            height, 0);
  }
  
  /**
   * 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);
  }

  /**
   * Handles key presses.
   *
   * @param keyCode key code.
   */
  protected void keyPressed(int keyCode)
  {
    Mesh text = (Mesh) _world.find(USER_ID_TEXT);
    
    switch (getGameAction(keyCode))
    {
      case LEFT:
        text.postRotate(2.0f, 0.0f, 0.0f, 1.0f);
        break;

      case RIGHT:
        text.postRotate(-2.0f, 0.0f, 0.0f, 1.0f);
        break;

      case FIRE:
        init();
        break;

      // no default
    }

    // Check whether ray cast by crosshair intersects with the text.
    _crossHairOn = isHit();
    
    repaint();
  }
  
  /**
   * Checks whether a ray that orginates in the middle of the viewport and has 
   * the same direction as the active camera intersects with the text.
   * 
   * @return true if there's an intersection.
   */
  private boolean isHit()
  {
    boolean isHit = false;
    RayIntersection rayIntersection = new RayIntersection();
    Mesh mesh = (Mesh) _world.find(USER_ID_TEXT);
    
    if (_world.pick(-1, 0.5f, 0.5f, _world.getActiveCamera(), rayIntersection))
    {
      if (rayIntersection.getIntersected() == mesh)
      {
        isHit = true;
      }
    }
    
    return isHit;
  }
  
  /**
   * 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 "Alignment and Picking";
  }
}


Return to article