mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Improve tap detection (#432)
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
<NumberSelector
|
||||
@@ -655,7 +655,7 @@ export function GeneralSettings() {
|
||||
})
|
||||
}
|
||||
min={2}
|
||||
max={3}
|
||||
max={10}
|
||||
step={1}
|
||||
/>
|
||||
<NumberSelector
|
||||
@@ -670,7 +670,7 @@ export function GeneralSettings() {
|
||||
})
|
||||
}
|
||||
min={2}
|
||||
max={3}
|
||||
max={10}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,20 @@ public class TapDetection {
|
||||
private LinkedList<float[]> accelList = new LinkedList<>();
|
||||
private LinkedList<Float> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user