From e18bd2d382e927444483466d468be8bee66cff65 Mon Sep 17 00:00:00 2001 From: Collin Date: Thu, 20 Jul 2023 01:06:48 -0700 Subject: [PATCH] Better tap detection (#778) --- .github/workflows/gradle.yaml | 4 +- gui/public/i18n/en/translation.ftl | 11 ++++- .../settings/pages/GeneralSettings.tsx | 36 +++++++++++++++++ .../rpc/settings/RPCSettingsBuilder.java | 3 +- .../rpc/settings/RPCSettingsHandler.java | 9 +++++ .../processor/skeleton/HumanSkeleton.java | 3 ++ .../processor/skeleton/TapDetection.java | 30 +++++++------- .../skeleton/TapDetectionManager.java | 40 ++++++++++--------- solarxr-protocol | 2 +- 9 files changed, 98 insertions(+), 40 deletions(-) diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml index 456971d2b..0c907c06b 100644 --- a/.github/workflows/gradle.yaml +++ b/.github/workflows/gradle.yaml @@ -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 diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index df0ca3508..e2242e5af 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -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 diff --git a/gui/src/components/settings/pages/GeneralSettings.tsx b/gui/src/components/settings/pages/GeneralSettings.tsx index e5ba2f032..c3ecf91f6 100644 --- a/gui/src/components/settings/pages/GeneralSettings.tsx +++ b/gui/src/components/settings/pages/GeneralSettings.tsx @@ -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} /> + {config?.debug && ( +
+ + {l10n.getString( + 'settings-general-gesture_control-numberTrackersOverThreshold' + )} + + + {l10n.getString( + 'settings-general-gesture_control-numberTrackersOverThreshold-description' + )} + + + l10n.getString( + 'settings-general-gesture_control-trackers', + { + amount: Math.round(value), + } + ) + } + min={1} + max={20} + step={1} + /> +
+ )} diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java index 24ea3688a..b634b701e 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java @@ -142,7 +142,8 @@ public class RPCSettingsBuilder { tapDetectionConfig.getMountingResetDelay(), tapDetectionConfig.getMountingResetEnabled(), tapDetectionConfig.getMountingResetTaps(), - tapDetectionConfig.getSetupMode() + tapDetectionConfig.getSetupMode(), + tapDetectionConfig.getNumberTrackersOverThreshold() ); } diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java index 7bc5d194b..f01cfa754 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.java @@ -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()); diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.java b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.java index 610adc862..2a48a3f9c 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.java +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.java @@ -510,6 +510,9 @@ public class HumanSkeleton { // Rebuild the bone list resetBones(); + + // Update tap detection's trackers + tapDetectionManager.updateConfig(); } protected void setComputedTracker(Tracker tracker) { diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetection.java b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetection.java index 183509fb1..5e8355e61 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetection.java +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetection.java @@ -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 diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetectionManager.java b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetectionManager.java index 42dc97516..9fb208664 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetectionManager.java +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/TapDetectionManager.java @@ -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() { diff --git a/solarxr-protocol b/solarxr-protocol index e70c764ec..e0718504e 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit e70c764ec00d05d82fe082b81c6c7abc98671e79 +Subproject commit e0718504e702b025d50e5389802a0bc8d8cdc59d