안드로이드 jPCT-AE로 OpenGL ES 3D 데모 개발

2012/06/27 21:31

jPCT-AE는 jPCT의 안드로이드 버전이다. jPCT는 java로 3D를 개발할 수 있게 만든 무료 3D 엔진이다. OpenGL기반이다. 


다음 링크를 통해 jPCT-AE를 구경해볼 수 있다.


http://www.jpct.net/jpct-ae/


jPCT-AE를 사용해 공개되어 있는 소스를 가져다가 안드로이드 프로젝트를 만들어 돌려보았다. 별로 어렵지 않게 결과물을 볼 수 있었다. 



조금 아쉬우니 간단하게 이클립스를 통해 안드로이드 기기에서 저 데모를 실행하는 방법을 적어둔다. 


1. 이클립스에서 안드로이드 프로젝트를 만든다. 물론 ADT 및 SDK가 모두 설치되어 있어야 한다.

2. 생성된 프로젝트에 libs폴더를 만든다. 

3. 사이트에서 "Download jPCT-AE"를 통해 압축파일을 받으면 내부에 jpct-ae.jar를 프로젝트 libs폴더에 넣는다.

4. Project -> Properties에 들어가 창이 뜨면 Java Build Path > Libraries탭을 누른뒤 우측에 Add JARs... 버튼을 눌러 추가한 libs에 jpct-ae.jar를 참고시킨다.

5. 사이트에서 The source code of the demo 링크를 통해 소스를 받고 프로젝트내 ...Activity.java 소스를 대체시킨다. 물론 클래스명은 잘 바꿔줘야 겠지? 소스내에 import com.threed.jpct.R에서 에러가 발생하면 주석처리까지!

6. 사이트에서 The resources used in the demo 링크를 통해 res.zip을 받아 압축을 푼뒤 프로젝트의 res를 대체한다. 

7. 그리고 실행하면 저 화면이 나옴! 


꽤 안정되고 버전업이 잘되고 있는 프로젝트이므로 3D 개발물을 만들때 참고하면 좋을 것이다. 한동안 이걸로 장난칠 듯....


소스도 구경하삼!


package com.threed.jpct.example01;

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.content.res.Resources;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Loader;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
//import com.threed.jpct.R;
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.MemoryHelper;

/**
 * A simple demo. Slightly more advanced than the HelloWorld example, but still
 * pretty basic.
 * 
 * @author EgonOlsen
 * 
 */
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 int move = 0;
	private float turn = 0;
	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 Texture planeReplace = null;

	private Object3D plane = null;
	private Object3D tree2 = null;
	private Object3D tree1 = null;
	private Object3D grass = null;
	private Texture font = null;

	private Object3D box = null;
	private Object3D rock = null;

	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() {
		Logger.log("onPause");
		super.onPause();
		mGLView.onPause();
	}

	@Override
	protected void onResume() {
		Logger.log("onResume");
		super.onResume();
		mGLView.onResume();
	}

	protected void onStop() {
		Logger.log("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) {
			// Doesn't matter here...
		}

		return super.onTouchEvent(me);
	}

	public boolean onKeyDown(int keyCode, KeyEvent msg) {

		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			move = 2;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			move = -2;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			turn = 0.05f;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			turn = -0.05f;
			return true;
		}

		return super.onKeyDown(keyCode, msg);
	}

	public boolean onKeyUp(int keyCode, KeyEvent msg) {
		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			move = 0;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			move = 0;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			turn = 0;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			turn = 0;
			return true;
		}

		if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
			TextureManager.getInstance().replaceTexture("grassy", planeReplace);
			return true;
		}

		return super.onKeyUp(keyCode, msg);
	}

	protected boolean isFullscreenOpaque() {
		return true;
	}

	class MyRenderer implements GLSurfaceView.Renderer {

		private int fps = 0;
		private int lfps = 0;

		private long time = System.currentTimeMillis();

		private boolean stop = false;

		private SimpleVector sunRot = new SimpleVector(0, 0.05f, 0);

		public MyRenderer() {
			Config.maxPolysVisible = 500;
			Config.farPlane = 1500;
			Config.glTransparencyMul = 0.1f;
			Config.glTransparencyOffset = 0.1f;
			Config.useVBO=true;
			
			Texture.defaultToMipmapping(true);
			Texture.defaultTo4bpp(true);
		}

		public void stop() {
			stop = true;
		}

		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();
				Resources res = getResources();

				TextureManager tm = TextureManager.getInstance();

				Texture grass2 = new Texture(res.openRawResource(R.raw.grassy));
				Texture leaves = new Texture(res.openRawResource(R.raw.tree2y));
				Texture leaves2 = new Texture(res.openRawResource(R.raw.tree3y));
				Texture rocky = new Texture(res.openRawResource(R.raw.rocky));
				Texture planetex = new Texture(res.openRawResource(R.raw.planetex));

				planeReplace = new Texture(res.openRawResource(R.raw.rocky));
				font = new Texture(res.openRawResource(R.raw.numbers));
				font.setMipmap(false);

				tm.addTexture("grass2", grass2);
				tm.addTexture("leaves", leaves);
				tm.addTexture("leaves2", leaves2);
				tm.addTexture("rock", rocky);
				tm.addTexture("grassy", planetex);

				plane = Loader.loadSerializedObject(res.openRawResource(R.raw.serplane));
				rock = Loader.loadSerializedObject(res.openRawResource(R.raw.serrock));
				tree1 = Loader.loadSerializedObject(res.openRawResource(R.raw.sertree1));
				tree2 = Loader.loadSerializedObject(res.openRawResource(R.raw.sertree2));
				grass = Loader.loadSerializedObject(res.openRawResource(R.raw.sergrass));

				grass.translate(-45, -17, -50);
				grass.rotateZ((float) Math.PI);
				rock.translate(0, 0, -90);
				rock.rotateX(-(float) Math.PI / 2);
				tree1.translate(-50, -92, -50);
				tree1.rotateZ((float) Math.PI);
				tree2.translate(60, -95, 10);
				tree2.rotateZ((float) Math.PI);
				plane.rotateX((float) Math.PI / 2f);
				plane.setName("plane");
				tree1.setName("tree1");
				tree2.setName("tree2");
				grass.setName("grass");
				rock.setName("rock");

				world.addObject(plane);
				world.addObject(tree1);
				world.addObject(tree2);
				world.addObject(grass);
				world.addObject(rock);

				plane.strip();
				tree1.strip();
				tree2.strip();
				grass.strip();
				rock.strip();

				grass.setTransparency(10);
				tree1.setTransparency(40);
				tree2.setTransparency(40);

				RGBColor dark = new RGBColor(100, 100, 100);
				tree1.setAdditionalColor(dark);
				tree2.setAdditionalColor(dark);
				grass.setAdditionalColor(dark);

				world.setAmbientLight(20, 20, 20);
				world.buildAllObjects();

				sun = new Light(world);
				sun.setIntensity(250, 250, 250);

				Camera cam = world.getCamera();
				cam.moveCamera(Camera.CAMERA_MOVEOUT, 250);
				cam.moveCamera(Camera.CAMERA_MOVEUP, 100);
				cam.lookAt(plane.getTransformedCenter());

				SimpleVector sv = new SimpleVector();
				sv.set(plane.getTransformedCenter());
				sv.y -= 300;
				sv.x -= 100;
				sv.z += 200;
				sun.setPosition(sv);

				MemoryHelper.compact();

				if (master == null) {
					Logger.log("Saving master Activity!");
					master = DemoActivity.this;
				}
			}
		}

		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			Logger.log("onSurfaceCreated");
		}

		public void onDrawFrame(GL10 gl) {

			try {
				if (!stop) {
					Camera cam = world.getCamera();
					if (turn != 0) {
						world.getCamera().rotateY(-turn);
					}

					if (touchTurn != 0) {
						world.getCamera().rotateY(touchTurn);
						touchTurn = 0;
					}

					if (touchTurnUp != 0) {
						world.getCamera().rotateX(touchTurnUp);
						touchTurnUp = 0;
					}

					if (move != 0) {
						world.getCamera().moveCamera(cam.getDirection(), move);
					}

					fb.clear(back);
					world.renderScene(fb);
					world.draw(fb);
					blitNumber(lfps, 5, 5);

					fb.display();

					if (box != null) {
						box.rotateX(0.01f);
					}

					if (sun != null) {
						sun.rotate(sunRot, plane.getTransformedCenter());
					}

					if (System.currentTimeMillis() - time >= 1000) {
						lfps = fps;
						fps = 0;
						time = System.currentTimeMillis();
					}
					fps++;
				} else {
					if (fb != null) {
						fb.dispose();
						fb = null;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				Logger.log("Drawing thread terminated!", Logger.MESSAGE);
			}
		}

		private void blitNumber(int number, int x, int y) {
			if (font != null) {
				String sNum = Integer.toString(number);

				for (int i = 0; i < sNum.length(); i++) {
					char cNum = sNum.charAt(i);
					int iNum = cNum - 48;
					fb.blit(font, iNum * 5, 0, x, y, 5, 9, FrameBuffer.TRANSPARENT_BLITTING);
					x += 5;
				}
			}
		}
	}

}


jPCT-AE를 통해 만들어진 게임들도 있네.


글쓴이 : 지돌스타 (http://blog.jidolstar.com/824)

저작자 표시 비영리 동일 조건 변경 허락

Android , , , , ,