mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Better tap detection (#778)
This commit is contained in:
4
.github/workflows/gradle.yaml
vendored
4
.github/workflows/gradle.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
run: git fetch --tags origin --recurse-submodules=no
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
run: git fetch --tags origin --recurse-submodules=no
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
|
||||
@@ -339,13 +339,19 @@ settings-general-fk_settings-vive_emulation-label = Enable Vive emulation
|
||||
## Gesture control settings (tracker tapping)
|
||||
settings-general-gesture_control = Gesture control
|
||||
settings-general-gesture_control-subtitle = Tap based resets
|
||||
settings-general-gesture_control-description = Allows for resets to be triggered by tapping a tracker. The tracker highest up on your torso is used for Yaw Reset, the tracker highest up on your left leg is used for Full Reset, and the tracker highest up on your right leg is used for Mounting Reset. It should be mentioned that taps must happen within 0.6 seconds to be registered.
|
||||
settings-general-gesture_control-description = Allows for resets to be triggered by tapping a tracker. The tracker highest up on your torso is used for Yaw Reset, the tracker highest up on your left leg is used for Full Reset, and the tracker highest up on your right leg is used for Mounting Reset. Taps must occur within the time limit of 0.3 seconds times the number of taps to be recognized.
|
||||
# This is a unit: 3 taps, 2 taps, 1 tap
|
||||
# $amount (Number) - Amount of taps (touches to the tracker's case)
|
||||
settings-general-gesture_control-taps = { $amount ->
|
||||
[one] 1 tap
|
||||
*[other] { $amount } taps
|
||||
}
|
||||
# This is a unit: 3 trackers, 2 trackers, 1 tracker
|
||||
# $amount (Number) - Amount of trackers
|
||||
settings-general-gesture_control-trackers = { $amount ->
|
||||
[one] 1 tracker
|
||||
*[other] { $amount } trackers
|
||||
}
|
||||
settings-general-gesture_control-yawResetEnabled = Enable tap to yaw reset
|
||||
settings-general-gesture_control-yawResetDelay = Yaw reset delay
|
||||
settings-general-gesture_control-yawResetTaps = Taps for yaw reset
|
||||
@@ -355,6 +361,9 @@ settings-general-gesture_control-fullResetTaps = Taps for full reset
|
||||
settings-general-gesture_control-mountingResetEnabled = Enable tap to reset mounting
|
||||
settings-general-gesture_control-mountingResetDelay = Mounting reset delay
|
||||
settings-general-gesture_control-mountingResetTaps = Taps for mounting reset
|
||||
# The number of trackers that can have higher acceleration before a tap is rejected
|
||||
settings-general-gesture_control-numberTrackersOverThreshold = Trackers over threshold
|
||||
settings-general-gesture_control-numberTrackersOverThreshold-description = Increase this value if tap detection is not working. Do not increase it above what is needed to make tap detection work this will cause false positives
|
||||
|
||||
## Interface settings
|
||||
settings-general-interface = Interface
|
||||
|
||||
@@ -72,6 +72,7 @@ interface SettingsForm {
|
||||
yawResetTaps: number;
|
||||
fullResetTaps: number;
|
||||
mountingResetTaps: number;
|
||||
numberTrackersOverThreshold;
|
||||
};
|
||||
legTweaks: {
|
||||
correctionStrength: number;
|
||||
@@ -122,6 +123,7 @@ const defaultValues = {
|
||||
yawResetTaps: 2,
|
||||
fullResetTaps: 3,
|
||||
mountingResetTaps: 3,
|
||||
numberTrackersOverThreshold: 1,
|
||||
},
|
||||
legTweaks: { correctionStrength: 0.3 },
|
||||
interface: {
|
||||
@@ -195,6 +197,8 @@ export function GeneralSettings() {
|
||||
values.tapDetection.mountingResetEnabled;
|
||||
tapDetection.mountingResetDelay = values.tapDetection.mountingResetDelay;
|
||||
tapDetection.mountingResetTaps = values.tapDetection.mountingResetTaps;
|
||||
tapDetection.numberTrackersOverThreshold =
|
||||
values.tapDetection.numberTrackersOverThreshold;
|
||||
tapDetection.setupMode = false;
|
||||
settings.tapDetectionSettings = tapDetection;
|
||||
|
||||
@@ -294,6 +298,9 @@ export function GeneralSettings() {
|
||||
mountingResetTaps:
|
||||
settings.tapDetectionSettings.mountingResetTaps ||
|
||||
defaultValues.tapDetection.mountingResetTaps,
|
||||
numberTrackersOverThreshold:
|
||||
settings.tapDetectionSettings.numberTrackersOverThreshold ||
|
||||
defaultValues.tapDetection.numberTrackersOverThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -869,6 +876,35 @@ export function GeneralSettings() {
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
{config?.debug && (
|
||||
<div className="grid sm:grid-cols-1 gap-2 pt-2">
|
||||
<Typography bold>
|
||||
{l10n.getString(
|
||||
'settings-general-gesture_control-numberTrackersOverThreshold'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-gesture_control-numberTrackersOverThreshold-description'
|
||||
)}
|
||||
</Typography>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.numberTrackersOverThreshold"
|
||||
valueLabelFormat={(value) =>
|
||||
l10n.getString(
|
||||
'settings-general-gesture_control-trackers',
|
||||
{
|
||||
amount: Math.round(value),
|
||||
}
|
||||
)
|
||||
}
|
||||
min={1}
|
||||
max={20}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ public class RPCSettingsBuilder {
|
||||
tapDetectionConfig.getMountingResetDelay(),
|
||||
tapDetectionConfig.getMountingResetEnabled(),
|
||||
tapDetectionConfig.getMountingResetTaps(),
|
||||
tapDetectionConfig.getSetupMode()
|
||||
tapDetectionConfig.getSetupMode(),
|
||||
tapDetectionConfig.getNumberTrackersOverThreshold()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,15 @@ public class RPCSettingsHandler {
|
||||
.setMountingResetEnabled(tapDetectionSettings.mountingResetEnabled());
|
||||
tapDetectionConfig.setSetupMode(tapDetectionSettings.setupMode());
|
||||
|
||||
// set number of trackers that can have high accel before taps
|
||||
// are rejected
|
||||
if (tapDetectionSettings.hasNumberTrackersOverThreshold()) {
|
||||
tapDetectionConfig
|
||||
.setNumberTrackersOverThreshold(
|
||||
tapDetectionSettings.numberTrackersOverThreshold()
|
||||
);
|
||||
}
|
||||
|
||||
// set tap detection delays
|
||||
if (tapDetectionSettings.hasYawResetDelay()) {
|
||||
tapDetectionConfig.setYawResetDelay(tapDetectionSettings.yawResetDelay());
|
||||
|
||||
@@ -510,6 +510,9 @@ public class HumanSkeleton {
|
||||
|
||||
// Rebuild the bone list
|
||||
resetBones();
|
||||
|
||||
// Update tap detection's trackers
|
||||
tapDetectionManager.updateConfig();
|
||||
}
|
||||
|
||||
protected void setComputedTracker(Tracker tracker) {
|
||||
|
||||
@@ -23,9 +23,9 @@ public class TapDetection {
|
||||
// hyperparameters
|
||||
private static final float NS_CONVERTER = 1.0e9f;
|
||||
private static final float NEEDED_ACCEL_DELTA = 6.0f;
|
||||
private static final float ALLOWED_BODY_ACCEL = 1.5f;
|
||||
private static final float ALLOWED_BODY_ACCEL = 2.5f;
|
||||
private static final float ALLOWED_BODY_ACCEL_SQUARED = ALLOWED_BODY_ACCEL * ALLOWED_BODY_ACCEL;
|
||||
private static final float CLUMP_TIME_NS = 0.08f * NS_CONVERTER;
|
||||
private static final float CLUMP_TIME_NS = 0.06f * NS_CONVERTER;
|
||||
private float timeWindowNS = 0.6f * NS_CONVERTER;
|
||||
|
||||
// state
|
||||
@@ -114,8 +114,9 @@ public class TapDetection {
|
||||
}
|
||||
|
||||
// if waiting for low accel
|
||||
if (accelDelta < ALLOWED_BODY_ACCEL)
|
||||
if (getMaxAccel() < ALLOWED_BODY_ACCEL) {
|
||||
waitForLowAccel = false;
|
||||
}
|
||||
|
||||
// remove old taps from the list (if they are too old)
|
||||
if (!tapTimes.isEmpty()) {
|
||||
@@ -134,12 +135,15 @@ public class TapDetection {
|
||||
|
||||
// get the amount of taps in the list
|
||||
// and set the detection time
|
||||
int newTaps = getTapEvents();
|
||||
int newTaps = tapTimes.size();
|
||||
if (newTaps > taps) {
|
||||
taps = newTaps;
|
||||
detectionTime = time;
|
||||
}
|
||||
|
||||
if (time - detectionTime > timeWindowNS) {
|
||||
tapTimes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private float getAccelDelta() {
|
||||
@@ -154,20 +158,14 @@ public class TapDetection {
|
||||
return max - min;
|
||||
}
|
||||
|
||||
// return the number of distinct tap events in tapTimes
|
||||
private int getTapEvents() {
|
||||
if (tapTimes.isEmpty())
|
||||
return 0;
|
||||
|
||||
int tapEvents = 1;
|
||||
float lastTapTime = tapTimes.getFirst();
|
||||
for (Float tapTime : tapTimes) {
|
||||
if (tapTime - lastTapTime > CLUMP_TIME_NS) {
|
||||
tapEvents++;
|
||||
lastTapTime = tapTime;
|
||||
private float getMaxAccel() {
|
||||
float max = 0.0f;
|
||||
for (float[] val : accelList) {
|
||||
if (val[0] > max) {
|
||||
max = val[0];
|
||||
}
|
||||
}
|
||||
return tapEvents;
|
||||
return max;
|
||||
}
|
||||
|
||||
// returns true if the user is not imparting more than allowedBodyAccel of
|
||||
|
||||
@@ -68,10 +68,6 @@ public class TapDetectionManager {
|
||||
this.resetHandler = resetHandler;
|
||||
this.tapSetupHandler = tapSetupHandler;
|
||||
|
||||
yawResetDetector = new TapDetection(skeleton, getTrackerToWatchYawReset());
|
||||
fullResetDetector = new TapDetection(skeleton, getTrackerToWatchFullReset());
|
||||
mountingResetDetector = new TapDetection(skeleton, getTrackerToWatchMountingReset());
|
||||
|
||||
// a list of tap detectors for each tracker
|
||||
tapDetectors = new ArrayList<>();
|
||||
for (Tracker tracker : trackers) {
|
||||
@@ -80,25 +76,19 @@ public class TapDetectionManager {
|
||||
tapDetectors.add(tapDetector);
|
||||
}
|
||||
|
||||
// since this config value is only modified by editing the config file,
|
||||
// we can set it here
|
||||
yawResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
fullResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
mountingResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
public void updateConfig() {
|
||||
// check the skeleton for new trackers
|
||||
yawResetDetector = new TapDetection(skeleton, getTrackerToWatchYawReset());
|
||||
fullResetDetector = new TapDetection(skeleton, getTrackerToWatchFullReset());
|
||||
mountingResetDetector = new TapDetection(skeleton, getTrackerToWatchMountingReset());
|
||||
|
||||
if (this.config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.yawResetDelayNs = config.getYawResetDelay() * NS_CONVERTER;
|
||||
this.fullResetDelayNs = config.getFullResetDelay() * NS_CONVERTER;
|
||||
this.mountingResetDelayNs = config.getMountingResetDelay() * NS_CONVERTER;
|
||||
@@ -111,6 +101,18 @@ public class TapDetectionManager {
|
||||
yawResetDetector.setMaxTaps(yawResetTaps);
|
||||
fullResetDetector.setMaxTaps(fullResetTaps);
|
||||
mountingResetDetector.setMaxTaps(mountingResetTaps);
|
||||
yawResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
fullResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
mountingResetDetector
|
||||
.setNumberTrackersOverThreshold(
|
||||
config.getNumberTrackersOverThreshold()
|
||||
);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
Submodule solarxr-protocol updated: e70c764ec0...e0718504e7
Reference in New Issue
Block a user