diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java index 1c614c2..6c2ae36 100644 --- a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java @@ -5,9 +5,11 @@ import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.AbstractController; import dev.isxander.controlify.controller.BatteryLevel; import dev.isxander.controlify.controller.hid.ControllerHIDService; +import dev.isxander.controlify.debug.DebugProperties; import dev.isxander.controlify.driver.*; import dev.isxander.controlify.rumble.RumbleManager; import dev.isxander.controlify.rumble.RumbleSource; +import dev.isxander.controlify.utils.ControllerUtils; import net.minecraft.resources.ResourceLocation; import org.lwjgl.glfw.GLFW; @@ -23,6 +25,8 @@ public class GamepadController extends AbstractController uniqueDrivers; + private int antiSnapbackTicksL, antiSnapbackTicksR; + public GamepadController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) { super(joystickId, hidInfo); if (!GLFW.glfwJoystickIsGamepad(joystickId)) @@ -64,6 +68,23 @@ public class GamepadController extends AbstractController 0) { + deadzoneAxesState = deadzoneAxesState.neutraliseLeft(); + antiSnapbackTicksL--; + } else if (ControllerUtils.shouldApplyAntiSnapBack(deadzoneAxesState.leftStickX(), deadzoneAxesState.leftStickY(), prevState.gamepadAxes().leftStickX(), prevState.gamepadAxes().leftStickY(), 0.08f)) { + antiSnapbackTicksL = 2; + deadzoneAxesState = deadzoneAxesState.neutraliseLeft(); + } + if (antiSnapbackTicksR > 0) { + deadzoneAxesState = deadzoneAxesState.neutraliseRight(); + antiSnapbackTicksR--; + } else if (ControllerUtils.shouldApplyAntiSnapBack(deadzoneAxesState.rightStickX(), deadzoneAxesState.rightStickY(), prevState.gamepadAxes().rightStickX(), prevState.gamepadAxes().rightStickY(), 0.08f)) { + antiSnapbackTicksR = 2; + deadzoneAxesState = deadzoneAxesState.neutraliseRight(); + } + } + GamepadState.GyroState gyroState = drivers.gyroDriver().getGyroState(); state = new GamepadState(deadzoneAxesState, basicState.axes(), basicState.buttons(), gyroState, absoluteGyro); diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java index ad9bd4a..4ea15e6 100644 --- a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java @@ -177,6 +177,14 @@ public final class GamepadState implements ControllerState { ControllerUtils.deadzone(rightTrigger, deadZone) ); } + + public AxesState neutraliseLeft() { + return new AxesState(0, 0, rightStickX, rightStickY, leftTrigger, rightTrigger); + } + + public AxesState neutraliseRight() { + return new AxesState(leftStickX, leftStickY, 0, 0, leftTrigger, rightTrigger); + } } public record ButtonState( diff --git a/src/main/java/dev/isxander/controlify/controller/sdl2/SDL2NativesManager.java b/src/main/java/dev/isxander/controlify/controller/sdl2/SDL2NativesManager.java index 63b208b..d6a0756 100644 --- a/src/main/java/dev/isxander/controlify/controller/sdl2/SDL2NativesManager.java +++ b/src/main/java/dev/isxander/controlify/controller/sdl2/SDL2NativesManager.java @@ -80,6 +80,7 @@ public class SDL2NativesManager { // without calling JoystickUpdate, which we don't do. SDL.SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); // better rumble + SDL.SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); SDL.SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL.SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL.SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); diff --git a/src/main/java/dev/isxander/controlify/debug/DebugProperties.java b/src/main/java/dev/isxander/controlify/debug/DebugProperties.java index 7a3630f..065c1e8 100644 --- a/src/main/java/dev/isxander/controlify/debug/DebugProperties.java +++ b/src/main/java/dev/isxander/controlify/debug/DebugProperties.java @@ -20,6 +20,8 @@ public class DebugProperties { public static final boolean PRINT_GYRO = boolProp("controlify.debug.print_gyro", false, false); /* Print what drivers are being used */ public static final boolean PRINT_DRIVER = boolProp("controlify.debug.print_driver", true, true); + /* Use experimental anti-snapback */ + public static final boolean USE_SNAPBACK = boolProp("controlify.debug.use_snapback", false, false); public static void printProperties() { if (properties.stream().noneMatch(prop -> prop.enabled() != prop.def())) diff --git a/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java b/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java index 7aa3f71..8aaaa90 100644 --- a/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java +++ b/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java @@ -1,7 +1,40 @@ package dev.isxander.controlify.utils; +import net.minecraft.util.Mth; + public class ControllerUtils { public static float deadzone(float value, float deadzone) { return (value - Math.copySign(Math.min(deadzone, Math.abs(value)), value)) / (1 - deadzone); } + + public static float applyCircularityX(float x, float y) { + return (float) (x * Math.sqrt(1 - (y * y) / 2)); + } + public static float applyCircularityY(float x, float y) { + return (float) (y * Math.sqrt(1 - (x * x) / 2)); + } + + public static boolean shouldApplyAntiSnapBack(float x, float y, float px, float py, float threshold) { + float dx = x - px; + float dy = y - py; + float distanceSquared = dx * dx + dy * dy; + + boolean isSnap = distanceSquared >= threshold * threshold; + boolean hasCrossedOrigin = Math.signum(x) != Math.signum(px) && Math.signum(y) != Math.signum(py); + + if (isSnap && hasCrossedOrigin) { + // t is the distance from the origin to the middle of the line + float t = (-x * (px - x) + -y * (py - y)) / distanceSquared; + t = Mth.clamp(t, 0, 1); + + // Calculate the distance from the middle of the line to the origin + double distanceToMiddle = Math.sqrt(Math.pow(-t * x + t * px, 2) + + Math.pow(-t * y + t * py, 2)); + + // If the distance is less than 0.05, then the stick is close enough to the middle to be snap-backed + return distanceToMiddle <= 0.01; + } + + return false; + } }