服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

3d编程指南第一部分:快速进入移动java 3d编程世界

3d编程指南第一部分:快速进入移动java 3d编程世界

下面你可以下载源程序和应用程序包的zip文件,并且继续看该指南。

l         源代码(类和资源)
http://developer.sonyericsson.com/getdocument.do?docid=73849

l         应用程序包(jar/jad)
http://developer.sonyericsson.com/getdocument.do?docid=73850

l         关于redikod的更多信息
http://www.redikod.com/

 

绪论

 

在一开始,我希望你知道网络上的几个链接,这些网站有助于你进入m3g领域。

 

首先,或许是最主要的是在索尼爱立信开发者世界的移动java 3d部分;其次,如果你曾经困惑,那就去索尼爱立信java手机3d论坛。在索尼爱立信开发网,你将找到你问题的答案以及其他信息。

 

既然你已经知道遇到问题时应该去那里,让我继续指南的讲解。这个讲解的目的是教你怎么设置你自己的3d画布和在屏幕上进行填充。为了填充模型,我首先将向你说明如何装载它们,并告诉你创建m3g模型用到的工具。然后我们将巧妙地处理这台照相机,为了使我们能围绕我们的场景行走。我要让你在座位上感到兴奋和了解如何用m3g快速的开发一个3d应用程序。因此,这个讲解是相当快速和直截了当的,几乎没有复杂的解释。这个指南的其他部分将分别探索m3g的各个主题。

 

以教育为目的的最佳讲授方法不是讲解代码,它不能包括你可能遇到的所有错误当你编写3d程序的时候。在以后,更多的高级主题将被讲解。

 

预备知识

 

在你开始阅读这之前,你应该知道midlet类和canvas类。这不是一个复杂的主题,如果你不清楚,请参考源代码(分布在指南中)并看看m3gmidlet和m3gcanvas类。你最好有一些3d编程和3d算术基础,但这不是必须的。

 

画布

 

当我们开发jsr 184程序时,我们将使用midp 2.0规范。这以为着我们得到了几个重要的函数。让我们开始设定我们的画布。这与普通的2d java策略是相同的,你设定你的midlet类,你启动你的canvas,并用paint方法在你的画布上绘画。这是一个相当容易的过程,所以你应该知道我之前为何快速撇去它了。首先让我们看一看画布类,导入和变量声明。

 

import javax.microedition.lcdui.graphics;
import javax.microedition.lcdui.game.gamecanvas;
import javax.microedition.m3g.camera;
import javax.microedition.m3g.graphics3d;
import javax.microedition.m3g.light;
import javax.microedition.m3g.loader;
import javax.microedition.m3g.object3d;
import javax.microedition.m3g.transform;
import javax.microedition.m3g.world;

/**
 *
 * @author  biovenger
 * @version
 */
public class m3gcanvas
extends gamecanvas
implements runnable {
    // thread-control
    boolean running = false;
    boolean done = true;
   
    // if the game should end
    public static boolean gameover = false;
   
    // rendering hints
    public static final int strong_rendering_hints = graphics3d.antialias | graphics3d.true_color | graphics3d.dither;
    public static final int weak_rendering_hints = 0;
    public static int rendering_hints = strong_rendering_hints;
   
    // key array
    boolean[] key = new boolean[5];
   
    // key constants
    public static final int fire = 0;
    public static final int up = fire + 1;
    public static final int down = up + 1;
    public static final int left = down + 1;
    public static final int right = left + 1;

 

这是个相当基础的填充,不过还是让我们迅速看看发生了什么。首先我们有很多导入,我们仅仅导入在该指南里所用到的所有类,并且我们能在jsr 184 api文档中找到它们。我们还定义了一些线程变量,例如:running和 done,这些是显而易见的。

 

现在,让我们看看绘制迹象(hints),这些“迹象”告诉移动设备在绘制的时候你想要的效果。然而,因为他们是迹象,它不能保证在移动设备上显示他们。这里我定义两个不同的迹象。弱和强,正如你看到的,强绘制迹象控制反混淆现象,真彩色和抖动。弱绘制控制没有迹象的情况,这基本上是你你能得到的最丑和最快的绘制效果。如同你从代码里看见的,迹象能由一些简单的逻辑或组合。我将谈论更多关于迹象在该指南以后的部分里。

 

其次我们有key数组,那是一个非常简单的数组用来保存被按下的键。如果你好奇键是如何处理的,看看这个例子的源代码。也就是说:如果up键被按下时,由if(key[up])询问发现。

 

m3g文件格式

jsr 184标准有自己的格式,称为m3g。这个非常通用的3d格式能保存大量数据,例如模型、灯光、照相机、纹理、甚至动画。非常好!非均匀是这个格式最大的好处,它能容易的装载入你的应用程序。无论如何,我打赌你在想“我从未听过m3g,又如何创建m3g文件呢?”你并不用考虑这个,我将作出说明。有许多方法创建m3g文件:

 

1、首先,最新的discreet's 3d studio max内建了m3g的导出接口。仅需点击导出按钮,你就能导出你的m3g文件里的所有场面、动画、骨架、材质等。然而,很多discreet's的导出有点麻烦并且有一些错误,因此最好使用方法2。

 

2、hicorp,同样是一个实现索尼爱立信 jsr 184的导出工具,非常强大的导出器,它可以利用三种最流行的3d建模程序,3d studio max, lightwave and maya。你可以从这里找到它。

(http://developer.sonyericsson.com/site/global/docstools/java/p_java.jsp)

 

3、blender,一个强大并且免费的3d建模工具,它也有m3g导出接口。然后,它的一些较早的版本仍然存在错误。点击这里查看blender。

(http://www.blender3d.org/cms/home.2.0.html)

 

如此,我们怎么装载这些强大的文件到我们的程序里呢?非常简单。jsr 184里有个名为loader的类,并且它正确的装载文件。调用一个简单的方法就可以装载一个m3g文件的所有引用,这个方法叫做loader.load,有两个不同的参数列表。一个是用string获得一个url,另一个是获得一个为加工的字节数组。这里是如何使用它的例子:

 

object3d[] objects = loader.load("file.m3g");

object3d[] objects2 = loader.load(bytearray, offset);

 

装载方法总是返回一个object3d数组,并且有它的一个非常好的原因。最好的是loader类能比m3g文件装载更多,基本上能保存object3d的所有子类。然而,你主要使用它来装载m3g文件。

 

现在,我创建一个简单的m3g文件,叫做map。我要绘制它,我将使用loader.load方法装载该文件,旧如你看见过的那样,返回一个object3d数组。我们不能使用object3d数组去绘制。因此我们需要转换它,使之成为我们能重画的场景。在这个指南里,我们将装载世界(world)结点。世界结点是jsr 184场面图表中最顶层的结点。它控制所有种类的信息,例如:照相机、照明设备、背景和许多网眼。我将讲诉场面图表和场面图表的jsr 184的实施在这个系列后面的部分,你现在只需要了解世界类能控制整个场面,并且知道什么是我们想要的!查看这个方法,它从一个m3g文件中装载世界结点。

 

/** loads our world */
    private void loadworld()
    {
        try
        {
            // loading the world is very simple. note that i like to use a
            // res-folder that i keep all files in. if you normally just put your
            // resources in the project root, then load it from the root.
            object3d[] buffer = loader.load("/res/map.m3g");
           
            // find the world node, best to do it the "safe" way
            for(int i = 0; i < buffer.length; i++)
            {
                if(buffer[i] instanceof world)
                {
                    world = (world)buffer[i];
                    break;
                }
            }
           
            // clean objects
            buffer = null;
        }
        catch(exception e)
        {
            // error!
            system.out.println("loading error!");
            reportexception(e);
        }
    }

 

正如你所见的,在我们用loader类装载object3d数组后,我们简单地审阅整个数组并找到世界结点。这是查找世界结点最安全的方法。在我们找到世界结点后,我们将跳出循环并清空缓冲(它是不需要的,因为当离开这个方法时,他们会自动的得到清楚,这是好的习惯)

 

好的,我们现在装载我们的世界结点,我已经告诉过你,它是场面图表的顶层结点并能控制所有场面信息。在我告诉你它的绘制是如何简单之前,先让我们提取照相机,为了我们能在装载的世界中移动。

 

处理照相机

 

我们有了我们的世界结点准备绘画,并且现在我们需要一个照相机,它能使我们围绕世界移动。如果你记得,我已经告诉过你,世界结点能控制照相机信息,因此我们应该从世界中提取照相机并操作它。

 

jsr 184的一个照相机是用camera类描述的。这个类能很容易的用一些简单的平移和定位方法来操作我们3d应用程序里的照相机。在这个例子里,我们将使用translate(float, float, float)和setorientation(float, float, float, float)方法。第一个方法是在3d空间里简单的移动照相机,偏移量为x,y,z。因此,如果你想让照相机在x和z轴方向上各移动3个单位。你可以这样做:

 

camera cam = new camera();  // this is our camera

//move camera   x  y  z
cam.translate(3.0f, 0.0f, 3.0f);

 

很简单的事!每个方法调用translate进一步调整照相机,因此上面两个调用实际上转变照相机在x和z轴都是6个单位。旋转也是一样的简单,我会首先说明这个方法。它操作像3d api的所有旋转方法一样。有个参数,第一个是实际的旋转度数,剩下的三个组成一个方向向量(xaxis, yaxis, zaxis)在周围旋转。方向和方向向量在后面的丛书将会讲到,现在仅仅需要知道这些:

 

//rotate camera 30 degrees around the x axis
cam.setorientation(30.0f, 1.0f, 0.0f, 0.0f);

//rotate camera 30 degrees around the y axis
cam.setorientation(30.0f, 0.0f, 1.0f, 0.0f);

//rotate camera 30 degrees around the z axis
cam.setorientation(30.0f, 0.0f, 0.0f, 1.0f);

 

注意名为setorientation的方法,它实际意味着清除所有先前你做的旋转。我假设你已经知道了怎么围绕一个坐标轴旋转,在这里并不会提到关于这个主题的更多细节。

 

你现在知道如何移动和旋转照相机。我将告诉你如何从世界中提取照相机。

 

/** loads our camera */
    private void loadcamera()
    {
        // bad!
        if(world == null)
            return;
       
        // get the active camera from the world
        cam = world.getactivecamera();
       
        // create a light
        light l = new light();
       
        // make sure it's ambient
        l.setmode(light.ambient);
       
        // we want a little higher intensity
        l.setintensity(3.0f);
       
        // add it to our world
        world.addchild(l);
    }

 

是这么简单吗?是的,就是这么简单。我们使用getactivecamera方法从世界中提取照相机。我们将得到世界导出时的哪个照相机。通过上面的方法,我们得到了一个照相机,我们可以把它移动成想要的效果。然而,这个方法还做了其他事,它添加了一个灯光!我们将在后面的部分钻研灯光,但在这里你只需知道如何在世界里添加一个灯光。我创建一个环境光(如果你不知道,环境光是光从所有方向照射所有表面)并添加它到世界里。这个方法让我们得到了一个非常好的有光的世界。我之前告诉过你,世界结点可以控制所有种类的信息,也包括灯光,因此,我们只需要将灯光添加入我们的世界,jsr 184将为我们处理。那不是很容易吗?在我们阅读最后一个部分之前,绘制,让我们的照相机移动。我已经告诉过你boolean数组,键值,控制我们的键信息,因此我们要询问这个数组并是照相机运转。首先,我们需要一些变量来控制我们的照相机。

 

// camera rotation
float camrot = 0.0f;
double camsine = 0.0f;
double camcosine = 0.0f;
   
// head bobbing
float headdeg = 0.0f;

 

我们将用上面的变量来控制照相机旋转的轨迹,三角函数和顶点振动。三角函数用于稍后的移动,顶点振动是十分简单的,它使照相机上下振动当我们在世界里行走时,为了一种更自然的感觉。好的,我们需要怎么样做才能移动照相机呢?使用下面的方法:

 

private void movecamera() {
        // check controls
        if(key[left])
        {
            camrot += 5.0f;
        }
        else if(key[right])
        {
            camrot -= 5.0f;
        }
       
        // set rotation
        cam.setorientation(camrot, 0.0f, 1.0f, 0.0f);
       
        // calculate trigonometry for camera movement
        double rads = math.toradians(camrot);
        camsine = math.sin(rads);
        camcosine = math.cos(rads);

 

如你所见,这部分方法是很简单的,首先我们检查用户是否按下左或右键,如果被按下,我们仅仅是增加或减少camrot的值,然后旋转照相机。那是如此的简单。余下的几行代码是有趣的,当用户按下左或右时,我们需要旋转顶点,我们是围绕y轴旋转的,这就意味着方向响亮为0.0f, 1.0f, 0.0f。当我们旋转了照相机后,我们要计算出新的sine和cosine角度,这是为以后的移动而计算的,现在我们看另一部分方法:

 

if(key[up])
        {
            // move forward
            cam.translate(-0.1f * (float)camsine, 0.0f, -0.1f * (float)camcosine);
           
            // bob head
            headdeg += 0.5f;
           
            // a simple way to "bob" the camera as the user moves
            cam.translate(0.0f, (float)math.sin(headdeg) / 40.0f, 0.0f);
        }
        else if(key[down])
        {
            // move backward
            cam.translate(0.1f * (float)camsine, 0.0f, 0.1f * (float)camcosine);
           
            // bob head
            headdeg -= 0.5f;
           
            // a simple way to "bob" the camera as the user moves
            cam.translate(0.0f, (float)math.sin(headdeg) / 40.0f, 0.0f);
        }

        // if the user presses the fire key, let's quit
        if(key[fire])
            m3gmidlet.die();
    }

 

这里我们检查up或down键,up键将移动照相机向前,down键将移动照相机向后。这是一个简单的平移,但我还是要做出简单的说明。照相机总是朝着负z轴方向的,因此要向前移动照相机,我们只需要在负z轴上移动它。然而,如果我们旋转照相机,我们不能只沿着z轴移动了,这样会导致错误。我们想在x轴上移动照相机,因此我们要得到我们希望的动作。这个就需要使用三角函数。这个指南不是讲解3d算术的,我将不会讲更多细节,毕竟你应该知道这些,如果你认为它是复杂的,那就在网络上找一个好的3d算术指南看看吧。

 

在每次平移后,我们总是要移动顶点用我简单的顶点振动。我只提供在y轴上转化方法用sine函数,因此它看上去是顶点上下移动,这也是为什么在照相机每次移动时我增加或减少headdeg变量。在代码的末端,我们同样的检查fire键,是为了能让用户随时退出程序的。(它能使用在画布创建是我添加的无形的exit命令)。

 

好的,这是我们所有高级照相机动作,现在让我们了解如何绘制世界结点。

 

绘制

在编码之前,我将告诉你关于立即保留模式绘制。在我们的指南中,保留模式是一种基本模式,你可以用来绘制整个世界结点和所有照相机、灯光和网眼。这是最简单的绘制模式,不过你还是需要控制你的世界。立即模式是直接绘制组中的网眼和顶点数据。这个给你更多的控制权,每个绘画都提供一个变换矩阵,在重绘前变换这个对象。在立即模式,你可以绘制一个世界结点,提供一个转化矩阵让绘制方法调用,不过你得放弃世界结点的漂亮的效果,例如照相机、背景等。在后面的丛书里,我将详细介绍两种绘制模式的不同。现在,让我们看看如何绘制一个世界。

 

graphics3d

 

jsr 184的所有绘制都是靠graphics3d对象完成的。如果你在立即模式下绘制,它能控制照相机和灯光信息。现在我们不用担心,我在后面讲会讲解到这些。

 

要用graphics3d对象绘制,首先你必须绑定它给一个图形上下文。一个图形上下文基本上意味着所有能画的图形对象。如果你想绘制一张图片,它能成为一个图片的图形对象,或者它也可以通过getgraphics()方法获取一个主图形对象。使用主图形对象,你可以直接绘制你想要的场景。获得一个graphics3d对象是简单的,你只需要调用graphics3d.getinstance()方法。每个midlet只能获得一个graphics3d对象,这就是为什么只能通过getinstance方法获得该对象。绑定使用bindtarget方法,同时还使用了一些其他方法,让我们看下面的例子:

 

//here is our graphics3d object
graphics3d g3d = graphics3d.getinstance();

// bind to an image
image img = image.createimage("myimage.png");
graphics g = img.getgraphics();
g3d.bindtarget(g);

// bind to the main graphics object
g3d.bindtarget(getgraphics());

// we can also supply rendering hints. remember those? i talked about them at the beginning.
// this is done by using the other form of the bindtarget method.
// it takes a graphics object to begin with, as always, and then it needs a boolean
// and an integer mask of hints.
// the boolean simply tells the graphics3d object if it should use a depth buffer
// and you'll probably always set it to 'true'. here is how we'll use it to bind
// with our hints:
g3d.bindtarget(getgraphics(), true, rendering_hints);

 

现在,你知道了如何绑定你的目标,你同样应该知道在循环中如何释放目标。这就意味着在绘制完后,必须释放目标。释放和绑定时有可能出现问题。所以,大多数人维持正个游戏循环在try/catch块里,并把releasetarget调用放在finally子句里。在这个例子里,我们也将这样做。现在,让我们了解一下绘制方法。绘制不同的事物,你可以使用不同的绘制方法,不过现在我们只对render(world)方法感兴趣。简单吧?是的,你只需要提供你的世界结点,它将帮你绘制世界。让我们看看我们的游戏循环是如何的:

 

/** draws to screen
     */
    private void draw(graphics g)
    {
        // envelop all in a try/catch block just in case
        try
        {
            // move the camera around
            movecamera();
           
            // get the graphics3d context
            g3d = graphics3d.getinstance();
           
            // first bind the graphics object. we use our pre-defined rendering hints.
            g3d.bindtarget(g, true, rendering_hints);
        
            // now, just render the world. simple as pie!
            g3d.render(world);
        }
        catch(exception e)
        {
            reportexception(e);
        }
        finally
        {
            // always remember to release!
            g3d.releasetarget();
        }
    } 

 

如此简单的循环,让我们看看它做了些什么!首先调用movecamera方法,这个用于照相机的移动和旋转的。我们在之前已经见过的。然后,它获得一个graphics3d实例并绑定到图形对象提供给draw方法。(注意:draw方法在线程的run方法里被调用,并有一个全局图形对象)

 

它同时添加在我们画布开始时定义的绘制迹象。毕竟是这样做的,它调用g3d.render(world)方法,它将为我们做所有事。它绘制我们的全部场面、网眼、材料、灯光和照相机。

 

结论

 

通过上面这些,下面是两张该程序运行时的屏幕截图:

 

3d编程指南第一部分:快速进入移动java 3d编程世界(图一) 3d编程指南第一部分:快速进入移动java 3d编程世界(图二)

 

 

看起来还不错吧?

 

下面是完整的midlet和canvas类的源代码。代码并不多,如果你认为这个是显示在屏幕上的,你可以在指南的前面下载并运行它,并且有将找到完整jar/jad的应用程序包

 

m3gmidlet

 

import javax.microedition.lcdui.command;
import javax.microedition.lcdui.commandlistener;
import javax.microedition.lcdui.display;
import javax.microedition.lcdui.displayable;
import javax.microedition.midlet.midlet;
import javax.microedition.midlet.midletstatechangeexception;

public class m3gmidlet extends midlet implements commandlistener
{
    // a variable that holds the unique display
 private display display = null;
 
 // the canvas
 private m3gcanvas canvas = null;
 
 // the midlet itself
 private static midlet self = null;

 /** called when the application starts, and when it is resumed.
  * we ignore the resume here and allocate data for our canvas
  * in the startapp method. this is generally very bad practice.
  */
 protected void startapp() throws midletstatechangeexception
 {
     // allocate
  display = display.getdisplay(this);
  canvas = new m3gcanvas(30);
  
  // add a quit command to the canvas
  // this command won't be seen, as we
  // are running in fullscreen mode
  // but it's always nice to have a quit command
  canvas.addcommand(new command("quit", command.exit, 1));
  
  // set the listener to be the midlet
  canvas.setcommandlistener(this);
  
  // start canvas
  canvas.start();
  display.setcurrent(canvas);
  
  // set the self
  self = this;
 }

 /** called when the game should pause, such as during a call */
 protected void pauseapp()
 {
  
 }

 /** called when the application should shut down */
 protected void destroyapp(boolean unconditional) throws midletstatechangeexception
 {
     // method that shuts down the entire midlet
  notifydestroyed();
 }

 /** listens to commands and processes */
    public void commandaction(command c, displayable d) {
        // if we get an exit command we destroy the application
        if(c.getcommandtype() == command.exit)
            notifydestroyed();
    }
   
    /** static method that quits our application
     * by using the static field 'self' */
    public static void die()
    {
        self.notifydestroyed();
    }
}

 

m3gcanvas

 

import javax.microedition.lcdui.graphics;
import javax.microedition.lcdui.game.gamecanvas;
import javax.microedition.m3g.camera;
import javax.microedition.m3g.graphics3d;
import javax.microedition.m3g.light;
import javax.microedition.m3g.loader;
import javax.microedition.m3g.object3d;
import javax.microedition.m3g.transform;
import javax.microedition.m3g.world;

public class m3gcanvas
extends gamecanvas
implements runnable {
    // thread-control
    boolean running = false;
    boolean done = true;
   
    // if the game should end
    public static boolean gameover = false;
   
    // rendering hints
    public static final int strong_rendering_hints = graphics3d.antialias | graphics3d.true_color | graphics3d.dither;
    public static final int weak_rendering_hints = 0;
    public static int rendering_hints = strong_rendering_hints;
   
    // key array
    boolean[] key = new boolean[5];
   
    // key constants
    public static final int fire = 0;
    public static final int up = fire + 1;
    public static final int down = up + 1;
    public static final int left = down + 1;
    public static final int right = left + 1;
   
    // global identity matrix
    transform identity = new transform();
   
    // global graphics3d object
    graphics3d g3d = null;
   
    // the global world object
    world world = null;
   
    // the global camera object
    camera cam = null;
   
    // camera rotation
    float camrot = 0.0f;
    double camsine = 0.0f;
    double camcosine = 0.0f;
   
    // head bobbing
    float headdeg = 0.0f;
   
    /** constructs the canvas
     */
    public m3gcanvas(int fps)
    {
        // we don't want to capture keys normally
        super(true);
       
        // we want a fullscreen canvas
        setfullscreenmode(true);
       
        // load our world
        loadworld();
       
        // load our camera
        loadcamera();
    }
   
    /** when fullscreen mode is set, some devices will call
     * this method to notify us of the new width/height.
     * however, we don't really care about the width/height
     * in this tutorial so we just let it be
     */
    public void sizechanged(int newwidth, int newheight)
    {
       
    }
   
    /** loads our camera */
    private void loadcamera()
    {
        // bad!
        if(world == null)
            return;
       
        // get the active camera from the world
        cam = world.getactivecamera();
       
        // create a light
        light l = new light();
       
        // make sure it's ambient
        l.setmode(light.ambient);
       
        // we want a little higher intensity
        l.setintensity(3.0f);
       
        // add it to our world
        world.addchild(l);
    }
   
    /** loads our world */
    private void loadworld()
    {
        try
        {
            // loading the world is very simple. note that i like to use a
            // res-folder that i keep all files in. if you normally just put your
            // resources in the project root, then load it from the root.
            object3d[] buffer = loader.load("/res/map.m3g");
           
            // find the world node, best to do it the "safe" way
            for(int i = 0; i < buffer.length; i++)
            {
                if(buffer[i] instanceof world)
                {
                    world = (world)buffer[i];
                    break;
                }
            }
           
            // clean objects
            buffer = null;
        }
        catch(exception e)
        {
            // error!
            system.out.println("loading error!");
            reportexception(e);
        }
    }

    /** draws to screen
     */
    private void draw(graphics g)
    {
        // envelop all in a try/catch block just in case
        try
        {
            // move the camera around
            movecamera();
           
            // get the graphics3d context
            g3d = graphics3d.getinstance();
           
         // first bind the graphics object. we use our pre-defined rendering hints.
         g3d.bindtarget(g, true, rendering_hints);
        
         // now, just render the world. simple as pie!
         g3d.render(world);
        }
        catch(exception e)
        {
            reportexception(e);
        }
        finally
        {
            // always remember to release!
            g3d.releasetarget();
        }
    }
   
    /**
     *
     */
    private void movecamera() {
        // check controls
        if(key[left])
        {
            camrot += 5.0f;
        }
        else if(key[right])
        {
            camrot -= 5.0f;
        }
       
        // set the orientation
        cam.setorientation(camrot, 0.0f, 1.0f, 0.0f);
       
        // calculate trigonometry for camera movement
        double rads = math.toradians(camrot);
        camsine = math.sin(rads);
        camcosine = math.cos(rads);
       
        if(key[up])
        {
            // move forward
            cam.translate(-2.0f * (float)camsine, 0.0f, -2.0f * (float)camcosine);
           
            // bob head
            headdeg += 0.5f;
           
            // a simple way to "bob" the camera as the user moves
            cam.translate(0.0f, (float)math.sin(headdeg) / 3.0f, 0.0f);
        }
        else if(key[down])
        {
            // move backward
            cam.translate(2.0f * (float)camsine, 0.0f, 2.0f * (float)camcosine);
           
            // bob head
            headdeg -= 0.5f;
           
            // a simple way to "bob" the camera as the user moves
            cam.translate(0.0f, (float)math.sin(headdeg) / 3.0f, 0.0f);
        }
       
        // if the user presses the fire key, let's quit
        if(key[fire])
            m3gmidlet.die();
    }

    /** starts the canvas by firing up a thread
     */
    public void start() {
        thread mythread = new thread(this);
       
        // make sure we know we are running
        running = true;
        done = false;
       
        // start
        mythread.start();
    }
   
    /** run, runs the whole thread. also keeps track of fps
     */
    public void run() {
        while(running) {
            try {               
                // call the process method (computes keys)
                process();
               
                // draw everything
                draw(getgraphics());
                flushgraphics();
               
                // sleep to prevent starvation
                try{ thread.sleep(30); } catch(exception e) {}
            }
            catch(exception e) {
                reportexception(e);
            }
        }
       
        // notify completion
        done = true;
    }
   
    /**
     * @param e
     */
    private void reportexception(exception e) {
        system.out.println(e.getmessage());
        system.out.println(e);
        e.printstacktrace();
    }

/** pauses the game
     */
    public void pause() {}
   
    /** stops the game
     */
    public void stop() { running = false; }
   
    /** processes keys
     */
    protected void process()
    {
        int keys = getkeystates();
       
        if((keys & gamecanvas.fire_pressed) != 0)
            key[fire] = true;
        else
            key[fire] = false;
       
        if((keys & gamecanvas.up_pressed) != 0)
            key[up] = true;
        else
            key[up] = false;
       
        if((keys & gamecanvas.down_pressed) != 0)
            key[down] = true;
        else
            key[down] = false;
       
        if((keys & gamecanvas.left_pressed) != 0)
            key[left] = true;
        else
            key[left] = false;
       
        if((keys & gamecanvas.right_pressed) != 0)
      &n