在这里我首先感谢大家对上一篇文章J2ME Mobile 3D入门教程系列文章之一的支持,也正是有了大家的支持,我决定把我的Mobile3D学习过程拿出来和大家共享,希望大家能一起来讨论Mobile3D。
在上一次的教程中我为您详细的介绍了,在Moble3D中通过及时运算建立3D图形,并且对从*.m3g文件中导入模型做了一个简单的介绍,这次我想在这里通过*.m3g文件的使用简单的介绍一下Mobile3D对动画的控制,以及对模型的一些操作。
首先我来简单的说一下m3g文件的建立,这个其实很简单,你只需要选择一种你所熟悉的3D图形制作软件,并安装对应的插件即可,在这里我使用的软件是MAYA和3DS MAX,插件使用的是H3T Export Plugin,该插件有maya和3ds max的,同时还需要下载一个软件M3G Tools kit。
这些都可以从Sony Ericsson的网站上找到连接。插件的安装应该都没问题吧。插件安装完成后,就可以建立模型、设置材质帖图、设置摄影机、设置关键帧等等了,最后只要输出成H3T文件即可。然后再打开M3G Tools kit将h3t文件输出成m3g文件就可以了。在使用m3g文件之前最好用M3G Tools kit浏览一下该文件,为的是记录对应的信息,例如userid和该场景的树状结构等信息,这些都是非常重要的。如果你这样做了,你会发现这样导出的m3g文件中camera并没有在world树下,而是和world树是平级的。
其实上一篇文章中我也说过,camera和渲染信息可以不被放在world树下,但是所有的模型信息必须被放到树下。当我们看m3g文件的时候比较重要的是看清楚world节点所在的位置,以及需要我们操作的模型的userid。
准备工作差不多了,现在来看Mobile3D吧,在上一文章中我说过m3g文件使用通过javax.microedition.m3g.Loader.load(String url)载入的,而且该方法返回的是javax.microedition.m3g.Object3D的数组,也许有人要问既然world是根节点,那么为什么不直接返回一个World呢?
刚才让大家看m3g文件的结构的时候就已经说明了摄影机和动画设定等等并没有被放到World为节点的书中,而是和World节点同级的。而World节点是场景的根节点才对。那么我们如何才正确的取出World节点呢?方法有两个:
1、遍历该Object3D数组,并比较每个元素的userid如果正式World节点的useid将该元素取出。
2、遍历该Object3D数组,并比较每个元素是不是World类的实例,那么既然World节点是场景的根节点,那么在该Object3D数组中也应该只有一个World类的实例对象。
第一个方法比较简单,我在这里只给出第二个方法的片断代码。(其实是一样的)
private void loadWorld(){
System.out.println("now loading...");
try{
buffer=Loader.load("/img/TmpMicroFile.m3g");
for(int i=0;i
if(buffer[i] instanceof World){
world=(World)buffer[i];
return ;
}
}
}catch(Exception e){
buffer=null;
System.out.println("thorw a exception when loading");
e.printStackTrace();
}
}
然后我们设置摄影机,不同的是,这次我们从World中获得摄影机信息,获取后我们可以对其进行一些基本的设置这里不细说了。下面着重的说一下动画的部分。
在我们导入这个m3g文件时候我们其实已经有了动画的信息了,并且这些动画信息有可能是很复杂的,我测试了连杆两轴的运动可以没问题,从一些资料上看到的IK反向运动也没有问题。也许你已经着急了,怎么还不说如何控制动画的播放呢?
简单的说World.animate()来更新动画信息,这个方法需要传进去一个int类型的参数,当你第一次调用该方法的时候,系统会记录下这个数值,后面每次调用的时候都会和这个数值相比较,然后系统替我们计算出动画更新到什么位置,同时该方法会返回一个int类型的参数,这个参数表示的下一次更新的一个建议的数值(是以毫秒为单位的)。
我们这时就可以让调用这个方法的线程休眠这个时间,以便下以次更新动画数据。这里也许你会有疑问,我的动画明明只有几十帧,可是这里却反复的播放;这虽然是个好消息,但在有很多情况播放的长度、什么时候播放,都需要我们自己来控制怎么办?
这时候我们就需要用到javax.microedition.m3g.AnimationController类了,其实每个动画中的每个可以动的模型都有自己的AnimationController对象,和模型动画一样我们可以通过World.find(int controllerID)来获得,在该类中我们可以通过setActiveInterval(int activeTime,int unactiveTime)来设定动画在该系统中播放的起始和重点时间,另外通过setPosition(int startTime,int endTime)方法控制这个动画需要播放哪一段落。
很抱歉有关两个方法的时候在这次教程实例中我并没有使用,不过如果您想了解关于这两个方法的使用,我建议你看看WTK2.2的那个Demo3D中袋鼠的例子,那里面写的挺详细的。这里呢我给出动画和绘制的代码片断吧。
protected void paint(Graphics g) {
startTime= System.currentTimeMillis() - worldStartTime;
validity= world.animate((int)startTime);
perFrameTime=(int)System.currentTimeMillis();
g.setColor(0x00);
g.fillRect(0,0,getWidth(), getHeight());
g.setClip(0,0,getWidth(),getHeight());
g3d.bindTarget(g,true,Graphics3D.DITHER|Graphics3D.TRUE_COLOR);
g3d.setViewport(0,0,getWidth(),getHeight());
g3d.render(world);
g3d.releaseTarget();
framePor=(int)1000/((int)System.currentTimeMillis()-perFrameTime);
System.out.println("3D demo frame/sn:"+framePor);
if(validity < 1)
{ // The validity too small; allow a minimum of 1ms.
validity = 1;
}
if(validity == 0x7fffffff)
{ // The validity is infinite; scehdule a refresh in 1 second.
validity=1000;
}
}
public void run() {
while(isRun){
repaint();
try{
Thread.sleep(validity);
}catch(Exception e){}
}
}
这里如果你不喜欢使用线程的话,你可以换成Timer。个人喜好问题吧,我对线程比较熟悉,所以这里我使用的是线程。
动画似乎我现在也只理解到这些,全部和大家共享出来了。下面我想说一说Transformable类中的几个方法。首先来介绍一下Transformable类吧。Transformable类是很重要的一个类,Node就是它的子类,知道它的重要性了吧。
Transformable中有四个方法是今天我要简单说一下的postRotate(float, float, float, float)放是和物体的旋转有关系的在Mobile3D中默认所有的物体的旋转轴都在自身的中心,所以对物体进行旋转操作的话,一定是自转,而不是绕某个点、或某个轴公转,这一点一定要弄清楚。也许这样说有些抽象那么我换个说法,就是这里的旋转只改变物体的朝向,并不改变物体的位置。
这下明白了吧,也许有人要问“这不正是我们需要的吗?为什么要强调呢?”原因其实是这样的,在有的3D引擎中,旋转是以原点为基础的。也就是说如果需要做自转运动,就必须做移动、再旋转、再移动这样的操作,其实这样做的好处是可以方便点的运算,因为点不存在自转的概念。而我们的Mobile3D在某种程度上说是不可以对点操作的,所以它的最小单位是Mesh,那么它如何实现自转呢?来看个矩阵你就明白了
90,0,0,0
0,0,0,0
0,0,1,0
0,0,0,0
这个矩阵表示的当前的模型在Y轴上有90度的旋转。现在我们回头来看看postRotate(float a, float x, float y, float z)方法,其中有4个参数,第一个就是这次需要旋转的角度,后三个其实一个这次旋转的轴,是通过一个向量来表示的,postRotate方法就是在原来的朝向上继续进行新的旋转,如果用数学公式来表示的话就是使用原有的矩阵乘以
a,0,0,0
0,x,0,0
0,0,y,0
0,0,0,z
这个矩阵就是新的方向矩阵。这个方法如果从数学角度听起来很难理解的话,你只要记住四个参数第一个是这次旋转的角度,后三个确定旋转轴。
同postRotate方法类似的是setOrientation(float a, float x, float y, float z)。不同的是这次不再是旋转了而是直接设置到这个朝向。
说过旋转再来看看移动,这个似乎就简单很多了translate(float x, float y, float z)这个就是指按照指定的向量平移;setTranslation(float x, float y, float z)这个更简单了,直接移动到指定的位置。
其实还有一个方法scale(float sx, float sy, float sz)是用来进行缩放的,同样还有setScale(float sx, float sy, float sz)直接设置比例尺,这个六个方法是对物体进行直接的操作的,非常有用,尤其是前四个,在编写游戏的时候会经常用到,而后两个使用的频率似乎少一些,因为很多时候我们都是直接对摄像机的距离进行操作的(除非有特殊需要)。下面我给出个使用前四方法的举例吧。
public void keyPressed(int keycode){
float[] camerTra;
float x;
float z;
switch(keycode){
case GameCanvas.DOWN:
break;
case GameCanvas.UP:
break;
case 52:
dir=dir-2;
System.out.println(dir);
x=(float)(3*Math.sin((dir * 3.14159f)