diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GyroControl.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GyroControl.java
index ce8b41e3e..e0fd35b71 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GyroControl.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/GyroControl.java
@@ -7,13 +7,20 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.view.OrientationEventListener;
+import android.view.Surface;
import android.view.WindowManager;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import org.lwjgl.glfw.CallbackBridge;
-public class GyroControl implements SensorEventListener, GrabListener{
+import java.util.Arrays;
+
+public class GyroControl implements SensorEventListener, GrabListener {
+ /* How much distance has to be moved before taking into account the gyro */
+ private static final float SINGLE_AXIS_LOW_PASS_THRESHOLD = 1.13F;
+ private static final float MULTI_AXIS_LOW_PASS_THRESHOLD = 1.3F;
+
private final WindowManager mWindowManager;
private int mSurfaceRotation;
private final SensorManager mSensorManager;
@@ -29,12 +36,29 @@ public class GyroControl implements SensorEventListener, GrabListener{
private final float[] mCurrentRotation = new float[16];
private final float[] mAngleDifference = new float[3];
+
+ /* Used to average the last values, if smoothing is enabled */
+ private final float[][] mAngleBuffer = new float[
+ LauncherPreferences.PREF_GYRO_SMOOTHING ? 2 : 1
+ ][3];
+ private float xTotal = 0;
+ private float yTotal = 0;
+
+ private float xAverage = 0;
+ private float yAverage = 0;
+ private int mHistoryIndex = -1;
+
+ /* Store the gyro movement under the threshold */
+ private float mStoredX = 0;
+ private float mStoredY = 0;
+
public GyroControl(Activity activity) {
mWindowManager = activity.getWindowManager();
mSurfaceRotation = -10;
mSensorManager = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);
mCorrectionListener = new OrientationCorrectionListener(activity);
+ updateOrientation();
}
public void enable() {
@@ -50,8 +74,10 @@ public class GyroControl implements SensorEventListener, GrabListener{
if(mSensor == null) return;
mSensorManager.unregisterListener(this);
mCorrectionListener.disable();
+ resetDamper();
CallbackBridge.removeGrabListener(this);
}
+
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (!mShouldHandleEvents) return;
@@ -59,21 +85,78 @@ public class GyroControl implements SensorEventListener, GrabListener{
System.arraycopy(mCurrentRotation, 0, mPreviousRotation, 0, 16);
SensorManager.getRotationMatrixFromVector(mCurrentRotation, sensorEvent.values);
+
if(mFirstPass){ // Setup initial position
mFirstPass = false;
return;
}
SensorManager.getAngleChange(mAngleDifference, mCurrentRotation, mPreviousRotation);
+ damperValue(mAngleDifference);
+ mStoredX += xAverage * 1000 * LauncherPreferences.PREF_GYRO_SENSITIVITY;
+ mStoredY += yAverage * 1000 * LauncherPreferences.PREF_GYRO_SENSITIVITY;
- CallbackBridge.mouseX -= (mAngleDifference[mSwapXY ? 2 : 1] * 1000 * LauncherPreferences.PREF_GYRO_SENSITIVITY * xFactor);
- CallbackBridge.mouseY += (mAngleDifference[mSwapXY ? 1 : 2] * 1000 * LauncherPreferences.PREF_GYRO_SENSITIVITY * yFactor);
- CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
+ boolean updatePosition = false;
+ float absX = Math.abs(mStoredX);
+ float absY = Math.abs(mStoredY);
+
+ if(absX + absY > MULTI_AXIS_LOW_PASS_THRESHOLD) {
+ CallbackBridge.mouseX -= ((mSwapXY ? mStoredY : mStoredX) * xFactor);
+ CallbackBridge.mouseY += ((mSwapXY ? mStoredX : mStoredY) * yFactor);
+ mStoredX = 0;
+ mStoredY = 0;
+ updatePosition = true;
+ } else {
+ if(Math.abs(mStoredX) > SINGLE_AXIS_LOW_PASS_THRESHOLD){
+ CallbackBridge.mouseX -= ((mSwapXY ? mStoredY : mStoredX) * xFactor);
+ mStoredX = 0;
+ updatePosition = true;
+ }
+
+ if(Math.abs(mStoredY) > SINGLE_AXIS_LOW_PASS_THRESHOLD) {
+ CallbackBridge.mouseY += ((mSwapXY ? mStoredX : mStoredY) * yFactor);
+ mStoredY = 0;
+ updatePosition = true;
+ }
+ }
+
+ if(updatePosition){
+ CallbackBridge.sendCursorPos(CallbackBridge.mouseX, CallbackBridge.mouseY);
+ }
+ }
+
+ /** Update the axis mapping in accordance to activity rotation, used for initial rotation */
+ public void updateOrientation(){
+ int rotation = mWindowManager.getDefaultDisplay().getRotation();
+ mSurfaceRotation = rotation;
+ switch (rotation){
+ case Surface.ROTATION_0:
+ mSwapXY = true;
+ xFactor = 1;
+ yFactor = 1;
+ break;
+ case Surface.ROTATION_90:
+ mSwapXY = false;
+ xFactor = -1;
+ yFactor = 1;
+ break;
+ case Surface.ROTATION_180:
+ mSwapXY = true;
+ xFactor = -1;
+ yFactor = -1;
+ break;
+ case Surface.ROTATION_270:
+ mSwapXY = false;
+ xFactor = 1;
+ yFactor = -1;
+ break;
+ }
+
+ if(LauncherPreferences.PREF_GYRO_INVERT_X) xFactor *= -1;
+ if(LauncherPreferences.PREF_GYRO_INVERT_Y) yFactor *= -1;
}
@Override
- public void onAccuracyChanged(Sensor sensor, int i) {
-
- }
+ public void onAccuracyChanged(Sensor sensor, int i) {}
@Override
public void onGrabState(boolean isGrabbing) {
@@ -81,6 +164,40 @@ public class GyroControl implements SensorEventListener, GrabListener{
mShouldHandleEvents = isGrabbing;
}
+
+ /**
+ * Compute the moving average of the gyroscope to reduce jitter
+ * @param newAngleDifference The new angle difference
+ */
+ private void damperValue(float[] newAngleDifference){
+ mHistoryIndex ++;
+ if(mHistoryIndex >= mAngleBuffer.length) mHistoryIndex = 0;
+
+ xTotal -= mAngleBuffer[mHistoryIndex][1];
+ yTotal -= mAngleBuffer[mHistoryIndex][2];
+
+ System.arraycopy(newAngleDifference, 0, mAngleBuffer[mHistoryIndex], 0, 3);
+
+ xTotal += mAngleBuffer[mHistoryIndex][1];
+ yTotal += mAngleBuffer[mHistoryIndex][2];
+
+ // compute the moving average
+ xAverage = xTotal / mAngleBuffer.length;
+ yAverage = yTotal / mAngleBuffer.length;
+ }
+
+ /** Reset the moving average data */
+ private void resetDamper(){
+ mHistoryIndex = -1;
+ xTotal = 0;
+ yTotal = 0;
+ xAverage = 0;
+ yAverage = 0;
+ for(float[] oldAngle : mAngleBuffer){
+ Arrays.fill(oldAngle, 0);
+ }
+ }
+
class OrientationCorrectionListener extends OrientationEventListener {
public OrientationCorrectionListener(Context context) {
@@ -92,31 +209,39 @@ public class GyroControl implements SensorEventListener, GrabListener{
// Force to wait to be in game before setting factors
// Theoretically, one could use the whole interface in portrait...
if(!mShouldHandleEvents) return;
- int surfaceRotation = mWindowManager.getDefaultDisplay().getRotation();
- if(surfaceRotation == mSurfaceRotation) return;
if(i == OrientationEventListener.ORIENTATION_UNKNOWN) {
return; //change nothing
}
- mSurfaceRotation = surfaceRotation;
- if((315 < i && i <= 360) || (i < 45) ) {
- mSwapXY = true;
- xFactor = 1;
- yFactor = 1;
- }else if(45 < i && i < 135) {
- mSwapXY = false;
- xFactor = 1;
- yFactor = -1;
- }else if(135 < i && i < 225) {
- mSwapXY = true;
- xFactor = -1;
- yFactor = -1;
- }else if(225 < i && i < 315) {
- mSwapXY = false;
- xFactor = -1;
- yFactor = 1;
+
+
+ switch (mSurfaceRotation){
+ case Surface.ROTATION_90:
+ case Surface.ROTATION_270:
+ mSwapXY = false;
+ if(225 < i && i < 315) {
+ xFactor = -1;
+ yFactor = 1;
+ }else if(45 < i && i < 135) {
+ xFactor = 1;
+ yFactor = -1;
+ }
+ break;
+
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ mSwapXY = true;
+ if((315 < i && i <= 360) || (i < 45) ) {
+ xFactor = 1;
+ yFactor = 1;
+ }else if(135 < i && i < 225) {
+ xFactor = -1;
+ yFactor = -1;
+ }
+ break;
}
+
if(LauncherPreferences.PREF_GYRO_INVERT_X) xFactor *= -1;
if(LauncherPreferences.PREF_GYRO_INVERT_Y) yFactor *= -1;
}
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
index 0741b5cea..68d8f3e83 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java
@@ -279,6 +279,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ if(mGyroControl != null) mGyroControl.updateOrientation();
Tools.updateWindowSize(this);
minecraftGLView.refreshSize();
runOnUiThread(() -> mControlLayout.refreshControlButtonPositions());
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
index 232eb1445..60c216b58 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/LauncherPreferences.java
@@ -49,6 +49,7 @@ public class LauncherPreferences {
public static boolean PREF_ENABLE_GYRO = false;
public static float PREF_GYRO_SENSITIVITY = 1f;
public static int PREF_GYRO_SAMPLE_RATE = 16;
+ public static boolean PREF_GYRO_SMOOTHING = true;
public static boolean PREF_GYRO_INVERT_X = false;
@@ -61,6 +62,7 @@ public class LauncherPreferences {
public static boolean PREF_BIG_CORE_AFFINITY = false;
+
public static void loadPreferences(Context ctx) {
//Required for the data folder.
Tools.initContextConstants(ctx);
@@ -93,6 +95,7 @@ public class LauncherPreferences {
PREF_ENABLE_GYRO = DEFAULT_PREF.getBoolean("enableGyro", false);
PREF_GYRO_SENSITIVITY = ((float)DEFAULT_PREF.getInt("gyroSensitivity", 100))/100f;
PREF_GYRO_SAMPLE_RATE = DEFAULT_PREF.getInt("gyroSampleRate", 16);
+ PREF_GYRO_SMOOTHING = DEFAULT_PREF.getBoolean("gyroSmoothing", true);
PREF_GYRO_INVERT_X = DEFAULT_PREF.getBoolean("gyroInvertX", false);
PREF_GYRO_INVERT_Y = DEFAULT_PREF.getBoolean("gyroInvertY", false);
PREF_FORCE_VSYNC = DEFAULT_PREF.getBoolean("force_vsync", false);
diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
index ed11ec071..d380bb7c9 100644
--- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
+++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java
@@ -25,6 +25,7 @@ public class LauncherPreferenceControlFragment extends LauncherPreferenceFragmen
float gyroSpeed = LauncherPreferences.PREF_GYRO_SENSITIVITY;
float joystickDeadzone = LauncherPreferences.PREF_DEADZONE_SCALE;
+
//Triggers a write for some reason which resets the value
addPreferencesFromResource(R.xml.pref_control);
@@ -53,6 +54,7 @@ public class LauncherPreferenceControlFragment extends LauncherPreferenceFragmen
deadzoneSeek.setValue((int) joystickDeadzone * 100);
deadzoneSeek.setSuffix(" %");
+
Context context = getContext();
if(context != null) {
mGyroAvailable = ((SensorManager)context.getSystemService(Context.SENSOR_SERVICE)).getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
@@ -83,6 +85,7 @@ public class LauncherPreferenceControlFragment extends LauncherPreferenceFragmen
findPreference("gyroSampleRate").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
findPreference("gyroInvertX").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
findPreference("gyroInvertY").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
+ findPreference("gyroSmoothing").setVisible(LauncherPreferences.PREF_ENABLE_GYRO);
}
}
diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml
index 5f75caf87..9f571caf3 100644
--- a/app_pojavlauncher/src/main/res/values/strings.xml
+++ b/app_pojavlauncher/src/main/res/values/strings.xml
@@ -302,6 +302,8 @@
Invert Y axis
Invert the horizontal axis
Invert the vertical axis
+ Enable gyro smoothing
+ Reduce jitter in exchange of latency.
Back to the last screen
Don\'t shrink textures
@@ -367,12 +369,15 @@
Save and exit
Yes
No
+
The controller config has been wiped
Controller settings
Reset controller mapping
Allow you to remap the controller buttons
Joystick deadzone scale
Increase it if the joystick drifts
+
+
Force renderer to run on the big core
Forces the Minecraft render thread to run on the core with the highest max frequency
Select a version
@@ -395,4 +400,10 @@
Failed to collect data for OptiFine installation
Downloading %s
Create OptiFine profile
+
+
+ Smoothing time
+ Reduce jitter in exchange of latency. 0 to disable it
+
+
diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml
index 73e26380d..0f333095e 100644
--- a/app_pojavlauncher/src/main/res/xml/pref_control.xml
+++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml
@@ -112,6 +112,12 @@
android:summary="@string/preference_gyro_sample_rate_description"
app2:selectable="false"
app2:showSeekBarValue="true"/>
+