483 lines
13 KiB
C++
483 lines
13 KiB
C++
/*
|
|
* 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 <jni.h>
|
|
#include <errno.h>
|
|
|
|
#include <vector>
|
|
|
|
#include <EGL/egl.h>
|
|
#include <GLES/gl.h>
|
|
|
|
#include <android/sensor.h>
|
|
#include <android/log.h>
|
|
#include <android_native_app_glue.h>
|
|
#include <android/native_window_jni.h>
|
|
#include <cpu-features.h>
|
|
|
|
#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();
|
|
}
|
|
}
|
|
}
|