/* * 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 "MoreTeapotsRenderer.h" //------------------------------------------------------------------------- //Preprocessor //------------------------------------------------------------------------- #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function //------------------------------------------------------------------------- //Constants //------------------------------------------------------------------------- const int32_t NUM_TEAPOTS_X = 8; const int32_t NUM_TEAPOTS_Y = 8; const int32_t NUM_TEAPOTS_Z = 8; //------------------------------------------------------------------------- //Shared state for our app. //------------------------------------------------------------------------- struct android_app; class Engine { MoreTeapotsRenderer renderer_; ndk_helper::GLContext* gl_context_; bool initialized_resources_; bool has_focus_; ndk_helper::DoubletapDetector doubletap_detector_; ndk_helper::PinchDetector pinch_detector_; ndk_helper::DragDetector drag_detector_; ndk_helper::PerfMonitor monitor_; ndk_helper::TapCamera tap_camera_; android_app* app_; ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_; void UpdateFPS( float fps ); void ShowUI(); void TransformPosition( ndk_helper::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 index, float& x, float& y ); void InitSensors(); void ProcessSensors( int32_t id ); void SuspendSensors(); void ResumeSensors(); }; //------------------------------------------------------------------------- //Ctor //------------------------------------------------------------------------- Engine::Engine() : initialized_resources_( false ), has_focus_( false ), app_( NULL ), sensor_manager_( NULL ), accelerometer_sensor_( NULL ), sensor_event_queue_( NULL ) { gl_context_ = ndk_helper::GLContext::GetInstance(); } //------------------------------------------------------------------------- //Dtor //------------------------------------------------------------------------- Engine::~Engine() { } /** * Load resources */ void Engine::LoadResources() { renderer_.Init( NUM_TEAPOTS_X, NUM_TEAPOTS_Y, NUM_TEAPOTS_Z ); renderer_.Bind( &tap_camera_ ); } /** * Unload resources */ void Engine::UnloadResources() { renderer_.Unload(); } /** * Initialize an EGL context for the current display. */ int Engine::InitDisplay() { if( !initialized_resources_ ) { gl_context_->Init( app_->window ); LoadResources(); initialized_resources_ = true; } else { // initialize OpenGL ES and EGL if( EGL_SUCCESS != gl_context_->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, gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() ); renderer_.UpdateViewport(); tap_camera_.SetFlip( 1.f, -1.f, -1.f ); tap_camera_.SetPinchTransformFactor( 10.f, 10.f, 8.f ); return 0; } /** * Just the current frame in the display. */ void Engine::DrawFrame() { float fps; if( monitor_.Update( fps ) ) { UpdateFPS( fps ); } 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 != gl_context_->Swap() ) { UnloadResources(); LoadResources(); } } /** * Tear down the EGL context currently associated with the display. */ void Engine::TermDisplay() { gl_context_->Suspend(); } void Engine::TrimMemory() { LOGI( "Trimming memory" ); gl_context_->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 ) { ndk_helper::GESTURE_STATE doubleTapState = eng->doubletap_detector_.Detect( event ); ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event ); ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event ); //Double tap detector has a priority over other detectors if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION ) { //Detect double tap eng->tap_camera_.Reset( true ); } else { //Handle drag state if( dragState & ndk_helper::GESTURE_STATE_START ) { //Otherwise, start dragging ndk_helper::Vec2 v; eng->drag_detector_.GetPointer( v ); eng->TransformPosition( v ); eng->tap_camera_.BeginDrag( v ); } else if( dragState & ndk_helper::GESTURE_STATE_MOVE ) { ndk_helper::Vec2 v; eng->drag_detector_.GetPointer( v ); eng->TransformPosition( v ); eng->tap_camera_.Drag( v ); } else if( dragState & ndk_helper::GESTURE_STATE_END ) { eng->tap_camera_.EndDrag(); } //Handle pinch state if( pinchState & ndk_helper::GESTURE_STATE_START ) { //Start new pinch ndk_helper::Vec2 v1; ndk_helper::Vec2 v2; eng->pinch_detector_.GetPointers( v1, v2 ); eng->TransformPosition( v1 ); eng->TransformPosition( v2 ); eng->tap_camera_.BeginPinch( v1, v2 ); } else if( pinchState & ndk_helper::GESTURE_STATE_MOVE ) { //Multi touch //Start new pinch ndk_helper::Vec2 v1; ndk_helper::Vec2 v2; eng->pinch_detector_.GetPointers( v1, v2 ); eng->TransformPosition( v1 ); eng->TransformPosition( v2 ); eng->tap_camera_.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->has_focus_ = false; break; case APP_CMD_STOP: break; case APP_CMD_GAINED_FOCUS: eng->ResumeSensors(); //Start animation eng->has_focus_ = true; break; case APP_CMD_LOST_FOCUS: eng->SuspendSensors(); // Also stop animating. eng->has_focus_ = false; eng->DrawFrame(); break; case APP_CMD_LOW_MEMORY: //Free up GL resources eng->TrimMemory(); break; } } //------------------------------------------------------------------------- //Sensor handlers //------------------------------------------------------------------------- void Engine::InitSensors() { sensor_manager_ = ASensorManager_getInstance(); accelerometer_sensor_ = ASensorManager_getDefaultSensor( sensor_manager_, ASENSOR_TYPE_ACCELEROMETER ); sensor_event_queue_ = ASensorManager_createEventQueue( sensor_manager_, 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( accelerometer_sensor_ != NULL ) { ASensorEvent event; while( ASensorEventQueue_getEvents( sensor_event_queue_, &event, 1 ) > 0 ) { } } } } void Engine::ResumeSensors() { // When our app gains focus, we start monitoring the accelerometer. if( accelerometer_sensor_ != NULL ) { ASensorEventQueue_enableSensor( sensor_event_queue_, accelerometer_sensor_ ); // We'd like to get 60 events per second (in us). ASensorEventQueue_setEventRate( sensor_event_queue_, accelerometer_sensor_, (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( accelerometer_sensor_ != NULL ) { ASensorEventQueue_disableSensor( sensor_event_queue_, accelerometer_sensor_ ); } } //------------------------------------------------------------------------- //Misc //------------------------------------------------------------------------- void Engine::SetState( android_app* state ) { app_ = state; doubletap_detector_.SetConfiguration( app_->config ); drag_detector_.SetConfiguration( app_->config ); pinch_detector_.SetConfiguration( app_->config ); } bool Engine::IsReady() { if( has_focus_ ) return true; return false; } void Engine::TransformPosition( ndk_helper::Vec2& vec ) { vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec / ndk_helper::Vec2( gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() ) - ndk_helper::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 fps ) { 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, fps ); 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 ndk_helper::JNIHelper::GetInstance()->Init( state->activity, HELPER_CLASS_NAME ); state->userData = &g_engine; state->onAppCmd = Engine::HandleCmd; state->onInputEvent = Engine::HandleInput; #ifdef USE_NDK_PROFILER monstartup("libMoreTeapotsNativeActivity.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(); } } }