Improve tap detection (#432)

This commit is contained in:
Collin Kees
2023-01-05 06:09:38 -08:00
committed by GitHub
parent af807aa9c1
commit 77858e11f3
4 changed files with 83 additions and 52 deletions

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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()