2 * PROJECT: NyARToolkit for Android SDK
3 * --------------------------------------------------------------------------------
4 * This work is based on the original ARToolKit developed by
7 * HITLab, University of Washington, Seattle
8 * http://www.hitl.washington.edu/artoolkit/
10 * NyARToolkit for Android SDK
11 * Copyright (C)2010 NyARToolkit for Android team
12 * Copyright (C)2010 R.Iizuka(nyatla)
14 * This program is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 * For further information please contact.
28 * http://sourceforge.jp/projects/nyartoolkit-and/
30 * This work is based on the NyARToolKit developed by
32 * http://nyatla.jp/nyatoolkit/
38 package jp.androidgroup.nyartoolkit;
40 import java.io.InputStream;
41 import java.util.ArrayList;
43 import com.android.camera.CameraHardwareException;
44 import com.android.camera.CameraHolder;
47 import min3d.animation.AnimationObject3d;
48 import min3d.core.Renderer;
49 import min3d.core.Scene;
50 import min3d.parser.IParser;
51 import min3d.parser.Parser;
52 import min3d.vos.Light;
53 import min3d.vos.TextureVo;
55 import android.app.Activity;
56 import android.app.Dialog;
57 import android.app.ProgressDialog;
58 import android.content.Intent;
59 import android.content.SharedPreferences;
60 import android.content.res.Configuration;
61 import android.content.res.Resources;
62 import android.graphics.Bitmap;
63 import android.graphics.Matrix;
64 import android.graphics.PixelFormat;
65 import android.hardware.Camera;
66 import android.hardware.Camera.PreviewCallback;
67 import android.media.MediaPlayer;
68 import android.opengl.GLSurfaceView;
69 import android.os.Build;
70 import android.os.Bundle;
71 import android.os.Handler;
72 import android.os.Message;
73 import android.preference.PreferenceManager;
74 import android.util.Log;
75 import android.view.MotionEvent;
76 import android.view.OrientationEventListener;
77 import android.view.SurfaceHolder;
78 import android.view.SurfaceView;
79 import android.view.View;
80 import android.view.Window;
81 import android.view.WindowManager;
82 import android.view.ViewGroup.LayoutParams;
83 import android.widget.FrameLayout;
84 import android.widget.ImageView;
86 //public class NyARToolkitAndroidActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback {
87 public class NyARToolkitAndroidActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback, min3d.interfaces.ISceneController {
89 public static final String TAG = "NyARToolkitAndroid";
91 private static final int CROP_MSG = 1;
92 private static final int FIRST_TIME_INIT = 2;
93 private static final int RESTART_PREVIEW = 3;
94 private static final int CLEAR_SCREEN_DELAY = 4;
95 private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 5;
96 public static final int SHOW_LOADING = 6;
97 public static final int HIDE_LOADING = 7;
99 private static final int SCREEN_DELAY = 2 * 60 * 1000;
101 private android.hardware.Camera.Parameters mParameters;
103 private OrientationEventListener mOrientationListener;
104 private int mLastOrientation = 0;
105 private SharedPreferences mPreferences;
107 private static final int IDLE = 1;
108 private static final int SNAPSHOT_IN_PROGRESS = 2;
110 private int mStatus = IDLE;
112 private android.hardware.Camera mCameraDevice;
113 private SurfaceView mSurfaceView;
114 private SurfaceHolder mSurfaceHolder = null;
115 private boolean mStartPreviewFail = false;
117 private GLSurfaceView mGLSurfaceView = null;
118 // Renderer for metasequoia model
119 // private ModelRenderer mRenderer;
121 private Renderer mRenderer;
123 private boolean mPreviewing;
124 private boolean mPausing;
125 private boolean mFirstTimeInitialized;
127 private Handler mHandler = new MainHandler();
129 private PreviewCallback mPreviewCallback = new PreviewCallback();
131 private ARToolkitDrawer arToolkitDrawer = null;
133 private MediaPlayer mMediaPlayer = null;
135 /** This Handler is used to post message back onto the main thread of the application */
136 private class MainHandler extends Handler {
138 public void handleMessage(Message msg) {
140 case RESTART_PREVIEW: {
145 case CLEAR_SCREEN_DELAY: {
146 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
150 case FIRST_TIME_INIT: {
151 initializeFirstTime();
156 showDialog(DIALOG_LOADING);
161 dismissDialog(DIALOG_LOADING);
162 removeDialog(DIALOG_LOADING);
163 } catch (IllegalArgumentException e) {
171 private static final int DIALOG_LOADING = 0;
174 protected Dialog onCreateDialog(int id) {
176 case DIALOG_LOADING: {
177 ProgressDialog dialog = new ProgressDialog(this);
178 dialog.setMessage("Loading ...");
179 // dialog.setIndeterminate(true);
180 dialog.setCancelable(false);
181 dialog.getWindow().setFlags
182 (WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
183 WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
187 return super.onCreateDialog(id);
191 public static int roundOrientation(int orientationInput) {
192 // Log.d("roundOrientation", "orientationInput:" + orientationInput);
193 int orientation = orientationInput;
194 if (orientation == -1)
197 orientation = orientation % 360;
199 if (orientation < (0*90) + 45) {
201 } else if (orientation < (1*90) + 45) {
203 } else if (orientation < (2*90) + 45) {
205 } else if (orientation < (3*90) + 45) {
214 // Snapshots can only be taken after this is called. It should be called
215 // once only. We could have done these things in onCreate() but we want to
216 // make preview screen appear as soon as possible.
217 private void initializeFirstTime() {
218 if (mFirstTimeInitialized) return;
220 Log.d(TAG, "initializeFirstTime");
222 // Create orientation listenter. This should be done first because it
223 // takes some time to get first orientation.
224 mOrientationListener =
225 new OrientationEventListener(this) {
227 public void onOrientationChanged(int orientation) {
228 // We keep the last known orientation. So if the user
229 // first orient the camera then point the camera to
230 // floor/sky, we still have the correct orientation.
231 if (orientation != ORIENTATION_UNKNOWN) {
234 orientation = roundOrientation(orientation);
235 if (orientation != mLastOrientation) {
236 mLastOrientation = orientation;
240 mOrientationListener.enable();
242 mFirstTimeInitialized = true;
244 changeGLSurfaceViewState();
247 // If the activity is paused and resumed, this method will be called in
249 private void initializeSecondTime() {
250 Log.d(TAG, "initializeSecondTime");
252 // Start orientation listener as soon as possible because it takes
253 // some time to get first orientation.
254 mOrientationListener.enable();
256 changeGLSurfaceViewState();
260 * Callback interface used to deliver copies of preview frames as they are displayed.
262 private final class PreviewCallback
263 implements android.hardware.Camera.PreviewCallback {
266 public void onPreviewFrame(byte [] data, Camera camera) {
267 Log.d(TAG, "PreviewCallback.onPreviewFrame");
274 Log.d(TAG, "data exist");
276 if (arToolkitDrawer != null)
277 arToolkitDrawer.draw(data);
281 // The measure against over load.
283 } catch (InterruptedException e) {
291 /** Called with the activity is first created. */
293 public void onCreate(Bundle icicle) {
294 super.onCreate(icicle);
296 // Renderer for metasequoia model
297 // String[] modelName = new String[2];
298 // modelName[0] = "droid.mqo";
299 // modelName[1] = "miku01.mqo";
300 // float[] modelScale = new float[] {0.01f, 0.03f};
301 // mRenderer = new ModelRenderer(getAssets(), modelName, modelScale);
302 // mRenderer.setMainHandler(mHandler);
305 _initSceneHander = new Handler();
306 _updateSceneHander = new Handler();
309 // These 4 lines are important.
311 Shared.context(this);
312 scene = new Scene(this);
313 scene.backgroundTransparent(true);
314 mRenderer = new Renderer(scene);
315 Shared.renderer(mRenderer);
317 requestWindowFeature(Window.FEATURE_PROGRESS);
319 Window win = getWindow();
320 win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
321 win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
323 setContentView(R.layout.main);
324 mSurfaceView = (SurfaceView) findViewById(R.id.camera_preview);
325 mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
327 mSurfaceView.setKeepScreenOn(true);
329 // don't set mSurfaceHolder here. We have it set ONLY within
330 // surfaceChanged / surfaceDestroyed, other parts of the code
331 // assume that when it is set, the surface is also set.
332 SurfaceHolder holder = mSurfaceView.getHolder();
333 holder.addCallback(this);
334 holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
337 private void changeGLSurfaceViewState() {
338 // If the camera resumes behind the lock screen, the orientation
339 // will be portrait. That causes OOM when we try to allocation GPU
340 // memory for the GLSurfaceView again when the orientation changes. So,
341 // we delayed initialization of GLSurfaceView until the orientation
342 // becomes landscape.
343 Configuration config = getResources().getConfiguration();
344 if (config.orientation == Configuration.ORIENTATION_LANDSCAPE
345 && !mPausing && mFirstTimeInitialized) {
346 if (mGLSurfaceView == null) initializeGLSurfaceView();
347 } else if (mGLSurfaceView != null) {
348 finalizeGLSurfaceView();
352 private void initializeGLSurfaceView() {
355 if (arToolkitDrawer == null) {
356 InputStream camePara = getResources().openRawResource(R.raw.camera_para);
357 int[] width = new int[2];
358 for (int i = 0; i < 2; i++) {
361 ArrayList<InputStream> patt = new ArrayList<InputStream>();
362 patt.add(getResources().openRawResource(R.raw.patthiro));
363 patt.add(getResources().openRawResource(R.raw.pattkanji));
364 arToolkitDrawer = new ARToolkitDrawer(camePara, width, patt, mRenderer);
367 // mMediaPlayer = MediaPlayer.create(this, R.raw.miku_voice);
368 // mMediaPlayer.setLooping(true);
369 // mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
370 // public void onPrepared(MediaPlayer mediaplayer) {
371 // arToolkitDrawer.setMediaPlayer(mediaplayer);
376 FrameLayout frame = (FrameLayout) findViewById(R.id.frame);
377 mGLSurfaceView = new GLSurfaceView(this);
378 mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
379 mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
380 mGLSurfaceView.setZOrderOnTop(true);
381 mGLSurfaceView.setRenderer(mRenderer);
382 frame.addView(mGLSurfaceView);
385 private void finalizeGLSurfaceView() {
386 FrameLayout frame = (FrameLayout) findViewById(R.id.frame);
387 frame.removeView(mGLSurfaceView);
388 mGLSurfaceView = null;
390 if (mMediaPlayer != null)
391 mMediaPlayer.release();
394 arToolkitDrawer = null;
398 protected void onDestroy() {
403 public void onStart() {
408 public void onStop() {
413 public void onClick(View v) {
418 public void onResume() {
420 Log.d(TAG, "onResume");
424 // Start the preview if it is not started.
425 if (!mPreviewing && !mStartPreviewFail && (mSurfaceHolder != null)) {
428 } catch (Exception e) {
429 showCameraErrorAndFinish();
434 if (mSurfaceHolder != null) {
435 // If first time initialization is not finished, put it in the
437 if (!mFirstTimeInitialized) {
438 mHandler.sendEmptyMessage(FIRST_TIME_INIT);
440 initializeSecondTime();
443 keepScreenOnAwhile();
447 protected void onPause() {
448 Log.d(TAG, "onPause");
451 // Close the camera now because other activities may need to use it.
454 changeGLSurfaceViewState();
456 if (mFirstTimeInitialized) {
457 mOrientationListener.disable();
460 // Remove the messages in the event queue.
461 mHandler.removeMessages(RESTART_PREVIEW);
462 mHandler.removeMessages(FIRST_TIME_INIT);
468 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
469 switch (requestCode) {
471 Intent intent = new Intent();
473 Bundle extras = data.getExtras();
474 if (extras != null) {
475 intent.putExtras(extras);
478 setResult(resultCode, intent);
486 public boolean onTouchEvent(MotionEvent event) {
487 switch (event.getAction()) {
488 case MotionEvent.ACTION_DOWN:
491 case MotionEvent.ACTION_MOVE:
494 case MotionEvent.ACTION_UP:
501 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
502 Log.d(TAG, "surfaceChanged");
504 // Make sure we have a surface in the holder before proceeding.
505 if (holder.getSurface() == null) {
506 Log.d(TAG, "holder.getSurface() == null");
510 // We need to save the holder for later use, even when the mCameraDevice
511 // is null. This could happen if onResume() is invoked after this
513 mSurfaceHolder = holder;
515 // The mCameraDevice will be null if it fails to connect to the camera
516 // hardware. In this case we will show a dialog and then finish the
517 // activity, so it's OK to ignore it.
518 if (mCameraDevice == null) {
521 * To reduce startup time, we start the preview in another thread.
522 * We make sure the preview is started at the end of surfaceChanged.
524 Thread startPreviewThread = new Thread(new Runnable() {
527 mStartPreviewFail = false;
529 } catch (Exception e) {
530 // In eng build, we throw the exception so that test tool
531 // can detect it and report it
532 if ("eng".equals(Build.TYPE)) {
533 throw new RuntimeException(e);
535 mStartPreviewFail = true;
539 startPreviewThread.start();
541 // Make sure preview is started.
543 startPreviewThread.join();
544 if (mStartPreviewFail) {
545 showCameraErrorAndFinish();
548 } catch (InterruptedException ex) {
553 // Sometimes surfaceChanged is called after onPause.
555 if (mPausing || isFinishing()) return;
557 // If first time initialization is not finished, send a message to do
558 // it later. We want to finish surfaceChanged as soon as possible to let
559 // user see preview first.
560 if (!mFirstTimeInitialized) {
561 mHandler.sendEmptyMessage(FIRST_TIME_INIT);
563 initializeSecondTime();
568 public void surfaceCreated(SurfaceHolder holder) {
572 public void surfaceDestroyed(SurfaceHolder holder) {
574 mSurfaceHolder = null;
577 private void closeCamera() {
578 if (mCameraDevice != null) {
579 CameraHolder.instance().release();
580 mCameraDevice = null;
585 private void ensureCameraDevice() throws CameraHardwareException {
586 if (mCameraDevice == null) {
587 mCameraDevice = CameraHolder.instance().open();
591 private void showCameraErrorAndFinish() {
592 Resources ress = getResources();
593 com.android.camera.Util.showFatalErrorAndFinish(NyARToolkitAndroidActivity.this,
594 ress.getString(R.string.camera_error_title),
595 ress.getString(R.string.cannot_connect_camera));
598 public void restartPreview() {
599 Log.d(TAG, "restartPreview");
602 } catch (CameraHardwareException e) {
603 showCameraErrorAndFinish();
608 private void setPreviewDisplay(SurfaceHolder holder) {
610 mCameraDevice.setPreviewDisplay(holder);
611 } catch (Throwable ex) {
613 throw new RuntimeException("setPreviewDisplay failed", ex);
617 private void startPreview() throws CameraHardwareException {
618 if (mPausing || isFinishing()) return;
620 ensureCameraDevice();
622 // If we're previewing already, stop the preview first (this will blank
624 // FIXME: don't stop for avoiding blank screen.
625 // if (mPreviewing) stopPreview();
627 setPreviewDisplay(mSurfaceHolder);
629 setCameraParameters();
631 mCameraDevice.setOneShotPreviewCallback(mPreviewCallback);
634 Log.v(TAG, "startPreview");
635 mCameraDevice.startPreview();
636 } catch (Throwable ex) {
638 throw new RuntimeException("startPreview failed", ex);
644 private void stopPreview() {
645 if (mCameraDevice != null && mPreviewing) {
646 Log.v(TAG, "stopPreview");
647 mCameraDevice.setOneShotPreviewCallback(null);
648 mCameraDevice.stopPreview();
653 private void setCameraParameters() {
654 mParameters = mCameraDevice.getParameters();
656 mParameters.setPreviewSize(320, 240);
658 mCameraDevice.setParameters(mParameters);
661 private void resetScreenOn() {
662 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
663 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
666 private void keepScreenOnAwhile() {
667 mHandler.removeMessages(CLEAR_SCREEN_DELAY);
668 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
669 mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
672 //----------------------- for min3d ------------------------
675 protected Handler _initSceneHander;
676 protected Handler _updateSceneHander;
678 final Runnable _initSceneRunnable = new Runnable()
685 final Runnable _updateSceneRunnable = new Runnable()
693 * Instantiation of Object3D's, setting their properties, and adding Object3D's
694 * to the scene should be done here. Or any point thereafter.
696 * Note that this method is always called after GLCanvas is created, which occurs
697 * not only on Activity.onCreate(), but on Activity.onResume() as well.
698 * It is the user's responsibility to build the logic to restore state on-resume.
700 * @see min3d.interfaces.ISceneController#initScene()
702 public void initScene()
704 scene.lights().add(new Light());
705 scene.camera().frustum.zFar(10000.0f);
706 //scene.camera().frustum.shortSideLength(0.77f);
709 AnimationObject3d animationObject3d = null;
711 parser = Parser.createParser(Parser.Type.MD2,
712 getResources(), "jp.androidgroup.nyartoolkit:raw/droid", false);
715 animationObject3d = parser.getParsedAnimationObject();
716 animationObject3d.rotation().z = -90.0f;
717 animationObject3d.scale().x = animationObject3d.scale().y = animationObject3d.scale().z = 1.0f;
718 scene.addChild(animationObject3d);
719 animationObject3d.setFps(30);
721 parser = Parser.createParser(Parser.Type.MD2,
722 getResources(), "jp.androidgroup.nyartoolkit:raw/droidr", false);
725 animationObject3d = parser.getParsedAnimationObject();
726 animationObject3d.rotation().z = -90.0f;
727 animationObject3d.scale().x = animationObject3d.scale().y = animationObject3d.scale().z = 1.0f;
728 scene.addChild(animationObject3d);
729 animationObject3d.setFps(90);
733 * All manipulation of scene and Object3D instance properties should go here.
734 * Gets called on every frame, right before drawing.
736 * @see min3d.interfaces.ISceneController#updateScene()
738 public void updateScene()
743 * Called _after_ scene init (ie, after initScene).
744 * Unlike initScene(), is thread-safe.
746 public void onInitScene()
751 * Called _after_ scene init (ie, after initScene).
752 * Unlike initScene(), is thread-safe.
754 public void onUpdateScene()
759 * @see min3d.interfaces.ISceneController#getInitSceneHandler()
761 public Handler getInitSceneHandler()
763 return _initSceneHander;
767 * @see min3d.interfaces.ISceneController#getUpdateSceneHandler()
769 public Handler getUpdateSceneHandler()
771 return _updateSceneHander;
775 * @see min3d.interfaces.ISceneController#getInitSceneRunnable()
777 public Runnable getInitSceneRunnable()
779 return _initSceneRunnable;
783 * @see min3d.interfaces.ISceneController#getUpdateSceneRunnable()
785 public Runnable getUpdateSceneRunnable()
787 return _updateSceneRunnable;
789 //----------------------- for min3d ------------------------