/* * Copyright 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //-------------------------------------------------------------------------------- // Include files //-------------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include "TeapotRenderer.h" #include "NDKHelper.h" //------------------------------------------------------------------------- //Shared state for our app. //------------------------------------------------------------------------- struct android_app; class engine { TeapotRenderer _renderer; GLContext* _glContext; bool _bInitializedResources; bool _bHasFocus; DoubletapDetector _doubletapDetector; PinchDetector _pinchDetector; DragDetector _dragDetector; perfMonitor _monitor; tapCamera _tapCamera; android_app* _app; ASensorManager* _sensorManager; const ASensor* _accelerometerSensor; ASensorEventQueue* _sensorEventQueue; void updateFPS(float fFPS); void showUI(); void transformPosition( vec2& vec ); public: static void handleCmd(struct android_app* app, int32_t cmd); static int32_t handleInput( android_app* app, AInputEvent* event ); engine(); ~engine(); void setState(android_app* state); int initDisplay(); void loadResources(); void unloadResources(); void drawFrame(); void termDisplay(); void trimMemory(); bool isReady(); void updatePosition( AInputEvent* event, int32_t iIndex, float& fX, float& fY); void initSensors(); void processSensors( int32_t id ); void suspendSensors(); void resumeSensors(); }; //------------------------------------------------------------------------- //Ctor //------------------------------------------------------------------------- engine::engine() : _bInitializedResources( false ), _bHasFocus( false ), _app( NULL ), _sensorManager( NULL ), _accelerometerSensor( NULL ), _sensorEventQueue( NULL ) { _glContext = GLContext::getInstance(); } //------------------------------------------------------------------------- //Dtor //------------------------------------------------------------------------- engine::~engine() { } /** * Load resources */ void engine::loadResources() { _renderer.init(); _renderer.bind(&_tapCamera); } /** * Unload resources */ void engine::unloadResources() { _renderer.unload(); } /** * Initialize an EGL context for the current display. */ int engine::initDisplay() { if( !_bInitializedResources ) { _glContext->init( _app->window ); loadResources(); _bInitializedResources = true; } else { // initialize OpenGL ES and EGL if( EGL_SUCCESS != _glContext->resume( _app->window ) ) { unloadResources(); loadResources(); } } showUI(); // Initialize GL state. glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); //Note that screen size might have been changed glViewport(0, 0, _glContext->getScreenWidth(), _glContext->getScreenHeight() ); _renderer.updateViewport(); _tapCamera.setFlip(1.f, -1.f, -1.f); _tapCamera.setPinchTransformFactor(2.f, 2.f, 8.f); return 0; } /** * Just the current frame in the display. */ void engine::drawFrame() { float fFPS; if( _monitor.update(fFPS) ) { updateFPS( fFPS ); } double dTime = _monitor.getCurrentTime(); _renderer.update(dTime); // Just fill the screen with a color. glClearColor(0.5f, 0.5f, 0.5f, 1.f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _renderer.render(); // Swap if( EGL_SUCCESS != _glContext->swap() ) { unloadResources(); loadResources(); } } /** * Tear down the EGL context currently associated with the display. */ void engine::termDisplay() { _glContext->suspend(); } void engine::trimMemory() { LOGI( "Trimming memory" ); _glContext->invalidate(); } /** * Process the next input event. */ int32_t engine::handleInput( android_app* app, AInputEvent* event ) { engine* eng = (engine*)app->userData; if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { GESTURE_STATE doubleTapState = eng->_doubletapDetector.detect(event); GESTURE_STATE dragState = eng->_dragDetector.detect(event); GESTURE_STATE pinchState = eng->_pinchDetector.detect(event); //Double tap detector has a priority over other detectors if( doubleTapState == GESTURE_STATE_ACTION ) { //Detect double tap eng->_tapCamera.reset(true); } else { //Handle drag state if( dragState & GESTURE_STATE_START ) { //Otherwise, start dragging vec2 v; eng->_dragDetector.getPointer( v ); eng->transformPosition( v ); eng->_tapCamera.beginDrag( v ); } else if( dragState & GESTURE_STATE_MOVE ) { vec2 v; eng->_dragDetector.getPointer( v ); eng->transformPosition( v ); eng->_tapCamera.drag( v ); } else if( dragState & GESTURE_STATE_END ) { eng->_tapCamera.endDrag(); } //Handle pinch state if( pinchState & GESTURE_STATE_START ) { //Start new pinch vec2 v1; vec2 v2; eng->_pinchDetector.getPointers( v1, v2 ); eng->transformPosition( v1 ); eng->transformPosition( v2 ); eng->_tapCamera.beginPinch( v1, v2 ); } else if( pinchState & GESTURE_STATE_MOVE ) { //Multi touch //Start new pinch vec2 v1; vec2 v2; eng->_pinchDetector.getPointers( v1, v2 ); eng->transformPosition( v1 ); eng->transformPosition( v2 ); eng->_tapCamera.pinch( v1, v2 ); } } return 1; } return 0; } /** * Process the next main command. */ void engine::handleCmd(struct android_app* app, int32_t cmd) { engine* eng = (engine*)app->userData; switch (cmd) { case APP_CMD_SAVE_STATE: break; case APP_CMD_INIT_WINDOW: // The window is being shown, get it ready. if (app->window != NULL) { eng->initDisplay(); eng->drawFrame(); } break; case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. eng->termDisplay(); eng->_bHasFocus = false; break; case APP_CMD_STOP: break; case APP_CMD_GAINED_FOCUS: eng->resumeSensors(); //Start animation eng->_bHasFocus = true; break; case APP_CMD_LOST_FOCUS: eng->suspendSensors(); // Also stop animating. eng->_bHasFocus = false; eng->drawFrame(); break; case APP_CMD_LOW_MEMORY: //Free up GL resources eng->trimMemory(); break; } } //------------------------------------------------------------------------- //Sensor handlers //------------------------------------------------------------------------- void engine::initSensors() { _sensorManager = ASensorManager_getInstance(); _accelerometerSensor = ASensorManager_getDefaultSensor( _sensorManager, ASENSOR_TYPE_ACCELEROMETER); _sensorEventQueue = ASensorManager_createEventQueue( _sensorManager, _app->looper, LOOPER_ID_USER, NULL, NULL); } void engine::processSensors( int32_t id ) { // If a sensor has data, process it now. if( id == LOOPER_ID_USER ) { if (_accelerometerSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(_sensorEventQueue, &event, 1) > 0) { } } } } void engine::resumeSensors() { // When our app gains focus, we start monitoring the accelerometer. if (_accelerometerSensor != NULL) { ASensorEventQueue_enableSensor(_sensorEventQueue, _accelerometerSensor); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate(_sensorEventQueue, _accelerometerSensor, (1000L/60)*1000); } } void engine::suspendSensors() { // When our app loses focus, we stop monitoring the accelerometer. // This is to avoid consuming battery while not being used. if (_accelerometerSensor != NULL) { ASensorEventQueue_disableSensor(_sensorEventQueue, _accelerometerSensor); } } //------------------------------------------------------------------------- //Misc //------------------------------------------------------------------------- void engine::setState(android_app* state) { _app = state; _doubletapDetector.setConfiguration( _app->config ); _dragDetector.setConfiguration( _app->config ); _pinchDetector.setConfiguration( _app->config ); } bool engine::isReady() { if( _bHasFocus ) return true; return false; } void engine::transformPosition( vec2& vec ) { vec = vec2( 2.0f, 2.0f ) * vec / vec2( _glContext->getScreenWidth(), _glContext->getScreenHeight() ) - vec2( 1.f, 1.f ); } void engine::showUI() { JNIEnv *jni; _app->activity->vm->AttachCurrentThread(&jni, NULL); //Default class retrieval jclass clazz = jni->GetObjectClass( _app->activity->clazz ); jmethodID methodID = jni->GetMethodID(clazz, "showUI", "()V" ); jni->CallVoidMethod( _app->activity->clazz, methodID ); _app->activity->vm->DetachCurrentThread(); return; } void engine::updateFPS(float fFPS) { JNIEnv *jni; _app->activity->vm->AttachCurrentThread(&jni, NULL); //Default class retrieval jclass clazz = jni->GetObjectClass(_app->activity->clazz); jmethodID methodID = jni->GetMethodID(clazz, "updateFPS", "(F)V"); jni->CallVoidMethod(_app->activity->clazz, methodID, fFPS); _app->activity->vm->DetachCurrentThread(); return; } engine g_engine; /** * This is the main entry point of a native application that is using * android_native_app_glue. It runs in its own thread, with its own * event loop for receiving input events and doing other things. */ void android_main(android_app* state) { app_dummy(); g_engine.setState( state ); //Init helper functions JNIHelper::init( state->activity ); state->userData = &g_engine; state->onAppCmd = engine::handleCmd; state->onInputEvent = engine::handleInput; #ifdef USE_NDK_PROFILER monstartup("libNativeActivity.so"); #endif // Prepare to monitor accelerometer g_engine.initSensors(); // loop waiting for stuff to do. while (1) { // Read all pending events. int id; int events; android_poll_source* source; // If not animating, we will block forever waiting for events. // If animating, we loop until all events are read, then continue // to draw the next frame of animation. while ((id = ALooper_pollAll( g_engine.isReady() ? 0 : -1, NULL, &events, (void**)&source) ) >= 0) { // Process this event. if (source != NULL) source->process(state, source); g_engine.processSensors( id ); // Check if we are exiting. if (state->destroyRequested != 0) { g_engine.termDisplay(); return; } } if( g_engine.isReady() ) { // Drawing is throttled to the screen update rate, so there // is no need to do timing here. g_engine.drawFrame(); } } }