mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
add additional functionality to tap detection (#375)
This commit is contained in:
@@ -49,8 +49,15 @@ interface SettingsForm {
|
||||
skatingCorrection: boolean;
|
||||
};
|
||||
tapDetection: {
|
||||
tapMountingResetEnabled: boolean;
|
||||
tapQuickResetEnabled: boolean;
|
||||
tapResetEnabled: boolean;
|
||||
tapQuickResetDelay: number;
|
||||
tapResetDelay: number;
|
||||
tapMountingResetDelay: number;
|
||||
tapQuickResetTaps: number;
|
||||
tapResetTaps: number;
|
||||
tapMountingResetTaps: number;
|
||||
};
|
||||
legTweaks: {
|
||||
correctionStrength: number;
|
||||
@@ -79,7 +86,17 @@ const defaultValues = {
|
||||
skatingCorrection: false,
|
||||
},
|
||||
filtering: { amount: 0.1, type: FilteringType.NONE },
|
||||
tapDetection: { tapResetEnabled: false, tapResetDelay: 0.2 },
|
||||
tapDetection: {
|
||||
tapMountingResetEnabled: false,
|
||||
tapQuickResetEnabled: false,
|
||||
tapResetEnabled: false,
|
||||
tapQuickResetDelay: 0.2,
|
||||
tapResetDelay: 1.0,
|
||||
tapMountingResetDelay: 1.0,
|
||||
tapQuickResetTaps: 2,
|
||||
tapResetTaps: 3,
|
||||
tapMountingResetTaps: 3,
|
||||
},
|
||||
legTweaks: { correctionStrength: 0.3 },
|
||||
interface: { devmode: false, watchNewDevices: true },
|
||||
};
|
||||
@@ -125,8 +142,15 @@ export function GeneralSettings() {
|
||||
settings.modelSettings = modelSettings;
|
||||
|
||||
const tapDetection = new TapDetectionSettingsT();
|
||||
tapDetection.tapResetEnabled = values.tapDetection.tapResetEnabled;
|
||||
tapDetection.tapResetDelay = values.tapDetection.tapResetDelay;
|
||||
tapDetection.tapResetEnabled = values.tapDetection.tapResetEnabled;
|
||||
tapDetection.tapResetTaps = values.tapDetection.tapResetTaps;
|
||||
tapDetection.tapQuickResetDelay = values.tapDetection.tapQuickResetDelay;
|
||||
tapDetection.tapQuickResetEnabled = values.tapDetection.tapQuickResetEnabled;
|
||||
tapDetection.tapQuickResetTaps = values.tapDetection.tapQuickResetTaps;
|
||||
tapDetection.tapMountingResetEnabled = values.tapDetection.tapMountingResetEnabled;
|
||||
tapDetection.tapMountingResetDelay = values.tapDetection.tapMountingResetDelay;
|
||||
tapDetection.tapMountingResetTaps = values.tapDetection.tapMountingResetTaps;
|
||||
settings.tapDetectionSettings = tapDetection;
|
||||
|
||||
const filtering = new FilteringSettingsT();
|
||||
@@ -182,12 +206,33 @@ export function GeneralSettings() {
|
||||
|
||||
if (settings.tapDetectionSettings) {
|
||||
formData.tapDetection = {
|
||||
tapResetDelay:
|
||||
settings.tapDetectionSettings.tapResetDelay ||
|
||||
defaultValues.tapDetection.tapResetDelay,
|
||||
tapQuickResetEnabled:
|
||||
settings.tapDetectionSettings.tapQuickResetEnabled ||
|
||||
defaultValues.tapDetection.tapQuickResetEnabled,
|
||||
tapResetEnabled:
|
||||
settings.tapDetectionSettings.tapResetEnabled ||
|
||||
defaultValues.tapDetection.tapResetEnabled,
|
||||
tapMountingResetEnabled:
|
||||
settings.tapDetectionSettings.tapMountingResetEnabled ||
|
||||
defaultValues.tapDetection.tapMountingResetEnabled,
|
||||
tapQuickResetDelay:
|
||||
settings.tapDetectionSettings.tapQuickResetDelay ||
|
||||
defaultValues.tapDetection.tapQuickResetDelay,
|
||||
tapResetDelay:
|
||||
settings.tapDetectionSettings.tapResetDelay ||
|
||||
defaultValues.tapDetection.tapResetDelay,
|
||||
tapMountingResetDelay:
|
||||
settings.tapDetectionSettings.tapMountingResetDelay ||
|
||||
defaultValues.tapDetection.tapMountingResetDelay,
|
||||
tapQuickResetTaps:
|
||||
settings.tapDetectionSettings.tapQuickResetTaps ||
|
||||
defaultValues.tapDetection.tapQuickResetTaps,
|
||||
tapResetTaps:
|
||||
settings.tapDetectionSettings.tapResetTaps ||
|
||||
defaultValues.tapDetection.tapResetTaps,
|
||||
tapMountingResetTaps:
|
||||
settings.tapDetectionSettings.tapMountingResetTaps ||
|
||||
defaultValues.tapDetection.tapMountingResetTaps,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -464,23 +509,98 @@ export function GeneralSettings() {
|
||||
{t('settings.general.gesture-control.description')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-5">
|
||||
<div className="grid sm:grid-cols-3 gap-5 pb-2">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="tapDetection.tapQuickResetEnabled"
|
||||
label={t('settings.general.gesture-control.quickResetEnabled')}
|
||||
/>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="tapDetection.tapResetEnabled"
|
||||
label={t('settings.general.gesture-control.enable')}
|
||||
label={t('settings.general.gesture-control.resetEnabled')}
|
||||
/>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="tapDetection.tapMountingResetEnabled"
|
||||
label={t('settings.general.gesture-control.mountingResetEnabled')}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-3 gap-5 pb-2">
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapResetDelay"
|
||||
label={t('settings.general.gesture-control.delay')}
|
||||
name="tapDetection.tapQuickResetDelay"
|
||||
label={t('settings.general.gesture-control.quickResetDelay')}
|
||||
valueLabelFormat={(value) => `${Math.round(value * 10) / 10} s`}
|
||||
min={0.2}
|
||||
max={3.0}
|
||||
step={0.2}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapResetDelay"
|
||||
label={t('settings.general.gesture-control.resetDelay')}
|
||||
valueLabelFormat={(value) => `${Math.round(value * 10) / 10} s`}
|
||||
min={0.2}
|
||||
max={3.0}
|
||||
step={0.2}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapMountingResetDelay"
|
||||
label={t('settings.general.gesture-control.mountingResetDelay')}
|
||||
valueLabelFormat={(value) => `${Math.round(value * 10) / 10} s`}
|
||||
min={0.2}
|
||||
max={3.0}
|
||||
step={0.2}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-3 gap-5 pb-2">
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapQuickResetTaps"
|
||||
label={t('settings.general.gesture-control.quickResetTaps')}
|
||||
valueLabelFormat={(value) =>
|
||||
`${Math.round(value)} ${t(
|
||||
'settings.general.gesture-control.taps'
|
||||
)}`
|
||||
}
|
||||
min={2}
|
||||
max={3}
|
||||
step={1}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapResetTaps"
|
||||
label={t('settings.general.gesture-control.resetTaps')}
|
||||
valueLabelFormat={(value) =>
|
||||
`${Math.round(value)} ${t(
|
||||
'settings.general.gesture-control.taps'
|
||||
)}`
|
||||
}
|
||||
min={2}
|
||||
max={3}
|
||||
step={1}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="tapDetection.tapMountingResetTaps"
|
||||
label={t('settings.general.gesture-control.mountingResetTaps')}
|
||||
valueLabelFormat={(value) =>
|
||||
`${Math.round(value)} ${t(
|
||||
'settings.general.gesture-control.taps'
|
||||
)}`
|
||||
}
|
||||
min={2}
|
||||
max={3}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SettingsPageLayout>
|
||||
|
||||
@@ -230,10 +230,18 @@
|
||||
},
|
||||
"gesture-control": {
|
||||
"title": "Gesture control",
|
||||
"subtitle": "Double Tap quick reset",
|
||||
"description": "Enable or disable double tap quick reset. When enabled double tapping anywhere on the highest torso tracker will activate a quick reset. Delay is the time between registering a tap and resetting.",
|
||||
"enable": "Double tap quick reset",
|
||||
"delay": "Delay"
|
||||
"subtitle": "Tap based resets",
|
||||
"description": "Allows for resets to be triggered by tapping a tracker. The tracker highest up on your torso is used for Quick Reset, the tracker highest up on your left leg is used for 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",
|
||||
"taps": "taps",
|
||||
"quickResetEnabled": "Enable tap to quick reset",
|
||||
"quickResetDelay": "Quick reset delay",
|
||||
"quickResetTaps": "Taps for quick reset",
|
||||
"resetEnabled": "Enable tap to reset",
|
||||
"resetDelay": "Reset delay",
|
||||
"resetTaps": "Taps for reset",
|
||||
"mountingResetEnabled": "Enable tap to reset mounting",
|
||||
"mountingResetDelay": "Mounting reset delay",
|
||||
"mountingResetTaps": "Taps for mounting reset"
|
||||
},
|
||||
"interface": {
|
||||
"title": "Interface",
|
||||
|
||||
@@ -227,10 +227,18 @@
|
||||
},
|
||||
"gesture-control": {
|
||||
"title": "gestauw cantwol",
|
||||
"subtitle": "dubble bap qwick weset",
|
||||
"description": "enawbwe owr disawbwe dubble bap qwick weset. when enawbwed dubble bapping enywhewe on teh hyighest towso twackaw wil activate a qwick weset. deway is teh time between wowgistewing a bap and wowstting.",
|
||||
"enable": "dubble bap qwick weset",
|
||||
"delay": "deway"
|
||||
"subtitle": "bap bwased wesets",
|
||||
"description": "allaws owr wesets wo be twiggered by wapping a twackaw. The twackaw hyighest wup on youwur twursuu is uwued for qwick weset, wa twackaw hyighest up on youwur weft weg is uwused for weset, and teh twackaw hyighest up on youwur wight weg is used for mmountwing weset. It showold we wentioned what waps muwst happen within 0.6 seconds wo be wegistered",
|
||||
"taps": "baps",
|
||||
"quickResetEnabled": "enable wap to quwuick weset",
|
||||
"quickResetDelay": "quwuick weset deway",
|
||||
"quickResetTaps": "baps for quwuick weset",
|
||||
"resetEnabled": "enable wap to weset",
|
||||
"resetDelay": "weset deway",
|
||||
"resetTaps": "baps for weset",
|
||||
"mountingResetEnabled": "enable wap to weset mountwing",
|
||||
"mountingResetDelay": "mountwing weset deway",
|
||||
"mountingResetTaps": "baps for mountwing weset"
|
||||
},
|
||||
"interface": {
|
||||
"title": "intewface",
|
||||
|
||||
@@ -1,23 +1,99 @@
|
||||
package dev.slimevr.config;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
|
||||
// handles the tap detection config
|
||||
// this involves the number of taps, the delay, and whether or not the feature is enabled
|
||||
// for each reset type
|
||||
public class TapDetectionConfig {
|
||||
|
||||
private float delay = 0.2f;
|
||||
private boolean enabled = true;
|
||||
private float quickResetDelay = 0.2f;
|
||||
private float resetDelay = 1.0f;
|
||||
private float mountingResetDelay = 1.0f;
|
||||
private boolean quickResetEnabled = true;
|
||||
private boolean resetEnabled = true;
|
||||
private boolean mountingResetEnabled = true;
|
||||
private int quickResetTaps = 2;
|
||||
private int resetTaps = 3;
|
||||
private int mountingResetTaps = 3;
|
||||
|
||||
public float getDelay() {
|
||||
return delay;
|
||||
public float getQuickResetDelay() {
|
||||
return quickResetDelay;
|
||||
}
|
||||
|
||||
public void setDelay(float delay) {
|
||||
this.delay = delay;
|
||||
public void setQuickResetDelay(float quickResetDelay) {
|
||||
this.quickResetDelay = quickResetDelay;
|
||||
}
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
public float getResetDelay() {
|
||||
return resetDelay;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
public void setResetDelay(float resetDelay) {
|
||||
this.resetDelay = resetDelay;
|
||||
}
|
||||
|
||||
public float getMountingResetDelay() {
|
||||
return mountingResetDelay;
|
||||
}
|
||||
|
||||
public void setMountingResetDelay(float mountingResetDelay) {
|
||||
this.mountingResetDelay = mountingResetDelay;
|
||||
}
|
||||
|
||||
public boolean getQuickResetEnabled() {
|
||||
return quickResetEnabled;
|
||||
}
|
||||
|
||||
public void setQuickResetEnabled(boolean quickResetEnabled) {
|
||||
this.quickResetEnabled = quickResetEnabled;
|
||||
}
|
||||
|
||||
public boolean getResetEnabled() {
|
||||
return resetEnabled;
|
||||
}
|
||||
|
||||
public void setResetEnabled(boolean resetEnabled) {
|
||||
this.resetEnabled = resetEnabled;
|
||||
}
|
||||
|
||||
public boolean getMountingResetEnabled() {
|
||||
return mountingResetEnabled;
|
||||
}
|
||||
|
||||
public void setMountingResetEnabled(boolean mountingResetEnabled) {
|
||||
this.mountingResetEnabled = mountingResetEnabled;
|
||||
}
|
||||
|
||||
public int getQuickResetTaps() {
|
||||
return quickResetTaps;
|
||||
}
|
||||
|
||||
// clamp to 2-3 to prevent errors
|
||||
public void setQuickResetTaps(int quickResetTaps) {
|
||||
if (quickResetTaps > 3 || quickResetTaps < 2)
|
||||
FastMath.clamp(quickResetTaps, 2, 3);
|
||||
this.quickResetTaps = quickResetTaps;
|
||||
}
|
||||
|
||||
public int getResetTaps() {
|
||||
return resetTaps;
|
||||
}
|
||||
|
||||
public void setResetTaps(int resetTaps) {
|
||||
if (resetTaps > 3 || resetTaps < 2)
|
||||
FastMath.clamp(resetTaps, 2, 3);
|
||||
this.resetTaps = resetTaps;
|
||||
}
|
||||
|
||||
public int getMountingResetTaps() {
|
||||
return mountingResetTaps;
|
||||
}
|
||||
|
||||
public void setMountingResetTaps(int mountingResetTaps) {
|
||||
if (mountingResetTaps > 3 || mountingResetTaps < 2)
|
||||
FastMath.clamp(mountingResetTaps, 2, 3);
|
||||
this.mountingResetTaps = mountingResetTaps;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,15 @@ public class RPCSettingsBuilder {
|
||||
return TapDetectionSettings
|
||||
.createTapDetectionSettings(
|
||||
fbb,
|
||||
tapDetectionConfig.getDelay(),
|
||||
tapDetectionConfig.getEnabled()
|
||||
tapDetectionConfig.getResetDelay(),
|
||||
tapDetectionConfig.getResetEnabled(),
|
||||
tapDetectionConfig.getResetTaps(),
|
||||
tapDetectionConfig.getQuickResetDelay(),
|
||||
tapDetectionConfig.getQuickResetEnabled(),
|
||||
tapDetectionConfig.getQuickResetTaps(),
|
||||
tapDetectionConfig.getMountingResetDelay(),
|
||||
tapDetectionConfig.getMountingResetEnabled(),
|
||||
tapDetectionConfig.getMountingResetTaps()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,10 +174,40 @@ public record RPCSettingsHandler(RPCHandler rpcHandler, ProtocolAPI api) {
|
||||
var tapDetectionSettings = req.tapDetectionSettings();
|
||||
|
||||
if (tapDetectionSettings != null) {
|
||||
tapDetectionConfig.setEnabled(tapDetectionSettings.tapResetEnabled());
|
||||
// enable/disable tap detection
|
||||
tapDetectionConfig
|
||||
.setQuickResetEnabled(tapDetectionSettings.tapQuickResetEnabled());
|
||||
tapDetectionConfig
|
||||
.setResetEnabled(tapDetectionSettings.tapResetEnabled());
|
||||
tapDetectionConfig
|
||||
.setMountingResetEnabled(tapDetectionSettings.tapMountingResetEnabled());
|
||||
|
||||
// set tap detection delays
|
||||
if (tapDetectionSettings.hasTapQuickResetDelay()) {
|
||||
tapDetectionConfig
|
||||
.setQuickResetDelay(tapDetectionSettings.tapQuickResetDelay());
|
||||
}
|
||||
if (tapDetectionSettings.hasTapResetDelay()) {
|
||||
tapDetectionConfig.setDelay(tapDetectionSettings.tapResetDelay());
|
||||
tapDetectionConfig
|
||||
.setResetDelay(tapDetectionSettings.tapResetDelay());
|
||||
}
|
||||
if (tapDetectionSettings.hasTapMountingResetDelay()) {
|
||||
tapDetectionConfig
|
||||
.setMountingResetDelay(tapDetectionSettings.tapMountingResetDelay());
|
||||
}
|
||||
|
||||
// set the number of taps required for each action
|
||||
if (tapDetectionSettings.hasTapQuickResetTaps()) {
|
||||
tapDetectionConfig
|
||||
.setQuickResetTaps(tapDetectionSettings.tapQuickResetTaps());
|
||||
}
|
||||
if (tapDetectionSettings.hasTapResetTaps()) {
|
||||
tapDetectionConfig
|
||||
.setResetTaps(tapDetectionSettings.tapResetTaps());
|
||||
}
|
||||
if (tapDetectionSettings.hasTapMountingResetTaps()) {
|
||||
tapDetectionConfig
|
||||
.setMountingResetTaps(tapDetectionSettings.tapMountingResetTaps());
|
||||
}
|
||||
|
||||
this.api.server.humanPoseProcessor.getSkeleton().updateTapDetectionConfig();
|
||||
|
||||
@@ -139,7 +139,7 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
|
||||
// #endregion
|
||||
|
||||
// #region tap detection
|
||||
protected TapDetection tapDetection = new TapDetection(this);
|
||||
protected TapDetectionManager tapDetectionManager = new TapDetectionManager(this);
|
||||
// #endregion
|
||||
|
||||
// #region Constructors
|
||||
@@ -165,7 +165,7 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
|
||||
setTrackersFromServer(server);
|
||||
skeletonConfig.loadFromConfig(server.getConfigManager());
|
||||
|
||||
tapDetection = new TapDetection(
|
||||
tapDetectionManager = new TapDetectionManager(
|
||||
this,
|
||||
server.getVrcOSCHandler(),
|
||||
server.getConfigManager().getVrConfig().getTapDetection()
|
||||
@@ -790,7 +790,7 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void updatePose() {
|
||||
tapDetection.update();
|
||||
tapDetectionManager.update();
|
||||
updateLocalTransforms();
|
||||
updateRootTrackers();
|
||||
updateComputedTrackers();
|
||||
@@ -1846,7 +1846,7 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
|
||||
}
|
||||
|
||||
public void updateTapDetectionConfig() {
|
||||
tapDetection.updateConfig();
|
||||
tapDetectionManager.updateConfig();
|
||||
}
|
||||
|
||||
public void updateLegTweaksConfig() {
|
||||
|
||||
@@ -27,7 +27,7 @@ public class LegTweakBuffer {
|
||||
public static final int FOOT_ACCEL = 3;
|
||||
public static final int ANKLE_ACCEL = 4;
|
||||
|
||||
public static final float NS_CONVERT = 1000000000.0f;
|
||||
public static final float NS_CONVERT = 1.0e9f;
|
||||
private static final Vector3f GRAVITY = new Vector3f(0, -9.81f, 0);
|
||||
private static final float GRAVITY_MAGNITUDE = GRAVITY.length();
|
||||
private static final int BUFFER_LEN = 10;
|
||||
|
||||
@@ -3,37 +3,35 @@ package dev.slimevr.vr.processor.skeleton;
|
||||
import java.util.LinkedList;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.config.TapDetectionConfig;
|
||||
import dev.slimevr.osc.VRCOSCHandler;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
|
||||
|
||||
// class that monitors the acceleration of the waist, hip, or chest trackers to detect taps
|
||||
// and use this to trigger a quick reset (if your wondering why no single tap class exists, it's because
|
||||
// and use this to trigger a varaity of resets (if your wondering why no single tap class exists, it's because
|
||||
// to many false positives)
|
||||
public class TapDetection {
|
||||
|
||||
// server and related classes
|
||||
private HumanSkeleton skeleton;
|
||||
private VRCOSCHandler oscHandler;
|
||||
private TapDetectionConfig config;
|
||||
|
||||
|
||||
// tap detection
|
||||
private boolean enabled = false;
|
||||
private LinkedList<float[]> accelList = new LinkedList<>();
|
||||
private LinkedList<Float> tapTimes = new LinkedList<>();
|
||||
private float resetDelayNs = 0.20f * 1000000000.0f;
|
||||
private Tracker trackerToWatch = null;
|
||||
|
||||
// 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 * 1000000000.0f;
|
||||
private static final float TIME_WINDOW_NS = 0.5f * 1000000000.0f;
|
||||
private static final float CLUMP_TIME_NS = 0.03f * NS_CONVERTER;
|
||||
private static final float TIME_WINDOW_NS = 0.6f * NS_CONVERTER;
|
||||
|
||||
// state
|
||||
private float resetRequestTime = -1.0f;
|
||||
private float detectionTime = -1.0f;
|
||||
private boolean doubleTaped = false;
|
||||
private boolean tripleTaped = false;
|
||||
|
||||
public TapDetection(HumanSkeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
@@ -41,18 +39,10 @@ public class TapDetection {
|
||||
|
||||
public TapDetection(
|
||||
HumanSkeleton skeleton,
|
||||
VRCOSCHandler oscHandler,
|
||||
TapDetectionConfig config
|
||||
Tracker trackerToWatch
|
||||
) {
|
||||
this.skeleton = skeleton;
|
||||
this.oscHandler = oscHandler;
|
||||
this.config = config;
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
public void updateConfig() {
|
||||
this.enabled = config.getEnabled();
|
||||
this.resetDelayNs = config.getDelay() * 1000000000.0f;
|
||||
this.trackerToWatch = trackerToWatch;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
@@ -63,29 +53,42 @@ public class TapDetection {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// set the tracker to watch and detect taps on
|
||||
public void setTrackerToWatch(Tracker tracker) {
|
||||
trackerToWatch = tracker;
|
||||
}
|
||||
|
||||
public boolean getDoubleTapped() {
|
||||
return doubleTaped;
|
||||
}
|
||||
|
||||
public boolean getTripleTapped() {
|
||||
return tripleTaped;
|
||||
}
|
||||
|
||||
public float getDetectionTime() {
|
||||
return detectionTime;
|
||||
}
|
||||
|
||||
// reset the lists for detecting taps
|
||||
public void resetDetector() {
|
||||
tapTimes.clear();
|
||||
accelList.clear();
|
||||
doubleTaped = false;
|
||||
tripleTaped = false;
|
||||
}
|
||||
|
||||
// main function for tap detection
|
||||
public void update() {
|
||||
if (skeleton == null || !enabled)
|
||||
return;
|
||||
|
||||
Tracker tracker = getTrackerToWatch();
|
||||
if (tracker == null)
|
||||
if (trackerToWatch == null)
|
||||
return;
|
||||
|
||||
// check if we should reset
|
||||
if (resetRequestTime != -1.0f) {
|
||||
if (System.nanoTime() - resetRequestTime > resetDelayNs) {
|
||||
if (oscHandler != null)
|
||||
oscHandler.yawAlign();
|
||||
skeleton.resetTrackersYaw();
|
||||
resetRequestTime = -1.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// get the acceleration of the tracker and add it to the list
|
||||
Vector3f accel = new Vector3f();
|
||||
tracker.getAcceleration(accel);
|
||||
trackerToWatch.getAcceleration(accel);
|
||||
float time = System.nanoTime();
|
||||
float[] listval = { accel.length(), time };
|
||||
accelList.add(listval);
|
||||
@@ -110,7 +113,7 @@ public class TapDetection {
|
||||
}
|
||||
|
||||
// if the user is moving their body too much, reset the tap list
|
||||
if (!isUserStatic(tracker)) {
|
||||
if (!isUserStatic(trackerToWatch)) {
|
||||
tapTimes.clear();
|
||||
accelList.clear();
|
||||
}
|
||||
@@ -119,29 +122,17 @@ public class TapDetection {
|
||||
int tapEvents = getTapEvents();
|
||||
|
||||
// if there are two tap events and the user is moving relatively slowly,
|
||||
// reset the skeleton
|
||||
// quick reset
|
||||
if (tapEvents == 2) {
|
||||
resetRequestTime = System.nanoTime();
|
||||
// reset the list
|
||||
tapTimes.clear();
|
||||
accelList.clear();
|
||||
doubleTaped = true;
|
||||
detectionTime = time;
|
||||
} else if (tapEvents >= 3) {
|
||||
detectionTime = time;
|
||||
tripleTaped = true;
|
||||
doubleTaped = false;
|
||||
}
|
||||
}
|
||||
|
||||
// returns either the chest tracker, hip tracker, or waist tracker depending
|
||||
// on which one is available
|
||||
// if none are available, returns null
|
||||
public Tracker getTrackerToWatch() {
|
||||
if (skeleton.chestTracker != null)
|
||||
return skeleton.chestTracker;
|
||||
else if (skeleton.hipTracker != null)
|
||||
return skeleton.hipTracker;
|
||||
else if (skeleton.waistTracker != null)
|
||||
return skeleton.waistTracker;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private float getAccelDelta() {
|
||||
float max = -999.9f;
|
||||
float min = 999.9f;
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import dev.slimevr.config.TapDetectionConfig;
|
||||
import dev.slimevr.osc.VRCOSCHandler;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
|
||||
|
||||
// handles tap detection for the skeleton
|
||||
public class TapDetectionManager {
|
||||
|
||||
// server and related classes
|
||||
private HumanSkeleton skeleton;
|
||||
private VRCOSCHandler oscHandler;
|
||||
private TapDetectionConfig config;
|
||||
|
||||
// tap detectors
|
||||
private TapDetection quickResetDetector;
|
||||
private TapDetection resetDetector;
|
||||
private TapDetection mountingResetDetector;
|
||||
|
||||
// number of taps to detect
|
||||
private int quickResetTaps = 2;
|
||||
private int resetTaps = 3;
|
||||
private int mountingResetTaps = 3;
|
||||
|
||||
// delay
|
||||
private static final float NS_CONVERTER = 1.0e9f;
|
||||
private float resetDelayNs = 0.20f * NS_CONVERTER;
|
||||
private float quickResetDelayNs = 1.00f * NS_CONVERTER;
|
||||
private float mountingResetDelayNs = 1.00f * NS_CONVERTER;
|
||||
|
||||
public TapDetectionManager(HumanSkeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
public TapDetectionManager(
|
||||
HumanSkeleton skeleton,
|
||||
VRCOSCHandler oscHandler,
|
||||
TapDetectionConfig config
|
||||
) {
|
||||
this.skeleton = skeleton;
|
||||
this.oscHandler = oscHandler;
|
||||
this.config = config;
|
||||
|
||||
quickResetDetector = new TapDetection(skeleton, getTrackerToWatchQuickReset());
|
||||
resetDetector = new TapDetection(skeleton, getTrackerToWatchReset());
|
||||
mountingResetDetector = new TapDetection(skeleton, getTrackerToWatchMountingReset());
|
||||
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
public void updateConfig() {
|
||||
this.quickResetDelayNs = config.getQuickResetDelay() * NS_CONVERTER;
|
||||
this.resetDelayNs = config.getResetDelay() * NS_CONVERTER;
|
||||
this.mountingResetDelayNs = config.getMountingResetDelay() * NS_CONVERTER;
|
||||
quickResetDetector.setEnabled(config.getQuickResetEnabled());
|
||||
resetDetector.setEnabled(config.getResetEnabled());
|
||||
mountingResetDetector.setEnabled(config.getMountingResetEnabled());
|
||||
quickResetTaps = config.getQuickResetTaps();
|
||||
resetTaps = config.getResetTaps();
|
||||
mountingResetTaps = config.getMountingResetTaps();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (quickResetDetector == null || resetDetector == null || mountingResetDetector == null)
|
||||
return;
|
||||
// update the tap detectors
|
||||
quickResetDetector.update();
|
||||
resetDetector.update();
|
||||
mountingResetDetector.update();
|
||||
|
||||
// check if any tap detectors have detected taps
|
||||
checkQuickReset();
|
||||
checkReset();
|
||||
checkMountingReset();
|
||||
}
|
||||
|
||||
private void checkQuickReset() {
|
||||
boolean tapped = (quickResetTaps == 2)
|
||||
? quickResetDetector.getDoubleTapped()
|
||||
: quickResetDetector.getTripleTapped();
|
||||
if (
|
||||
tapped && System.nanoTime() - quickResetDetector.getDetectionTime() > quickResetDelayNs
|
||||
) {
|
||||
if (oscHandler != null)
|
||||
oscHandler.yawAlign();
|
||||
skeleton.resetTrackersYaw();
|
||||
quickResetDetector.resetDetector();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkReset() {
|
||||
boolean tapped = (resetTaps == 2)
|
||||
? resetDetector.getDoubleTapped()
|
||||
: resetDetector.getTripleTapped();
|
||||
if (
|
||||
tapped && System.nanoTime() - resetDetector.getDetectionTime() > resetDelayNs
|
||||
) {
|
||||
if (oscHandler != null)
|
||||
oscHandler.yawAlign();
|
||||
skeleton.resetTrackersFull();
|
||||
resetDetector.resetDetector();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkMountingReset() {
|
||||
boolean tapped = (mountingResetTaps == 2)
|
||||
? mountingResetDetector.getDoubleTapped()
|
||||
: mountingResetDetector.getTripleTapped();
|
||||
if (
|
||||
tapped
|
||||
&& System.nanoTime() - mountingResetDetector.getDetectionTime()
|
||||
> mountingResetDelayNs
|
||||
) {
|
||||
skeleton.resetTrackersMounting();
|
||||
mountingResetDetector.resetDetector();
|
||||
}
|
||||
}
|
||||
|
||||
// returns either the chest tracker, hip tracker, or waist tracker depending
|
||||
// on which one is available
|
||||
// if none are available, returns null
|
||||
private Tracker getTrackerToWatchQuickReset() {
|
||||
if (skeleton.chestTracker != null)
|
||||
return skeleton.chestTracker;
|
||||
else if (skeleton.hipTracker != null)
|
||||
return skeleton.hipTracker;
|
||||
else if (skeleton.waistTracker != null)
|
||||
return skeleton.waistTracker;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private Tracker getTrackerToWatchReset() {
|
||||
if (skeleton.leftUpperLegTracker != null)
|
||||
return skeleton.leftUpperLegTracker;
|
||||
else if (skeleton.leftLowerLegTracker != null)
|
||||
return skeleton.leftLowerLegTracker;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Tracker getTrackerToWatchMountingReset() {
|
||||
if (skeleton.rightUpperLegTracker != null)
|
||||
return skeleton.rightUpperLegTracker;
|
||||
else if (skeleton.rightLowerLegTracker != null)
|
||||
return skeleton.rightLowerLegTracker;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Submodule solarxr-protocol updated: f45f290712...8466dbc6a4
Reference in New Issue
Block a user