diff --git a/gui/src/components/settings/pages/GeneralSettings.tsx b/gui/src/components/settings/pages/GeneralSettings.tsx index 5bcd58c2d..cd78a2527 100644 --- a/gui/src/components/settings/pages/GeneralSettings.tsx +++ b/gui/src/components/settings/pages/GeneralSettings.tsx @@ -539,7 +539,7 @@ export function GeneralSettings() { variant="toggle" outlined control={control} - name="toggles-viveEmulation" + name="toggles.viveEmulation" label={l10n.getString( 'settings-general-fk_settings-vive_emulation-label' )} @@ -640,7 +640,7 @@ export function GeneralSettings() { }) } min={2} - max={3} + max={10} step={1} /> diff --git a/server/src/main/java/dev/slimevr/config/TapDetectionConfig.java b/server/src/main/java/dev/slimevr/config/TapDetectionConfig.java index 74d2ad6f3..dc4a7dfcd 100644 --- a/server/src/main/java/dev/slimevr/config/TapDetectionConfig.java +++ b/server/src/main/java/dev/slimevr/config/TapDetectionConfig.java @@ -17,6 +17,7 @@ public class TapDetectionConfig { private int quickResetTaps = 2; private int resetTaps = 3; private int mountingResetTaps = 3; + private int numberTrackersOverThreshold = 1; public float getQuickResetDelay() { return quickResetDelay; @@ -72,8 +73,7 @@ public class TapDetectionConfig { // clamp to 2-3 to prevent errors public void setQuickResetTaps(int quickResetTaps) { - if (quickResetTaps > 3 || quickResetTaps < 2) - FastMath.clamp(quickResetTaps, 2, 3); + this.quickResetTaps = (int) FastMath.clamp(quickResetTaps, 2, 10); this.quickResetTaps = quickResetTaps; } @@ -82,8 +82,7 @@ public class TapDetectionConfig { } public void setResetTaps(int resetTaps) { - if (resetTaps > 3 || resetTaps < 2) - FastMath.clamp(resetTaps, 2, 3); + this.resetTaps = (int) FastMath.clamp(resetTaps, 2, 10); this.resetTaps = resetTaps; } @@ -92,8 +91,15 @@ public class TapDetectionConfig { } public void setMountingResetTaps(int mountingResetTaps) { - if (mountingResetTaps > 3 || mountingResetTaps < 2) - FastMath.clamp(mountingResetTaps, 2, 3); + this.mountingResetTaps = (int) FastMath.clamp(mountingResetTaps, 2, 10); this.mountingResetTaps = mountingResetTaps; } + + public int getNumberTrackersOverThreshold() { + return numberTrackersOverThreshold; + } + + public void setNumberTrackersOverThreshold(int numberTrackersOverThreshold) { + this.numberTrackersOverThreshold = numberTrackersOverThreshold; + } } diff --git a/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetection.java b/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetection.java index 5d53420c9..b9ccd6455 100644 --- a/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetection.java +++ b/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetection.java @@ -19,19 +19,20 @@ public class TapDetection { private LinkedList accelList = new LinkedList<>(); private LinkedList tapTimes = new LinkedList<>(); private Tracker trackerToWatch = null; + private int numberTrackersOverThreshold = 1; // 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_SQUARED = ALLOWED_BODY_ACCEL * ALLOWED_BODY_ACCEL; - private static final float CLUMP_TIME_NS = 0.03f * NS_CONVERTER; - private static final float TIME_WINDOW_NS = 0.6f * NS_CONVERTER; + private static final float CLUMP_TIME_NS = 0.08f * NS_CONVERTER; + private float timeWindowNS = 0.6f * NS_CONVERTER; // state private float detectionTime = -1.0f; - private boolean doubleTaped = false; - private boolean tripleTaped = false; + private int taps = 0; + private boolean waitForLowAccel = false; public TapDetection(HumanSkeleton skeleton) { this.skeleton = skeleton; @@ -58,24 +59,28 @@ public class TapDetection { trackerToWatch = tracker; } - public boolean getDoubleTapped() { - return doubleTaped; - } - - public boolean getTripleTapped() { - return tripleTaped; + public int getTaps() { + return taps; } public float getDetectionTime() { return detectionTime; } + public void setNumberTrackersOverThreshold(int numberTrackersOverThreshold) { + this.numberTrackersOverThreshold = numberTrackersOverThreshold; + } + // reset the lists for detecting taps public void resetDetector() { tapTimes.clear(); accelList.clear(); - doubleTaped = false; - tripleTaped = false; + taps = 0; + } + + // set the max taps this detector is configured to detect + public void setMaxTaps(int maxTaps) { + timeWindowNS = 0.3f * maxTaps * NS_CONVERTER; } // main function for tap detection @@ -99,13 +104,21 @@ public class TapDetection { } // check for a tap - if (getAccelDelta() > NEEDED_ACCEL_DELTA) { + float accelDelta = getAccelDelta(); + if (accelDelta > NEEDED_ACCEL_DELTA && !waitForLowAccel) { + // after a tap is added to the list, a lower acceleration + // is needed before another tap can be added tapTimes.add(time); + waitForLowAccel = true; } + // if waiting for low accel + if (accelDelta < ALLOWED_BODY_ACCEL) + waitForLowAccel = false; + // remove old taps from the list (if they are too old) if (!tapTimes.isEmpty()) { - while (time - tapTimes.getFirst() > TIME_WINDOW_NS) { + while (time - tapTimes.getFirst() > timeWindowNS) { tapTimes.removeFirst(); if (tapTimes.isEmpty()) return; @@ -119,18 +132,13 @@ public class TapDetection { } // get the amount of taps in the list - int tapEvents = getTapEvents(); - - // if there are two tap events and the user is moving relatively slowly, - // quick reset - if (tapEvents == 2) { - doubleTaped = true; + // and set the detection time + int newTaps = getTapEvents(); + if (newTaps > taps) { + taps = newTaps; detectionTime = time; - } else if (tapEvents >= 3) { - detectionTime = time; - tripleTaped = true; - doubleTaped = false; } + } private float getAccelDelta() { @@ -151,7 +159,7 @@ public class TapDetection { if (tapTimes.isEmpty()) return 0; - int tapEvents = 0; + int tapEvents = 1; float lastTapTime = tapTimes.getFirst(); for (int i = 0; i < tapTimes.size(); i++) { if (tapTimes.get(i) - lastTapTime > CLUMP_TIME_NS) { @@ -167,20 +175,21 @@ public class TapDetection { // you need two or more trackers for this feature to be reliable) private boolean isUserStatic(Tracker trackerToExclude) { Vector3f accel = new Vector3f(); + int num = 0; if (skeleton.chestTracker != null && !skeleton.chestTracker.equals(trackerToExclude)) { skeleton.chestTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if (skeleton.hipTracker != null && !skeleton.hipTracker.equals(trackerToExclude)) { skeleton.hipTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if (skeleton.waistTracker != null && !skeleton.waistTracker.equals(trackerToExclude)) { skeleton.waistTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if ( skeleton.leftUpperLegTracker != null @@ -188,7 +197,7 @@ public class TapDetection { ) { skeleton.leftUpperLegTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if ( skeleton.rightUpperLegTracker != null @@ -196,22 +205,22 @@ public class TapDetection { ) { skeleton.rightUpperLegTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if ( skeleton.leftFootTracker != null && !skeleton.leftFootTracker.equals(trackerToExclude) ) { skeleton.leftFootTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } if ( skeleton.rightFootTracker != null && !skeleton.rightFootTracker.equals(trackerToExclude) ) { skeleton.rightFootTracker.getAcceleration(accel); if (accel.lengthSquared() > ALLOWED_BODY_ACCEL_SQUARED) - return false; + num++; } - return true; + return num < numberTrackersOverThreshold; } } diff --git a/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetectionManager.java b/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetectionManager.java index 2cfb597b2..846ba641a 100644 --- a/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetectionManager.java +++ b/server/src/main/java/dev/slimevr/vr/processor/skeleton/TapDetectionManager.java @@ -1,5 +1,6 @@ package dev.slimevr.vr.processor.skeleton; + import dev.slimevr.config.TapDetectionConfig; import dev.slimevr.osc.VRCOSCHandler; import dev.slimevr.vr.trackers.Tracker; @@ -46,6 +47,21 @@ public class TapDetectionManager { resetDetector = new TapDetection(skeleton, getTrackerToWatchReset()); mountingResetDetector = new TapDetection(skeleton, getTrackerToWatchMountingReset()); + // since this config value is only modified by editing the config file, + // we can set it here + quickResetDetector + .setNumberTrackersOverThreshold( + config.getNumberTrackersOverThreshold() + ); + resetDetector + .setNumberTrackersOverThreshold( + config.getNumberTrackersOverThreshold() + ); + mountingResetDetector + .setNumberTrackersOverThreshold( + config.getNumberTrackersOverThreshold() + ); + updateConfig(); } @@ -59,6 +75,9 @@ public class TapDetectionManager { quickResetTaps = config.getQuickResetTaps(); resetTaps = config.getResetTaps(); mountingResetTaps = config.getMountingResetTaps(); + quickResetDetector.setMaxTaps(quickResetTaps); + resetDetector.setMaxTaps(resetTaps); + mountingResetDetector.setMaxTaps(mountingResetTaps); } public void update() { @@ -76,9 +95,8 @@ public class TapDetectionManager { } private void checkQuickReset() { - boolean tapped = (quickResetTaps == 2) - ? quickResetDetector.getDoubleTapped() - : quickResetDetector.getTripleTapped(); + boolean tapped = (quickResetTaps <= quickResetDetector.getTaps()); + if ( tapped && System.nanoTime() - quickResetDetector.getDetectionTime() > quickResetDelayNs ) { @@ -90,9 +108,8 @@ public class TapDetectionManager { } private void checkReset() { - boolean tapped = (resetTaps == 2) - ? resetDetector.getDoubleTapped() - : resetDetector.getTripleTapped(); + boolean tapped = (resetTaps <= resetDetector.getTaps()); + if ( tapped && System.nanoTime() - resetDetector.getDetectionTime() > resetDelayNs ) { @@ -104,9 +121,8 @@ public class TapDetectionManager { } private void checkMountingReset() { - boolean tapped = (mountingResetTaps == 2) - ? mountingResetDetector.getDoubleTapped() - : mountingResetDetector.getTripleTapped(); + boolean tapped = (mountingResetTaps <= mountingResetDetector.getTaps()); + if ( tapped && System.nanoTime() - mountingResetDetector.getDetectionTime()