1
0
forked from Clones/Controlify

Improve controller calibration algorithm

This commit is contained in:
isXander
2023-06-12 19:41:49 +01:00
parent adc439128f
commit 4df60549c6
9 changed files with 129 additions and 78 deletions

View File

@ -177,42 +177,51 @@ public class ControllerConfigScreenFactory {
}
private static OptionGroup makeDeadzoneGroup(Controller<?, ?> controller, ControllerConfig def, ControllerConfig config) {
var deadzoneOpts = new ArrayList<Option<Float>>();
var group = OptionGroup.createBuilder()
.name(Component.translatable("controlify.config.group.deadzones"));
if (controller instanceof GamepadController gamepad) {
var gpCfg = gamepad.config();
var gpCfgDef = gamepad.defaultConfig();
group
.option(Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.left_stick")))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.left_stick")))
.text(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
.build())
.binding(
Math.max(gpCfgDef.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY),
() -> Math.max(gpCfg.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY),
v -> gpCfg.leftStickDeadzoneX = gpCfg.leftStickDeadzoneY = v
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0f, 1f).step(0.01f)
.valueFormatter(percentFormatter))
Option<Float> left = Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.left_stick")))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.left_stick")))
.text(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
.build())
.option(Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.right_stick")))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.right_stick")))
.text(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
.build())
.binding(
Math.max(gpCfgDef.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY),
() -> Math.max(gpCfg.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY),
v -> gpCfg.rightStickDeadzoneX = gpCfg.rightStickDeadzoneY = v
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0f, 1f).step(0.01f)
.valueFormatter(percentFormatter))
.build());
.binding(
gpCfgDef.getLeftStickDeadzone(),
gpCfg::getLeftStickDeadzone,
gpCfg::setLeftStickDeadzone
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0f, 1f).step(0.01f)
.valueFormatter(percentFormatter))
.build();
Option<Float> right = Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.axis_deadzone", Component.translatable("controlify.gui.right_stick")))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.axis_deadzone.tooltip", Component.translatable("controlify.gui.right_stick")))
.text(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
.build())
.binding(
gpCfgDef.getRightStickDeadzone(),
gpCfg::getRightStickDeadzone,
gpCfg::setRightStickDeadzone
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0f, 1f).step(0.01f)
.valueFormatter(percentFormatter))
.build();
group.option(left);
group.option(right);
deadzoneOpts.add(left);
deadzoneOpts.add(right);
} else if (controller instanceof SingleJoystickController joystick) {
JoystickMapping.Axis[] axes = joystick.mapping().axes();
Collection<Integer> deadzoneAxes = IntStream.range(0, axes.length)
@ -231,7 +240,7 @@ public class ControllerConfigScreenFactory {
for (int i : deadzoneAxes) {
var axis = axes[i];
group.option(Option.<Float>createBuilder()
Option<Float> deadzoneOpt = Option.<Float>createBuilder()
.name(Component.translatable("controlify.gui.joystick_axis_deadzone", axis.name()))
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.joystick_axis_deadzone.tooltip", axis.name()))
@ -241,7 +250,9 @@ public class ControllerConfigScreenFactory {
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0f, 1f).step(0.01f)
.valueFormatter(percentFormatter))
.build());
.build();
group.option(deadzoneOpt);
deadzoneOpts.add(deadzoneOpt);
}
}
@ -261,7 +272,10 @@ public class ControllerConfigScreenFactory {
.description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.auto_calibration.tooltip"))
.build())
.action((screen, button) -> Minecraft.getInstance().setScreen(new ControllerDeadzoneCalibrationScreen(controller, screen)))
.action((screen, button) -> Minecraft.getInstance().setScreen(new ControllerDeadzoneCalibrationScreen(controller, () -> {
deadzoneOpts.forEach(Option::forgetPendingValue);
return screen;
})))
.build());
return group.build();

View File

@ -1,8 +1,8 @@
package dev.isxander.controlify.gui.screen;
import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.utils.Log;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
@ -10,13 +10,18 @@ import net.minecraft.client.gui.components.MultiLineLabel;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ControllerDeadzoneCalibrationScreen extends Screen {
private static final int CALIBRATION_TIME = 100;
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
protected final Controller<?, ?> controller;
private final Screen parent;
private final Supplier<Screen> parent;
private MultiLineLabel waitLabel, infoLabel, completeLabel;
@ -25,7 +30,13 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
protected boolean calibrating = false, calibrated = false;
protected int calibrationTicks = 0;
private final Map<Integer, double[]> calibrationData = new HashMap<>();
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Screen parent) {
this(controller, () -> parent);
}
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Supplier<Screen> parent) {
super(Component.translatable("controlify.calibration.title"));
this.controller = controller;
this.parent = parent;
@ -104,42 +115,45 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
if (!calibrating)
return;
if (stateChanged())
if (stateChanged()) {
calibrationTicks = 0;
calibrationData.clear();
}
if (calibrationTicks < CALIBRATION_TIME) {
calibrate(calibrationTicks);
if (calibrationTicks < 100) {
calibrationTicks++;
} else {
useCurrentStateAsDeadzone();
applyDeadzones();
calibrating = false;
calibrated = true;
readyButton.active = true;
readyButton.setMessage(Component.translatable("controlify.calibration.done"));
controller.config().calibrated = true;
controller.config().deadzonesCalibrated = true;
Controlify.instance().config().save();
}
}
private void useCurrentStateAsDeadzone() {
var axes = controller.state().axes();
private void calibrate(int tick) {
var axes = controller.state().rawAxes();
for (int i = 0; i < axes.size(); i++) {
var axis = axes.get(i);
var minDeadzone = axis + 0.08f;
var deadzone = (float)Mth.clamp(0.05 * Math.ceil(minDeadzone / 0.05), 0, 0.95);
if (Float.isNaN(deadzone)) {
Log.LOGGER.warn("Deadzone for axis {} is NaN, using default deadzone.", i);
deadzone = controller.defaultConfig().getDeadzone(i);
}
controller.config().setDeadzone(i, deadzone);
var axis = Math.abs(axes.get(i));
calibrationData.computeIfAbsent(i, k -> new double[CALIBRATION_TIME])[tick] = axis;
}
}
private void applyDeadzones() {
calibrationData.forEach((i, data) -> {
var max = Arrays.stream(data).max().orElseThrow();
controller.config().setDeadzone(i, (float) max + 0.05f);
});
}
private boolean stateChanged() {
var amt = 0.0001f;
var amt = 0.4f;
return controller.state().axes().stream()
.anyMatch(axis -> Math.abs(axis - controller.prevState().axes().get(controller.state().axes().indexOf(axis))) > amt);
@ -147,7 +161,7 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
@Override
public void onClose() {
minecraft.setScreen(parent);
minecraft.setScreen(parent.get());
}
@Override