`
micheal19840929
  • 浏览: 163210 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

M3G教程:进阶篇(六)动画

    博客分类:
  • J2ME
阅读更多

 

      M3G中动画的数据结构如下:

【载入World节点】

      通过M3G Viewer查看M3G Converter转换的m3g文件,我们会发现m3g文件中camera并没有在world树下,而是和world树是平级的。因为JSR184要求渲染时所有模型信息必须放到world树下,所以我们要先获取World节点,方法有两种:

      ①遍历该Object3D数组,并比较每个元素的userid如果正是World节点的useid将该元素取出;
      ②遍历该Object3D数组,并比较每个元素是不是World类的实例,那么既然World节点是场景的根节点,那么在该Object3D数组中也应该只有一个World类的实例对象。

      第一种必需通过查看文件中world节点的userId来获取,第二种比较容易操作,代码如下:

/** 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("/axe.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);
	}
}

      在JSR184中,使用AnimationTrack、AnimationController和KeyframeSquence三个类来实现关键帧动画:

  AnimationTrack:是一个集合类(Composition),用来将AnimationController、KeyframeSquence和一个特定属性(Target Property)捆绑起来,这种结构也使得同一个AnimationController和KeyframeSquence可以被不同的关键帧动画复用。在每一个Object3D对象中,通过addAnimationTrack和removeAnimationTrack方法,管理一组AnimationTrack序列;当使用animate方法,计算该Object3D对象关键帧动画时,所有在序列中的AnimationTrack都会被依次计算,此外若该Object3D对象有到其他Object3D的引用,则所有引用的Object3D的animate方法也会被依次执行。

  AnimationController:通过指定采样点和采样速度,实现从WorldTime到SquenceTime的映射,从而控制动画的播放效果,如:暂停,继续,快进、倒退等。

      AnimationController有一个激活时间段属性(通过setActiveInterval(int,int)实现),通过指定最小及最大的world time值来控制这个AnimationController在这个时间段是激活状态的。如果AnimationController是在某时间是非激活状态,则系统在动画计算时直接将其忽略。

      另外,AnimationController有一个weight属性,因为系统对对象某属性进行计算时会将与此属性相关的所有动画计算的结果进行合并,而weight则表示这个AnimationController在这个计算中所占的比重,确切的说,对于一个标量属性P的计算是这样的:P = sum [ wi Pi ] 。

      AnimationController将传入Object3D.animate()方法的world time值与sequence time值进行映射,sequence time主要用于对关键帧动画数据进行抽样。sequence time会在每次调用对象的animate()方法时计算得出,而不是store it internally,这是为了避免误差的累积及由此产生的一系列状况。这种设计简化了动画系统,使动画系统能够无状态化(相对于状态机),并且效率更高。

      从world time到sequence time的映射由AnimationController中的三个常量及传入animate()方法的world time进行计算得到的。公式如下:

     

      其中的参考值t[sref]及t[wref]是通过setPosition()方法指定,而速度speed则是由setSpeed()方法指定(注意改变速度会影响到t[sref]及t[wref]值)。

      注意:虽然API及文档中并没有明确的说明这里的时间单位使用毫秒,但我们强烈建议你使用这个默认的时间单位。当然,如果确实需要使用特有有时间单位,动画系统还是支持的。

  KeyframeSquence:用来保存所有的关键帧的值,同时指定关键帧循环的方式,以及计算时的插值方法。

      无论在立即模式还是保留模式下,都是通过调用Object3D对象的animate()方法实现将对象动画数据的计算。根据使用情形,我们既可以调用World.animate(),也可以调用相应Object3D对象的animate()方法。

【示例】

以下代码实现一个灯光在红色与绿色之间转换,并沿着一条曲线路径进行移动:

   初始化时:

Light light = new Light();	// Create a light node
 
// Load a motion path from a stream, assuming it's the first object there
 
Object3D[] objects = Loader.load("http://www.ex.com/ex.m3g");
KeyframeSequence motion = (KeyframeSequence) objects[0];
 
// Create a color keyframe sequence, with keyframes at 0 ms
// and 500 ms, and a total duration of 1000 ms. The animate
// method will throw an exception if it encounters a
// KeyframeSequence whose duration has not been set or whose
// keyframes are out of order. Note that the Loader
// automatically validates any sequences that are loaded from
// a file.
 
KeyframeSequence blinking = new KeyframeSequence(2, 3, KeyframeSequence.LINEAR);
blinking.setKeyframe(0,   0, new float[] { 1.0f, 0.0f, 0.0f });
blinking.setKeyframe(1, 500, new float[] { 0.0f, 1.0f, 0.0f });
blinking.setDuration(1000);
 
AnimationTrack blink = new AnimationTrack(blinking, AnimationTrack.COLOR);
AnimationTrack move = new AnimationTrack(motion, AnimationTrack.TRANSLATION);
light.addAnimationTrack(blink);
light.addAnimationTrack(move);
 
// Create an AnimationController and make it control both the
// blinking and the movement of our light
 
AnimationController lightAnim = new AnimationController();
blink.setController(lightAnim);
move.setController(lightAnim);
 
// Start the animation when world time reaches 2 seconds, stop
// at 5 s.  There is only one reference point for this
// animation: sequence time must be zero at world time 2000
// ms. The animation will be running at normal speed (1.0, the
// default).
 
lightAnim.setActiveInterval(2000, 5000);
lightAnim.setPosition(0, 2000);

    渲染时:

appTime += 30;		// advance time by 30 ms each frame
light.animate(appTime);
 
// Assume 'myGraphics3D' is the Graphics3D object we draw into.
// In immediate mode, node transforms are ignored, so we get
// our animated transformation into a local Transform object,
// "lightToWorld". As its name implies, the transformation is
// from the Light node's local coordinates to world space.
 
light.getTransform(lightToWorld);
myGraphics3D.resetLights();
myGraphics3D.addLight(light, lightToWorld);

 

【示例:播放袋鼠跳动动画】

package study.pogoroo;



import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.m3g.AnimationController;
import javax.microedition.m3g.AnimationTrack;
import javax.microedition.m3g.Camera;
import javax.microedition.m3g.Graphics3D;
import javax.microedition.m3g.Group;
import javax.microedition.m3g.KeyframeSequence;
import javax.microedition.m3g.Loader;
import javax.microedition.m3g.Object3D;
import javax.microedition.m3g.World;


public class M3GCanvas extends GameCanvas implements Runnable {
	
	public static final int FPS = 50;	//每秒绘制的帧数
	
//	 Thread-control
	boolean running = false;

	boolean done = true;

	//UserIDs for objects we use in the scene.
    static final int POGOROO_TRANSFORM_ID = 347178853;
    static final int ROO_BOUNCE_ID = 418071423;

    // Control objects for game play
    // control for 'roo - group transform and cameras
    private AnimationController animRoo = null;
    private Group acRoo = null;
    private int animTime = 0;
    private int animLength = 0;
    private int animLastTime = 0;
    int viewport_x;
    int viewport_y;
    int viewport_width;
    int viewport_height;

	// Key array
	private boolean[] key = new boolean[5];

	// Key constants
	public static final int FIRE = 0;
	public static final int UP = 1;
	public static final int DOWN = 2;
	public static final int LEFT = 3;
	public static final int RIGHT = 4;

	// Camera rotation
	private float camRot = 0.0f;

	private Graphics3D g3d;
	private World world;
	private boolean runnable=true;
	private Thread thread;
	private Camera camera;

	protected M3GCanvas() {
		super(false);
		setFullScreenMode(true);
		g3d = Graphics3D.getInstance();
		
		//Load our world
		loadWorld();

		// Load our camera
		loadCamera();
		
		getObjects();
		
		setupAspectRatio();
	}
	
	/** Loads our camera */
	private void loadCamera() {
		// BAD!
		if (world == null)
			return;
		
		// Get the active camera from the world
		camera = world.getActiveCamera();
	}

	/** 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("/pogoroo.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!");
		}
	}
	
	/**
     * Make sure that the content is rendered with the correct aspect ratio.
     */
    void setupAspectRatio() {
        viewport_x = 0;
        viewport_y = 0;
        viewport_width = getWidth();
        viewport_height = getHeight();

        float[] params = new float[4];
        int type = camera.getProjection(params);

        if (type != Camera.GENERIC) {
            //calculate window aspect ratio
            float waspect = viewport_width / viewport_height;

            if (waspect < params[1]) {
                float height = viewport_width / params[1];
                viewport_height = (int)height;
                viewport_y = (getHeight() - viewport_height) / 2;
            } else {
                float width = viewport_height * params[1];
                viewport_width = (int)width;
                viewport_x = (getWidth() - viewport_width) / 2;
            }
        }
    }
    
    /**
     * getObjects()
     * get objects from the scene tree for use in the game AI
     */
    public void getObjects() {
        try {
            acRoo = (Group)world.find(POGOROO_TRANSFORM_ID);
            animRoo = (AnimationController)world.find(ROO_BOUNCE_ID);

            // get length of animation
            AnimationTrack track = acRoo.getAnimationTrack(0);
            animLength = 1000; // default length, 1 second

            if (track != null) {
                KeyframeSequence ks = track.getKeyframeSequence();

                if (ks != null) {
                    animLength = ks.getDuration();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * animateRoo()
     * Makes sure that the hopping animation loops correctly.
     */
    private void animateRoo(int worldTime) {
        // control the kangaroo animation sequence
        if (animLastTime == 0) {
            animLastTime = worldTime;
        }

        animTime += (worldTime - animLastTime);

        // initialise animation at end of sequence
        if (animTime > animLength) // sequence is ~1000ms
         {
            animRoo.setActiveInterval(worldTime, worldTime+2000);
            //setPosition(float sequenceTime, int worldTime) 
            //Sets a new playback position, relative to world time, for this animation controller. 
            animRoo.setPosition(0, worldTime);
            animTime = 0;
        }

        // update storage of last position and time
        animLastTime = worldTime;
    }
	
	private void moveCamera() {
		// Check controls
		if (key[LEFT]) {
			camRot += 5.0f;
		} else if (key[RIGHT]) {
			camRot -= 5.0f;
		}

		// Set the orientation
		camera.setOrientation(camRot, 0.0f, 1.0f, 0.0f);

		// If the user presses the FIRE key, let's quit
		if (key[FIRE])
			System.out.println("Fire");
	}
	
	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)
			key[RIGHT] = true;
		else
			key[RIGHT] = false;
	}

	public void run() {
		Graphics g = getGraphics();
		while (runnable) {
			long startTime = System.currentTimeMillis();
			
			//Call the process method (computes keys)
			process();

			//Move the camera around
			moveCamera();
			
			try {
				//First bind the graphics object. We use our pre-defined rendering
				// hints.
				g3d.bindTarget(g);
				
				int st=(int)startTime;
	            // update the control and game AI
				animateRoo(st);
	            // Update the world to the current time.
	            world.animate(st);
	            
				g3d.setViewport(viewport_x, viewport_y, viewport_width, viewport_height);
				//Now, just render the world. Simple as pie!
				g3d.render(world);
			} finally {
				g3d.releaseTarget();
			}
			flushGraphics();
			
			long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            if(costTime<1000/FPS)
            {
                try{
                  Thread.sleep(1000/FPS-costTime);
                }
                catch(Exception e){
                   e.printStackTrace();
                }
            }
		}
		System.out.println("Canvas stopped");

	}
	
	public void start()
	{
		thread=new Thread(this);
		thread.start();
	}
	
	public void stop()
	{
		this.runnable=false;
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

 

其中pogoroo.m3g的节点树关系如下:

效果如下:

 

分享到:
评论

相关推荐

    M3G API Docs

    6. **动画**:学习M3G API提供的动画机制,包括关键帧动画、路径动画和时间线动画,以实现角色动作、物体运动等动态效果。 7. **视图控制**:掌握如何使用Camera对象来控制3D场景的视角和投影,如透视投影和平行...

    M3G Viewer:查看M3G文件内容-开源

    M3G查看器是一个独立的应用程序,用于查看3D图形文件格式的内容,该格式是对移动3D图形API(M3G)的补充。

    J2ME_M3G_API.rar_M3G API_j2me 3d_j2me m_jsr 184 api c_m3g a

    M3G支持基本的3D几何形状创建、纹理映射、光照效果、动画以及视图控制等特性。 **J2ME 3D** 指的是使用J2ME进行3D图形编程的能力。J2ME是Java的一个子集,主要用于开发嵌入式和移动设备的应用程序。通过M3G API,...

    tk_m3gtk_v4_5.zip_m3g_tk_m3g_tk_m3gtk_v4_5

    1. **M3G解析器**:这是程序的核心部分,负责读取M3G文件的二进制数据,解析出模型的几何信息、材质属性、光照设置、动画序列等,并将其转化为可以显示的结构。 2. **3D渲染引擎**:利用Java J2ME的图形库,将解析...

    Mobile 3D Graphics with OpenGL ES and M3G

    此外,还涉及了M3G中的动画系统,如关键帧动画和物理模拟,以及如何使用M3G与Java代码进行交互,实现事件处理和用户交互。 除了基本概念和技术,本书还提供了许多实际案例和示例代码,帮助读者理解并实践所学知识。...

    jsr_184_midlet.rar_DEMO_jsr184_jsr184-m3g.jar_m3g

    在提供的压缩包"jsr_184_midlet.rar_DEMO_jsr184_jsr184-m3g.jar_m3g"中,我们可以看到与JSR 184相关的几个关键元素: 1. **DEMO**:这是一个演示程序,用于展示JSR 184技术的实际应用。通过这个DEMO,开发者或用户...

    M3G 的快速模式编程

    【M3G 快速模式编程】:M3G,全称Mobile 3D Graphics API,是基于JSR 184规范定义的一种为移动设备提供标准3D图形功能的API。它分为快速模式和保留模式。快速模式专注于单个3D对象的渲染,适合进行低级别的3D操作,...

    M3M0:M3m0工具:crossed_swords:网站漏洞扫描程序和自动利用程序

    M3M0渗透测试工具 M3m0工具 :crossed_swords: 网站漏洞扫描程序和自动浏览器您可以使用此工具通过在网站中找到漏洞来检查安全性,也可以使用此工具来获取Shell | 污损| cPanels | 资料库 M3M0 :laptop:M3m0工具 :...

    宝箧印塔模型设计m3g

    通过M3G,开发者可以构建复杂的3D场景,包括几何形状、纹理映射、光照效果以及动画等,这些在资源有限的移动设备上显得尤为珍贵。 “宝箧印塔”模型的设计,需要对3D建模有深入理解。在J2ME环境下,通常采用简单...

    LoaderM3G.rar_j2me loaderm_m3g_手机加载_手机游戏加载

    1. **解析M3G文件**:使用J2ME的M3G API,开发者需要编写代码来读取M3G文件的二进制数据,并将其转换为可操作的对象,如顶点、纹理坐标、索引和动画数据。 2. **初始化3D环境**:设置3D场景,创建相机、光源等元素...

    Java M3G相关类.rar

    Java M3G是Java Micro Edition (JME) 平台上用于移动设备3D图形开发的API,它基于OpenGL ES标准,使得开发者能够在手机等嵌入式设备上创建复杂的3D游戏和应用程序。这个"Java M3G相关类.rar"压缩包包含了一些关键的...

    M3G2FBX_neko_

    "M3G2FBX_neko_" 提供了解决这一问题的解决方案,它是一款专为游戏设计的模型转换工具,主要功能是将M3G格式的模型转换为FBX格式。M3G是一种由Java 3D API支持的3D模型格式,而FBX则是Autodesk的通用3D模型交换格式...

    jsr184+M3G+API.rar

    【标题】"jsr184+M3G+API.rar" 涉及到的主要技术是Java Mobile 3D Graphics API(JSR 184)和M3G(Mobile 3D Graphics)标准,以及相关的API接口。JSR 184是Java Community Process发布的一个规范,目的是为Java ME...

    [JSR-184][3D编程指南]Part V: Heightmap terrain rendering using M3G

    这篇【3D编程指南】的第五部分主要探讨的是使用M3G来实现地形渲染,特别是基于高度图(Heightmap)的地形渲染技术。 首先,理解高度图的概念至关重要。高度图是一种2D图像,其中每个像素的灰度值代表一个3D网格中的...

    M3Gtools 3D开发必备

    5. **动画系统**:M3G支持关节动画和顶点动画,开发者需要学习如何创建和导入动画数据,并在M3GViewer中进行预览。 6. **性能优化**:由于移动设备的硬件限制,优化3D模型的复杂度和渲染效率至关重要。M3GTools可以...

    M3GToolkit-0.5.0

    M3G标准支持包括几何、纹理、光照、动画等多种3D图形特性,使得开发者可以在资源有限的移动设备上实现复杂的3D场景。 M3GToolkit-0.5.0的核心功能在于其提供的EXE文件,位于解压后的BIN目录下。用户只需双击这个...

    一款纯静态的M3U8播放器页面

    源码说明:一个纯静态的M3U8播放器页面,可以直接把M3U8的网址填进去进行播放,超级方便。 部署方法: 可以使用宝塔面板来部署 1,打开宝塔面板,添加一个网站 2,把压缩包上传到站点跟目录,然后解压 3,解压以后...

    联想BIOS_L-IG41M3 V1.1 版本:DMKT05AUS 新圆梦F208

    联想BIOS_L-IG41M3 V1.1 版本:DMKT05AUS 新圆梦F208 原机备份 支持Q9400 支持8G内存 需两条4G双面内存 两个BIOS文件 AFUWIN备份的BIOS BIOS_Backup_TooKit_V2.0备份的BIOS

    M3u Parser:m3u和m3u8解析器-开源

    《深入理解M3U与M3U8:解析与Java实现》 M3U和M3U8是两种广泛应用于多媒体播放的索引文件格式,主要用于组织和播放音频、视频资源。它们在流媒体服务中扮演着至关重要的角色,允许用户连续地、无需等待下载完成即可...

    M3U8Loader:M3U8装载机

    M3U8Loader是一个专为处理M3U8格式文件的工具,主要功能是加载M3U8清单并将其内容组合成一个可播放的MP4文件。M3U8是一种基于HTTP的流媒体协议,广泛应用于在线视频传输,特别是在 HLS(HTTP Live Streaming)系统中...

Global site tag (gtag.js) - Google Analytics