Android之3D物理引擎
Android下有很多优秀的3D物理引擎,如alien3d,gamine,jpct等,今天,我们介绍如何使用jpct(选这个引擎是因为它的官网上的Demo和ScreenShots很不错)
1.
在http://www.jpct.net/下载jpct,jpct全部都是用Java写的,有两个版本,一个是在桌面OS上运行的(如Windows,Linux等),另一个是在Android上运行的,我们把这两个都下载下来,我们都会用到。
以下是官网的一些截图,很不错:
2.
我下载的桌面版是jpctapi.zip,Android版是jpct-ae.zip
分别解压。
下面我们要正式开始了,我们今天要做三个实验,第一个是掌握如何编译jpct的Demo,第二个是如何使用jpct写一个HelloWorld,第三个是用jpct写一个在Android上跑的3D程序并下载到真机上运行。前两个实验都用桌面版的jpct,最后一个实验用Android版的jpct。下面我们正式开始:
实验一:
打开jpct的example,有四个example,以car为例:
分别运行如下命令:
unzip jpctapi.zip
cd jpct/examples/car
chmod 777 run_java.sh
./run_java.sh
就会弹出一个窗口,如下:
同时控制台输入如下内容:
Adding Lightsource: 0 Adding Lightsource: 1 Adding Lightsource: 2 Loading Texture...textures/other/numbers.jpg Loading Texture...textures/rocks.jpg Loading file models/terascene.3ds File models/terascene.3ds loaded...146731 bytes Processing new material Material! Processing object from 3DS-file: t3_whole Object 't3_whole_jPCT0' created using 7200 polygons and 3721 vertices. Loading Texture...textures/spot.jpg Created 1485 triangle-strips for t3_whole_jPCT0 in 1 pass(es) Loading Texture...textures/spot.jpg Loading Texture...textures/decal.jpg Loading Texture...textures/radar.jpg Loading Texture...textures/plant.jpg Loading Texture...textures/plant2.jpg Loading file models/plant.3ds File models/plant.3ds loaded...1307 bytes Processing new material Material! Processing object from 3DS-file: skin Object 'skin_jPCT129' created using 34 polygons and 29 vertices. Loading Texture...textures/skidmark.jpg Building octree for 7200 triangles! Octree constructed with 517 nodes / 423 leafs. Java version is: 1.6.0_26 -> support for BufferedImage Version helper for 1.5+ initialized! -> using BufferedImage Software renderer (OpenGL mode) initialized Software renderer disposed Software renderer (OpenGL mode) initialized 339-361 -> using splitted buffer access! New WorldProcessor created using 1 thread(s) and granularity of 1! Creating new world processor buffer for thread main Software renderer disposed
这是一个小车3D游戏,按方向键,小车会在高低不平的山坡上移动,如下:
下面的截图是另一个example:fps的截图:
至此,我们学会了如何编译Demo,下面是一个小小的补充:如何在Eclipse中编译:
1.
在Eclipse中新建一个Java project,取名为Demo
2.
将car/src/下的java源文件全部copy到Demo/src里去,同时将car/models和car/textures也一同copy到Demo下,完成后的结果如下图:
3.
右击Demo,选择Properties,在Java Build Path的Libaries下,点击"Add External JARs...",把jpct.jar加入到Project中去,如下图:
4.
点击Run As Java Application就可以了,效果和之前的一样。
至此,第一个实验完成!
实验二:
我们要用Eclipse来写一个3D程序,主要的程序就是官网的HelloWorld,官网的Wiki有对它的详细说明:
http://www.jpct.net/wiki/index.php/Hello_World
1.
新建一个HelloWorld的Java Project,和第一步一样,把jpct.jar加入到Libraries中
2.
新建一个HelloWorld类,输入如下内容:
import javax.swing.JFrame; import com.threed.jpct.FrameBuffer; import com.threed.jpct.IRenderer; import com.threed.jpct.Object3D; import com.threed.jpct.Primitives; import com.threed.jpct.Texture; import com.threed.jpct.TextureManager; import com.threed.jpct.World; public class HelloWorld { private World world; private FrameBuffer buffer; private Object3D box; private JFrame frame; public static void main(String[] args) throws Exception { new HelloWorld().loop(); } public HelloWorld() throws Exception { frame=new JFrame("Hello world"); frame.setSize(800, 600); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); world = new World(); world.setAmbientLight(0, 255, 0); TextureManager.getInstance().addTexture("box", new Texture("box.jpg")); box = Primitives.getBox(13f, 2f); box.setTexture("box"); box.setEnvmapped(Object3D.ENVMAP_ENABLED); box.build(); world.addObject(box); world.getCamera().setPosition(50, -50, -5); world.getCamera().lookAt(box.getTransformedCenter()); } private void loop() throws Exception { buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_NORMAL); while (frame.isShowing()) { box.rotateY(0.01f); buffer.clear(java.awt.Color.BLUE); world.renderScene(buffer); world.draw(buffer); buffer.update(); buffer.display(frame.getGraphics()); Thread.sleep(10); } buffer.disableRenderer(IRenderer.RENDERER_OPENGL); buffer.dispose(); frame.dispose(); System.exit(0); } }
3.
将examples/helloworld/下的box.jpg拷贝到HelloWorld下,因为我们的程序里用到了它
4.
点击Run As Java Application就可以了,如下图:
这是一个在不停旋转的长方体。
其实,我们也可以直接运行Demo的Helloworld:
cd jpct/examples/helloworld
chmod 777 run_software.sh
./run_software.sh
注意,在helloworld下一共有三个不同平台的版本:OpenGL,awtgl和软件模拟的OpenGL,我们的例子用的就是软件模拟的OpenGL(我的机器无法运行OpenGL的版本,很奇怪!),你也可以试试其他版本的,效果是一样的
至此,第二个实验完成,我们用jpct写了一个HelloWorld
第三个实验
我们写一个Android下跑的3D程序:屏幕中央有一个正方体,用手拖动到不同的地方会朝那个地方旋转,这里这样参考了官网的Demo:http://www.jpct.net/wiki/index.php/Hello_World_for_Android
1.
新建一个Demo的Android Project,如下:
2.
将jpct-ae.jar加入到Project中(注意,不是jpct.jar)
3.
修改DemoActivity.java如下:
package com.test.demo; import java.lang.reflect.Field; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.view.MotionEvent; import com.threed.jpct.Camera; import com.threed.jpct.FrameBuffer; import com.threed.jpct.Light; import com.threed.jpct.Logger; import com.threed.jpct.Object3D; import com.threed.jpct.Primitives; import com.threed.jpct.RGBColor; import com.threed.jpct.SimpleVector; import com.threed.jpct.Texture; import com.threed.jpct.TextureManager; import com.threed.jpct.World; import com.threed.jpct.util.BitmapHelper; import com.threed.jpct.util.MemoryHelper; public class DemoActivity extends Activity { // Used to handle pause and resume... private static DemoActivity master = null; private GLSurfaceView mGLView; private MyRenderer renderer = null; private FrameBuffer fb = null; private World world = null; private RGBColor back = new RGBColor(50, 50, 100); private float touchTurn = 0; private float touchTurnUp = 0; private float xpos = -1; private float ypos = -1; private Object3D cube = null; private int fps = 0; private Light sun = null; protected void onCreate(Bundle savedInstanceState) { Logger.log("onCreate"); if (master != null) { copy(master); } super.onCreate(savedInstanceState); mGLView = new GLSurfaceView(getApplication()); mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() { public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { // Ensure that we get a 16bit framebuffer. Otherwise, we'll fall // back to Pixelflinger on some device (read: Samsung I7500) int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] result = new int[1]; egl.eglChooseConfig(display, attributes, configs, 1, result); return configs[0]; } }); renderer = new MyRenderer(); mGLView.setRenderer(renderer); setContentView(mGLView); } @Override protected void onPause() { super.onPause(); mGLView.onPause(); } @Override protected void onResume() { super.onResume(); mGLView.onResume(); } @Override protected void onStop() { super.onStop(); } private void copy(Object src) { try { Logger.log("Copying data from master Activity!"); Field[] fs = src.getClass().getDeclaredFields(); for (Field f : fs) { f.setAccessible(true); f.set(this, f.get(src)); } } catch (Exception e) { throw new RuntimeException(e); } } public boolean onTouchEvent(MotionEvent me) { if (me.getAction() == MotionEvent.ACTION_DOWN) { xpos = me.getX(); ypos = me.getY(); return true; } if (me.getAction() == MotionEvent.ACTION_UP) { xpos = -1; ypos = -1; touchTurn = 0; touchTurnUp = 0; return true; } if (me.getAction() == MotionEvent.ACTION_MOVE) { float xd = me.getX() - xpos; float yd = me.getY() - ypos; xpos = me.getX(); ypos = me.getY(); touchTurn = xd / -100f; touchTurnUp = yd / -100f; return true; } try { Thread.sleep(15); } catch (Exception e) { // No need for this... } return super.onTouchEvent(me); } protected boolean isFullscreenOpaque() { return true; } class MyRenderer implements GLSurfaceView.Renderer { private long time = System.currentTimeMillis(); public MyRenderer() { } public void onSurfaceChanged(GL10 gl, int w, int h) { if (fb != null) { fb.dispose(); } fb = new FrameBuffer(gl, w, h); if (master == null) { world = new World(); world.setAmbientLight(20, 20, 20); sun = new Light(world); sun.setIntensity(250, 250, 250); // Create a texture out of the icon...:-) Texture texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.ic_launcher)), 64, 64)); TextureManager.getInstance().addTexture("texture", texture); cube = Primitives.getCube(10); cube.calcTextureWrapSpherical(); cube.setTexture("texture"); cube.strip(); cube.build(); world.addObject(cube); Camera cam = world.getCamera(); cam.moveCamera(Camera.CAMERA_MOVEOUT, 50); cam.lookAt(cube.getTransformedCenter()); SimpleVector sv = new SimpleVector(); sv.set(cube.getTransformedCenter()); sv.y -= 100; sv.z -= 100; sun.setPosition(sv); MemoryHelper.compact(); if (master == null) { Logger.log("Saving master Activity!"); master = DemoActivity.this; } } } public void onSurfaceCreated(GL10 gl, EGLConfig config) { } public void onDrawFrame(GL10 gl) { if (touchTurn != 0) { cube.rotateY(touchTurn); touchTurn = 0; } if (touchTurnUp != 0) { cube.rotateX(touchTurnUp); touchTurnUp = 0; } fb.clear(back); world.renderScene(fb); world.draw(fb); fb.display(); if (System.currentTimeMillis() - time >= 1000) { Logger.log(fps + "fps"); fps = 0; time = System.currentTimeMillis(); } fps++; } } }
4.
点击Run As Android Application,打开模拟器,效果如下:
拖动中央的正方体,会朝不同的方向旋转
至此,第三个实验完成,快下载到手机里玩玩吧~~
这三个实验只是jpct的使用而已,更复杂的东西需要看官网的Wiki和相关文档。
完成!