mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-13 23:00:45 -04:00
Feat[gyro]: improve calibration and smoothing (#4229)
* Feat[gyro]: Reduce jitter with moving average * Cleanup: Remove logging * Feat[gyro]: Add dampering window * Feat[gyro]: Use extremely low pass filter Technically, it doesn't entirely filter out the input, only stores it * Tweak[Gyro]: Lower hyro threshold even more * Feat: Simplify smoothing, better calibration * Feat[gyro]: Add dampering window # Conflicts: # app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java # app_pojavlauncher/src/main/res/values/strings.xml * Cleanup[gyro]: Don't use import * Refactor[gyro]: reduce JNI calls * Fix[Gyro]: Adjust low pass treshold to sensitivity * Feat[Gyro]: Handle single/multi axis deplacement * Tweak[gyro]: Raise single axis threshold
This commit is contained in:
parent
2a27439887
commit
a7444aa99e
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -302,6 +302,8 @@
|
||||
<string name="preference_gyro_invert_y_axis">Invert Y axis</string>
|
||||
<string name="preference_gyro_invert_x_axis_description">Invert the horizontal axis</string>
|
||||
<string name="preference_gyro_invert_y_axis_description">Invert the vertical axis</string>
|
||||
<string name="preference_gyro_smoothing_title">Enable gyro smoothing</string>
|
||||
<string name="preference_gyro_smoothing_description">Reduce jitter in exchange of latency.</string>
|
||||
|
||||
<string name="preference_back_title">Back to the last screen</string>
|
||||
<string name="gles_hack_none">Don\'t shrink textures</string>
|
||||
@ -367,12 +369,15 @@
|
||||
<string name="global_save_and_exit">Save and exit</string>
|
||||
<string name="global_yes">Yes</string>
|
||||
<string name="global_no">No</string>
|
||||
|
||||
<string name="preference_controller_map_wiped">The controller config has been wiped</string>
|
||||
<string name="preference_category_controller_settings">Controller settings</string>
|
||||
<string name="preference_wipe_controller_title">Reset controller mapping</string>
|
||||
<string name="preference_wipe_controller_description">Allow you to remap the controller buttons</string>
|
||||
<string name="preference_deadzone_scale_title">Joystick deadzone scale</string>
|
||||
<string name="preference_deadzone_scale_description">Increase it if the joystick drifts</string>
|
||||
|
||||
|
||||
<string name="preference_force_big_core_title">Force renderer to run on the big core</string>
|
||||
<string name="preference_force_big_core_desc">Forces the Minecraft render thread to run on the core with the highest max frequency</string>
|
||||
<string name="version_select_hint">Select a version</string>
|
||||
@ -395,4 +400,10 @@
|
||||
<string name="of_dl_failed_to_scrape">Failed to collect data for OptiFine installation</string>
|
||||
<string name="of_dl_progress">Downloading %s</string>
|
||||
<string name="create_profile_optifine">Create OptiFine profile</string>
|
||||
|
||||
|
||||
<string name="preference_gyro_damper_window_title">Smoothing time</string>
|
||||
<string name="preference_gyro_damper_window_description">Reduce jitter in exchange of latency. 0 to disable it</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -112,6 +112,12 @@
|
||||
android:summary="@string/preference_gyro_sample_rate_description"
|
||||
app2:selectable="false"
|
||||
app2:showSeekBarValue="true"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="gyroSmoothing"
|
||||
android:title="@string/preference_gyro_smoothing_title"
|
||||
android:summary="@string/preference_gyro_smoothing_description"
|
||||
android:defaultValue="true"
|
||||
/>
|
||||
<SwitchPreference
|
||||
android:key="gyroInvertX"
|
||||
android:title="@string/preference_gyro_invert_x_axis"
|
||||
|
Loading…
x
Reference in New Issue
Block a user