diff --git a/CMakeLists.txt b/CMakeLists.txt index 235c08b18b..90e6c891af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1327,6 +1327,7 @@ set(NativeAppSource UI/GameScreen.cpp UI/GameSettingsScreen.cpp UI/TiltAnalogSettingsScreen.cpp + UI/TiltEventProcessor.cpp UI/TouchControlLayoutScreen.cpp UI/TouchControlVisibilityScreen.cpp UI/GamepadEmu.cpp diff --git a/Core/Config.cpp b/Core/Config.cpp index 1bfa83571a..a3c78dc48a 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -254,15 +254,14 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { #endif // control->Get("KeyMapping",iMappingMap); #ifdef USING_GLES2 - control->Get("AccelerometerToAnalogHoriz", &bAccelerometerToAnalogHoriz, false); - control->Get("TiltBaseX", &fTiltBaseX, 0); control->Get("TiltBaseY", &fTiltBaseY, 0); control->Get("InvertTiltX", &bInvertTiltX, false); control->Get("InvertTiltY", &bInvertTiltY, true); control->Get("TiltSensitivityX", &iTiltSensitivityX, 100); control->Get("TiltSensitivityY", &iTiltSensitivityY, 100); - control->Get("DeadzoneRadius", &fDeadzoneRadius, 0.35); + control->Get("DeadzoneRadius", &fDeadzoneRadius, 0.2); + control->Get("TiltInputType", &iTiltInputType, 0); #endif control->Get("DisableDpadDiagonals", &bDisableDpadDiagonals, false); @@ -539,7 +538,6 @@ void Config::Save() { control->Set("ShowTouchDpad", bShowTouchDpad); #ifdef USING_GLES2 - control->Set("AccelerometerToAnalogHoriz", bAccelerometerToAnalogHoriz); control->Set("TiltBaseX", fTiltBaseX); control->Set("TiltBaseY", fTiltBaseY); control->Set("InvertTiltX", bInvertTiltX); @@ -547,6 +545,7 @@ void Config::Save() { control->Set("TiltSensitivityX", iTiltSensitivityX); control->Set("TiltSensitivityY", iTiltSensitivityY); control->Set("DeadzoneRadius", fDeadzoneRadius); + control->Set("TiltInputType", iTiltInputType); #endif control->Set("DisableDpadDiagonals", bDisableDpadDiagonals); control->Set("TouchButtonStyle", iTouchButtonStyle); diff --git a/Core/Config.h b/Core/Config.h index 50b1eb95a9..ebc9e1fbd0 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -130,7 +130,6 @@ public: bool bShowDebuggerOnLoad; int iShowFPSCounter; bool bShowDebugStats; - bool bAccelerometerToAnalogHoriz; //Analog stick tilting //the base x and y tilt. this inclination is treated as (0,0) and the tilt input @@ -144,6 +143,9 @@ public: int iTiltSensitivityY; //the deadzone radius of the tilt float fDeadzoneRadius; + //type of tilt input currently selected: Defined in TiltEventProcessor.h + //0 - no tilt, 1 - analog stick, 2 - D-Pad, 3 - Action Buttons (Tri, Cross, Square, Circle) + int iTiltInputType; // The three tabs. bool bGridView1; diff --git a/Qt/PPSSPP.pro b/Qt/PPSSPP.pro index 2be162af44..e54035bc89 100755 --- a/Qt/PPSSPP.pro +++ b/Qt/PPSSPP.pro @@ -83,6 +83,7 @@ SOURCES += $$P/UI/*Screen.cpp \ $$P/UI/GamepadEmu.cpp \ $$P/UI/GameInfoCache.cpp \ $$P/UI/OnScreenDisplay.cpp \ + $$P/UI/TiltEventProcessor.cpp \ $$P/UI/UIShader.cpp \ $$P/UI/ui_atlas_lowmem.cpp \ $$P/android/jni/TestRunner.cpp diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index bd097d3aae..01e5fd031e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -506,6 +506,7 @@ void EmuScreen::update(InputState &input) { // Apply tilt to left stick // TODO: Make into an axis #ifdef USING_GLES2 + /* if (g_Config.bAccelerometerToAnalogHoriz) { // Get the "base" coordinate system which is setup by the calibration system float base_x = g_Config.fTiltBaseX; @@ -538,6 +539,7 @@ void EmuScreen::update(InputState &input) { leftstick_y += clamp1(delta_y); __CtrlSetAnalogY(clamp1(leftstick_y), CTRL_STICK_LEFT); } + */ #endif // Make sure fpsLimit starts at 0 diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index eb2efab402..c2444abddd 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -33,6 +33,7 @@ #include "UI/TouchControlLayoutScreen.h" #include "UI/TouchControlVisibilityScreen.h" #include "UI/TiltAnalogSettingsScreen.h" +#include "UI/TiltEventProcessor.h" #include "Core/Config.h" #include "Core/Host.h" @@ -233,10 +234,12 @@ void GameSettingsScreen::CreateViews() { #if defined(USING_GLES2) controlsSettings->Add(new CheckBox(&g_Config.bHapticFeedback, c->T("HapticFeedback", "Haptic Feedback (vibration)"))); - controlsSettings->Add(new CheckBox(&g_Config.bAccelerometerToAnalogHoriz, c->T("Tilt", "Tilt to Analog (horizontal)"))); - Choice *tiltAnalog = controlsSettings->Add(new Choice(c->T("Customize tilt"))); - tiltAnalog->OnClick.Handle(this, &GameSettingsScreen::OnTiltAnalogSettings); - tiltAnalog->SetEnabledPtr(&g_Config.bAccelerometerToAnalogHoriz); + static const char *tiltTypes[] = { "None (Disabled)", "Analog Stick", "D-PAD", "PSP Action Buttons"}; + controlsSettings->Add(new PopupMultiChoice(&g_Config.iTiltInputType, c->T("Tilt Input Type"), tiltTypes, 0, ARRAY_SIZE(tiltTypes), c, screenManager()))->OnClick.Handle(this, &GameSettingsScreen::OnTiltTypeChange); + + Choice *customizeTilt = controlsSettings->Add(new Choice(c->T("Customize tilt"))); + customizeTilt->OnClick.Handle(this, &GameSettingsScreen::OnTiltCuztomize); + customizeTilt->SetEnabledPtr((bool *)&g_Config.iTiltInputType); //<- dirty int-to-bool cast #endif controlsSettings->Add(new ItemHeader(c->T("OnScreen", "On-Screen Touch Controls"))); controlsSettings->Add(new CheckBox(&g_Config.bShowTouchControls, c->T("OnScreen", "On-Screen Touch Controls"))); @@ -501,7 +504,14 @@ UI::EventReturn GameSettingsScreen::OnTouchControlLayout(UI::EventParams &e) { return UI::EVENT_DONE; }; -UI::EventReturn GameSettingsScreen::OnTiltAnalogSettings(UI::EventParams &e){ +//when the tilt event type is modified, we need to reset all tilt settings. +//refer to the ResetTiltEvents() function for a detailed explanation. +UI::EventReturn GameSettingsScreen::OnTiltTypeChange(UI::EventParams &e){ + TiltEventProcessor::ResetTiltEvents(); + return UI::EVENT_DONE; +}; + +UI::EventReturn GameSettingsScreen::OnTiltCuztomize(UI::EventParams &e){ screenManager()->push(new TiltAnalogSettingsScreen()); return UI::EVENT_DONE; }; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index cb138171c7..df240c479c 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -54,7 +54,8 @@ private: UI::EventReturn OnTouchControlLayout(UI::EventParams &e); UI::EventReturn OnDumpNextFrameToLog(UI::EventParams &e); UI::EventReturn OnReloadCheats(UI::EventParams &e); - UI::EventReturn OnTiltAnalogSettings(UI::EventParams &e); + UI::EventReturn OnTiltTypeChange(UI::EventParams &e); + UI::EventReturn OnTiltCuztomize(UI::EventParams &e); // Global settings handlers UI::EventReturn OnLanguage(UI::EventParams &e); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 7580bc6eef..c9ab0661b6 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -73,6 +73,7 @@ #include "UI/OnScreenDisplay.h" #include "UI/MiscScreens.h" +#include "UI/TiltEventProcessor.h" // The new UI framework, for initialization @@ -669,16 +670,90 @@ void NativeKey(const KeyInput &key) { } void NativeAxis(const AxisInput &key) { - // ILOG("Axis id: %i value: %f", (int)key.axisId, key.value); - if (key.axisId >= JOYSTICK_AXIS_ACCELEROMETER_X && key.axisId <= JOYSTICK_AXIS_ACCELEROMETER_Z) { - // Disable accelerometer as an axis for now. + using namespace TiltEventProcessor; + + + //only handle tilt events if tilt is enabled. + if (g_Config.iTiltInputType == TILT_NULL){ + + //if tilt events are disabled, then run it through the usual way. + if (screenManager) { + screenManager->axis(key); + } + return; } - if (isOuya && key.axisId >= JOYSTICK_AXIS_OUYA_UNKNOWN1 && key.axisId <= JOYSTICK_AXIS_OUYA_UNKNOWN4) { - return; + //create the base coordinate tilt system from the calibration data. + //This is static for no particular reason, can be un-static'ed + static Tilt baseTilt; + baseTilt.x_ = g_Config.fTiltBaseX; baseTilt.y_ = g_Config.fTiltBaseY; + + + //figure out what the current tilt orientation is by checking the axis event + //This is static, since we need to remember where we last were (in terms of orientation) + static Tilt currentTilt; + + switch (key.axisId) { + case JOYSTICK_AXIS_ACCELEROMETER_X: + //x and y are flipped due to landscape orientation. The events are + //sent with respect to the portrait coordinate system, while we + //take all events in landscape. + //see [http://developer.android.com/guide/topics/sensors/sensors_overview.html] for details + currentTilt.y_ = key.value; + break; + + case JOYSTICK_AXIS_ACCELEROMETER_Y: + currentTilt.x_ = key.value; + break; + + + case JOYSTICK_AXIS_ACCELEROMETER_Z: + //don't handle this now as only landscape is enabled. + //TODO: make this generic. + return; + + + case JOYSTICK_AXIS_OUYA_UNKNOWN1: + case JOYSTICK_AXIS_OUYA_UNKNOWN2: + case JOYSTICK_AXIS_OUYA_UNKNOWN3: + case JOYSTICK_AXIS_OUYA_UNKNOWN4: + //Don't know how to handle these. Someone should figure it out. + //Does the Ouya even have an accelerometer / gyro? I can't find any reference to these + //in the Ouya docs... + return; + + default: + return; } - if (screenManager) - screenManager->axis(key); + + //figure out the sensitivity of the tilt. (sensitivity is originally 0 - 100) + //We divide by 50, so that the rest of the 50 units can be used to overshoot the + //target. If you want control, you'd keep the sensitivity ~50. + //For games that don't need much control but need fast reactions, + //then a value of 70-80 is the way to go. + float xSensitivity = g_Config.iTiltSensitivityX / 50.0; + float ySensitivity = g_Config.iTiltSensitivityY / 50.0; + + + //now transform out current tilt to the calibrated coordinate system + Tilt trueTilt = GenTilt(baseTilt, currentTilt, g_Config.bInvertTiltX, g_Config.bInvertTiltY, g_Config.fDeadzoneRadius, xSensitivity, ySensitivity); + + + //now send the appropriate tilt event + switch (g_Config.iTiltInputType) { + case TILT_ANALOG: + GenerateAnalogStickEvent(trueTilt); + break; + + case TILT_DPAD: + GenerateDPadEvent(trueTilt); + break; + + case TILT_ACTION_BUTTON: + GenerateActionButtonEvent(trueTilt); + break; + } + } void NativeMessageReceived(const char *message, const char *value) { diff --git a/UI/TiltEventProcessor.cpp b/UI/TiltEventProcessor.cpp new file mode 100644 index 0000000000..4960c06bdd --- /dev/null +++ b/UI/TiltEventProcessor.cpp @@ -0,0 +1,170 @@ +#define _USE_MATH_DEFINES +#include +#include "TiltEventProcessor.h" +#include "Core/HLE/sceCtrl.h" +#include "math.h" +#include "native/base/logging.h" + + +using namespace TiltEventProcessor; + +//deadzone is normalized - 0 to 1 +//sensitivity controls how fast the deadzone reaches max value +inline float tiltInputCurve (float x, float deadzone, float sensitivity) { + + const float factor = sensitivity * 1.0f / (1.0f - deadzone); + + if (x > deadzone) { + return (x - deadzone) * factor * factor; + } else if (x < -deadzone) { + return (x + deadzone) * factor * factor; + } else { + return 0.0f; + } +} + +//dampen the tilt according to the given deadzone amount. +inline Tilt dampTilt(const Tilt &tilt, float deadzone, float xSensitivity, float ySensitivity) { + //multiply sensitivity by 2 so that "overshoot" is possible. I personally prefer a + //sensitivity >1 for kingdom hearts and < 1 for Gods Eater. so yes, overshoot is nice + //to have. + return Tilt(tiltInputCurve(tilt.x_, deadzone, 2.0 * xSensitivity), tiltInputCurve(tilt.y_, deadzone, 2.0 * ySensitivity)); +} + +inline float clamp(float f) { + if (f > 1.0f) return 1.0f; + if (f < -1.0f) return -1.0f; + return f; +} + + +Tilt TiltEventProcessor::NormalizeTilt(const Tilt &tilt){ + //setup the maximum values of the accelerometer manually per-platform + //TODO: less hacky way of doing this? + #ifdef ANDROID + //I obtained these values EXPERIMENTALLY. They may differ device to device. + //needs more testing. I'll be testing it on the Nexus 4 once I get mine back. + float maxX = 10, maxY = 8; + #else + float maxX = 1.0, maxY = 1.0; + #endif + + return Tilt(tilt.x_ / maxX, tilt.y_ / maxY); + +}; + + +Tilt TiltEventProcessor::GenTilt(const Tilt &baseTilt, const Tilt ¤tTilt, bool invertX, bool invertY, float deadzone, float xSensitivity, float ySensitivity) { + //first convert to the correct coordinate system + Tilt transformedTilt(currentTilt.x_ - baseTilt.x_, currentTilt.y_ - baseTilt.y_); + + //invert x and y axes if needed + if (invertX) { + transformedTilt.x_ *= -1.0; + } + + if (invertY) { + transformedTilt.y_ *= -1.0; + } + + //next, normalize the tilt values + transformedTilt = NormalizeTilt(transformedTilt); + + //finally, dampen the tilt according to our curve. + return dampTilt(transformedTilt, deadzone, xSensitivity, ySensitivity); +}; + +void TiltEventProcessor::GenerateAnalogStickEvent(const Tilt &tilt) { + __CtrlSetAnalogX(clamp(tilt.x_), CTRL_STICK_LEFT); + __CtrlSetAnalogY(clamp(tilt.y_), CTRL_STICK_LEFT); +}; + + +void TiltEventProcessor::GenerateDPadEvent(const Tilt &tilt) { + static const int dir[4] = {CTRL_RIGHT, CTRL_DOWN, CTRL_LEFT, CTRL_UP}; + + if (tilt.x_ == 0) { + __CtrlButtonUp(CTRL_RIGHT); + __CtrlButtonUp(CTRL_LEFT); + + } + + if (tilt.y_ == 0) { + __CtrlButtonUp(CTRL_UP); + __CtrlButtonUp(CTRL_DOWN); + + } + + if (tilt.x_ == 0 && tilt.y_ == 0) { + return; + } + + int ctrlMask = 0; + int direction = (int)(floorf((atan2f(tilt.y_, tilt.x_) / (2 * M_PI) * 8) + 0.5f)) & 7; + switch (direction) { + case 0: ctrlMask |= CTRL_RIGHT; break; + case 1: ctrlMask |= CTRL_RIGHT | CTRL_DOWN; break; + case 2: ctrlMask |= CTRL_DOWN; break; + case 3: ctrlMask |= CTRL_DOWN | CTRL_LEFT; break; + case 4: ctrlMask |= CTRL_LEFT; break; + case 5: ctrlMask |= CTRL_UP | CTRL_LEFT; break; + case 6: ctrlMask |= CTRL_UP; break; + case 7: ctrlMask |= CTRL_UP | CTRL_RIGHT; break; + } + + for (int i = 0; i < 4; i++) { + if (ctrlMask & dir[i]) { + __CtrlButtonDown(dir[i]); + } + } +}; + + +void TiltEventProcessor::GenerateActionButtonEvent(const Tilt &tilt) { + static const int buttons[4] = {CTRL_CIRCLE, CTRL_CROSS, CTRL_SQUARE, CTRL_TRIANGLE}; + + if (tilt.x_ == 0) { + __CtrlButtonUp(CTRL_SQUARE); + __CtrlButtonUp(CTRL_CIRCLE); + } + + if (tilt.y_ == 0) { + __CtrlButtonUp(CTRL_TRIANGLE); + __CtrlButtonUp(CTRL_CROSS); + } + + if (tilt.x_ == 0 && tilt.y_ == 0) { + return; + } + + int direction = (int)(floorf((atan2f(tilt.y_, tilt.x_) / (2 * M_PI) * 4) + 0.5f)) & 3; + __CtrlButtonDown(buttons[direction]); +}; + +void TiltEventProcessor::ResetTiltEvents() { + //this is ugly, but it's needed since the entire tilt system is + //stateless. So, when a tilt option is changed, we have to reset + //the tilt. + //this scenario will take place without this: + //1) tilt is Analog based. the device is tilted. Analog controller is being moved + //2) user changes mode to D-Pad and goes back to game + //3) the event handling loop sends all events to D-Pad now. + // The analog controller doesn't know that the type of tilt input has changed. + // It keeps sending tilt events for the analog. + + //D-Pad + __CtrlButtonUp(CTRL_RIGHT); + __CtrlButtonUp(CTRL_LEFT); + __CtrlButtonUp(CTRL_UP); + __CtrlButtonUp(CTRL_DOWN); + + //action buttons + __CtrlButtonUp(CTRL_SQUARE); + __CtrlButtonUp(CTRL_CIRCLE); + __CtrlButtonUp(CTRL_TRIANGLE); + __CtrlButtonUp(CTRL_CROSS); + + //analog + __CtrlSetAnalogX(0.0f, CTRL_STICK_LEFT); + __CtrlSetAnalogY(0.0f, CTRL_STICK_LEFT); +}; \ No newline at end of file diff --git a/UI/TiltEventProcessor.h b/UI/TiltEventProcessor.h new file mode 100644 index 0000000000..3bc2d46ede --- /dev/null +++ b/UI/TiltEventProcessor.h @@ -0,0 +1,41 @@ + + +namespace TiltEventProcessor { + + enum TiltTypes{ + TILT_NULL = 0, + TILT_ANALOG, + TILT_DPAD, + TILT_ACTION_BUTTON + }; + + + //Represents a generic Tilt event + struct Tilt{ + + float x_, y_; + + Tilt () : x_(0), y_(0) {}; + Tilt (const float x, const float y) : x_(x), y_(y){} + }; + + + Tilt NormalizeTilt(const Tilt &tilt); + + //generates a tilt in the correct coordinate system based on + //calibration. BaseTilt is the "base" / "zero" tilt. currentTilt is the + //sensor tilt reading at this moment. + //NOTE- both base and current tilt *MUST BE NORMALIZED* by calling the NormalizeTilt() function. + Tilt GenTilt(const Tilt &baseTilt, const Tilt ¤tTilt, bool invertX, bool invertY, float deadzone, float xSensitivity, float ySensitivity); + + + //the next 3 functions generate tilt events given the current Tilt amount, + //and the deadzone radius. + void GenerateAnalogStickEvent(const Tilt &tilt); + void GenerateDPadEvent(const Tilt &tilt); + void GenerateActionButtonEvent(const Tilt &tilt); + + void ResetTiltEvents(); + + +} \ No newline at end of file diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 1333c3665a..4497d86490 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -33,6 +33,7 @@ + @@ -53,6 +54,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 818693e77a..0c525843e3 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -40,6 +40,9 @@ Screens + + Screens + Screens @@ -84,6 +87,9 @@ Screens + + Screens + Screens diff --git a/android/jni/Android.mk b/android/jni/Android.mk index a2d2a43e8d..f35bbdd73a 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -281,6 +281,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/ControlMappingScreen.cpp \ $(SRC)/UI/GameSettingsScreen.cpp \ $(SRC)/UI/TiltAnalogSettingsScreen.cpp \ + $(SRC)/UI/TiltEventProcessor.cpp \ $(SRC)/UI/TouchControlLayoutScreen.cpp \ $(SRC)/UI/TouchControlVisibilityScreen.cpp \ $(SRC)/UI/CwCheatScreen.cpp \