Leg tweaks (#223)

This commit is contained in:
Collin Kees
2022-08-30 09:50:15 -07:00
committed by GitHub
parent caa11f9dfc
commit 426f914f4d
21 changed files with 1911 additions and 34 deletions

View File

@@ -243,6 +243,24 @@ public class VRServer extends Thread {
});
}
public void setLegTweaksEnabled(boolean value) {
queueTask(() -> {
humanPoseProcessor.setLegTweaksEnabled(value);
});
}
public void setSkatingReductionEnabled(boolean value) {
queueTask(() -> {
humanPoseProcessor.setSkatingCorrectionEnabled(value);
});
}
public void setFloorClipEnabled(boolean value) {
queueTask(() -> {
humanPoseProcessor.setFloorClipEnabled(value);
});
}
public int getTrackersCount() {
return trackers.size();
}

View File

@@ -478,6 +478,9 @@ public class AutoBone {
intermediateOffsets
);
skeleton1.setLegTweaksEnabled(false);
skeleton2.setLegTweaksEnabled(false);
// If target height isn't specified, auto-detect
if (targetHeight < 0f) {
targetHeight = getTargetHeight(frames);

View File

@@ -147,6 +147,7 @@ public class TrackersList extends EJBoxNoStretch {
JLabel rotQuat;
JLabel rotAdj;
JLabel temperature;
JLabel accel;
@AWTThread
public TrackerPanel(Tracker t) {
@@ -321,6 +322,10 @@ public class TrackersList extends EJBoxNoStretch {
add(new JLabel("RotAdj:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
add(rotAdj = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
row++;
add(new JLabel("Accel:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
add(accel = new JLabel("0 0 0"), c(1, row, 2, GridBagConstraints.FIRST_LINE_START));
row++;
}
if (debug && t instanceof ReferenceAdjustedTracker) {
@@ -527,6 +532,16 @@ public class TrackersList extends EJBoxNoStretch {
temperature.setText(StringUtils.prettyNumber(imu.temperature, 1) + "∘C");
}
}
if (accel != null) {
accel
.setText(
StringUtils.prettyNumber(imu.accelVector.x, 1)
+ " "
+ StringUtils.prettyNumber(imu.accelVector.y, 1)
+ " "
+ StringUtils.prettyNumber(imu.accelVector.z, 1)
);
}
}
}
}

View File

@@ -8,6 +8,7 @@ import dev.slimevr.gui.swing.EJBagNoStretch;
import dev.slimevr.gui.swing.EJBox;
import dev.slimevr.gui.swing.EJBoxNoStretch;
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigToggles;
import dev.slimevr.vr.trackers.TrackerRole;
import io.eiren.util.MacOSX;
import io.eiren.util.OperatingSystem;
@@ -42,6 +43,8 @@ public class VRServerGUI extends JFrame {
private final SkeletonList skeletonList;
private final EJBox pane;
private JButton resetButton;
private JButton floorClipButton;
private JButton skatingCorrectionButton;
private WindowConfig config;
@@ -213,6 +216,50 @@ public class VRServerGUI extends JFrame {
});
}
});
add(Box.createHorizontalStrut(10));
add(floorClipButton = new JButton("Toggle Floor Clip") {
{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
boolean[] state = server.humanPoseProcessor.getLegTweaksState();
setFloorClipEnabled(!state[0]);
}
});
}
});
setFloorClipEnabled(
server
.getConfigManager()
.getVrConfig()
.getSkeleton()
.getToggles()
.get(SkeletonConfigToggles.FLOOR_CLIP.configKey)
);
add(Box.createHorizontalStrut(10));
add(skatingCorrectionButton = new JButton("Toggle Skating Correction") {
{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
boolean[] state = server.humanPoseProcessor.getLegTweaksState();
setSkatingReductionEnabled(!state[1]);
}
});
}
});
setSkatingReductionEnabled(
server
.getConfigManager()
.getVrConfig()
.getSkeleton()
.getToggles()
.get(SkeletonConfigToggles.SKATING_CORRECTION.configKey)
);
add(Box.createHorizontalGlue());
add(new JButton("Record BVH") {
{
@@ -501,4 +548,29 @@ public class VRServerGUI extends JFrame {
private void reset() {
ButtonTimer.runTimer(resetButton, 3, "RESET", server::resetTrackers);
}
@AWTThread
private void setSkatingReductionEnabled(Boolean value) {
if (value == null)
value = false;
skatingCorrectionButton.setBackground(value ? Color.GREEN : Color.RED);
skatingCorrectionButton
.setText("Skating Correction: " + (value ? "ON" : "OFF"));
server.setSkatingReductionEnabled(value);
}
@AWTThread
private void setFloorClipEnabled(Boolean value) {
if (value == null)
value = false;
floorClipButton.setBackground(value ? Color.GREEN : Color.RED);
// update the button
floorClipButton.setText("Floor clip: " + (value ? "ON" : "OFF"));
server.setFloorClipEnabled(value);
}
}

View File

@@ -162,6 +162,12 @@ public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
return false;
}
@Override
public boolean getAcceleration(Vector3f store) {
store.set(0, 0, 0);
return false;
}
@Override
public String getName() {
return name;

View File

@@ -105,6 +105,12 @@ public final class TrackerFrame implements Tracker {
return false;
}
@Override
public boolean getAcceleration(Vector3f store) {
store.set(0, 0, 0);
return false;
}
@Override
public String getName() {
return "TrackerFrame:/" + (designation != null ? designation.designation : "null");

View File

@@ -312,7 +312,9 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
config.getToggle(SkeletonConfigToggles.EXTENDED_SPINE_MODEL),
config.getToggle(SkeletonConfigToggles.EXTENDED_PELVIS_MODEL),
config.getToggle(SkeletonConfigToggles.EXTENDED_KNEE_MODEL),
config.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD)
config.getToggle(SkeletonConfigToggles.FORCE_ARMS_FROM_HMD),
config.getToggle(SkeletonConfigToggles.SKATING_CORRECTION),
config.getToggle(SkeletonConfigToggles.FLOOR_CLIP)
);
int ratiosOffset = ModelRatios
.createModelRatios(
@@ -377,34 +379,59 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
var ratios = modelSettings.ratios();
if (toggles != null) {
if (toggles.hasExtendedSpine()) {
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_SPINE_MODEL,
toggles.extendedSpine()
);
}
if (toggles.hasExtendedPelvis()) {
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
toggles.extendedPelvis()
);
}
if (toggles.hasExtendedKnee()) {
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_KNEE_MODEL,
toggles.extendedKnee()
);
}
if (toggles.forceArmsFromHmd()) {
cfg
.setToggle(
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
toggles.forceArmsFromHmd()
);
}
// Note: toggles.has____ returns the same as toggles._____ this
// seems like a bug
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_SPINE_MODEL,
toggles.extendedSpine()
);
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
toggles.extendedPelvis()
);
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_KNEE_MODEL,
toggles.extendedKnee()
);
cfg
.setToggle(
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
toggles.forceArmsFromHmd()
);
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_SPINE_MODEL,
toggles.extendedSpine()
);
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_PELVIS_MODEL,
toggles.extendedPelvis()
);
cfg
.setToggle(
SkeletonConfigToggles.EXTENDED_KNEE_MODEL,
toggles.extendedKnee()
);
cfg
.setToggle(
SkeletonConfigToggles.FORCE_ARMS_FROM_HMD,
toggles.forceArmsFromHmd()
);
cfg
.setToggle(
SkeletonConfigToggles.FLOOR_CLIP,
toggles.floorClip()
);
cfg
.setToggle(
SkeletonConfigToggles.SKATING_CORRECTION,
toggles.skatingCorrection()
);
}
if (ratios != null) {

View File

@@ -6,6 +6,7 @@ import dev.slimevr.vr.processor.skeleton.Skeleton;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigOffsets;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigToggles;
import dev.slimevr.vr.trackers.*;
import io.eiren.util.ann.ThreadSafe;
import io.eiren.util.collections.FastList;
@@ -194,4 +195,45 @@ public class HumanPoseProcessor {
if (skeleton != null)
skeleton.resetTrackersYaw();
}
@ThreadSafe
public boolean[] getLegTweaksState() {
return skeleton.getLegTweaksState();
}
@VRServerThread
public void setLegTweaksEnabled(boolean value) {
if (skeleton != null)
skeleton.setLegTweaksEnabled(value);
}
@VRServerThread
public void setFloorClipEnabled(boolean value) {
if (skeleton != null) {
skeleton.setFloorclipEnabled(value);
server
.getConfigManager()
.getVrConfig()
.getSkeleton()
.getToggles()
.put(SkeletonConfigToggles.FLOOR_CLIP.configKey, value);
server.getConfigManager().saveConfig();
}
}
@VRServerThread
public void setSkatingCorrectionEnabled(boolean value) {
if (skeleton != null) {
skeleton.setSkatingCorrectionEnabled(value);
server
.getConfigManager()
.getVrConfig()
.getSkeleton()
.getToggles()
.put(SkeletonConfigToggles.SKATING_CORRECTION.configKey, value);
server.getConfigManager().saveConfig();
}
}
}

View File

@@ -131,6 +131,10 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
protected boolean sendAllBones = false;
// #endregion
// #region Clip Correction
protected LegTweaks legTweaks = new LegTweaks(this);
// #endregion
// #region Constructors
protected HumanSkeleton(List<? extends ComputedHumanPoseTracker> computedTrackers) {
assembleSkeleton(false);
@@ -787,6 +791,7 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
updateLocalTransforms();
updateRootTrackers();
updateComputedTrackers();
tweakLegPos();
}
// #endregion
@@ -800,6 +805,12 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
}
}
// correct any clipping that is happening to the feet trackers
private void tweakLegPos() {
// correct the foot positions
legTweaks.tweakLegs();
}
// #region Update the node transforms from the trackers
protected void updateLocalTransforms() {
// #region Pass all trackers through trackerPreUpdate for Autobone
@@ -1335,6 +1346,12 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
case FORCE_ARMS_FROM_HMD:
forceArmsFromHMD = newValue;
break;
case SKATING_CORRECTION:
legTweaks.setSkatingReductionEnabled(newValue);
break;
case FLOOR_CLIP:
legTweaks.setFloorclipEnabled(newValue);
break;
}
}
@@ -1802,6 +1819,11 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
tracker.resetFull(referenceRotation);
}
}
// tell the clip corrector to reset its floor level on the next update
// of the computed trackers
this.legTweaks.resetFloorLevel();
this.legTweaks.resetBuffer();
}
@Override
@@ -1820,5 +1842,33 @@ public class HumanSkeleton extends Skeleton implements SkeletonConfigCallback {
tracker.resetYaw(referenceRotation);
}
}
this.legTweaks.resetBuffer();
}
@Override
public boolean[] getLegTweaksState() {
boolean[] state = new boolean[2];
state[0] = this.legTweaks.getFloorclipEnabled();
state[1] = this.legTweaks.getSkatingReductionEnabled();
return state;
}
// master enable/disable of all leg tweaks (for autobone)
@Override
@VRServerThread
public void setLegTweaksEnabled(boolean value) {
this.legTweaks.setEnabled(value);
}
@Override
@VRServerThread
public void setFloorclipEnabled(boolean value) {
this.skeletonConfig.setToggle(SkeletonConfigToggles.FLOOR_CLIP, value);
}
@Override
@VRServerThread
public void setSkatingCorrectionEnabled(boolean value) {
this.skeletonConfig.setToggle(SkeletonConfigToggles.SKATING_CORRECTION, value);
}
}

View File

@@ -0,0 +1,657 @@
package dev.slimevr.vr.processor.skeleton;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
/**
* class that holds data related to the state and other variuse attributes of
* the legs such as the position of the foot, knee, and waist, after and before
* correction, the velocity of the foot and the computed state of the feet at
* that frame. mainly calculates the state of the legs per frame using these
* rules: The conditions for an unlock are as follows: 1. the foot is to far
* from its correct position 2. a velocity higher than a threashold is achived
* 3. a large acceleration is applied to the foot 4. angular velocity of the
* foot goes higher than a threashold The conditions for a lock are the opposite
* of the above but require a lower value for all of the above conditions
*/
public class LegTweakBuffer {
public static final int STATE_UNKNOWN = 0; // fall back state
public static final int LOCKED = 1;
public static final int UNLOCKED = 2;
public static final int FOOT_ACCEL = 3;
public static final int ANKLE_ACCEL = 4;
public static final float NS_CONVERT = 1000000000.0f;
private static final Vector3f placeHolderVec = new Vector3f();
private static final Quaternion placeHolderQuat = new Quaternion();
// states for the legs
private int leftLegState = STATE_UNKNOWN;
private int rightLegState = STATE_UNKNOWN;
// positions and rotations
private Vector3f leftFootPosition = placeHolderVec;
private Vector3f rightFootPosition = placeHolderVec;
private Vector3f leftKneePosition = placeHolderVec;
private Vector3f rightKneePosition = placeHolderVec;
private Vector3f waistPosition = placeHolderVec;
private Quaternion leftFootRotation = placeHolderQuat;
private Quaternion rightFootRotation = placeHolderQuat;
private Vector3f leftFootPositionCorrected = placeHolderVec;
private Vector3f rightFootPositionCorrected = placeHolderVec;
private Vector3f leftKneePositionCorrected = placeHolderVec;
private Vector3f rightKneePositionCorrected = placeHolderVec;
private Vector3f waistPositionCorrected = placeHolderVec;
// velocities
private Vector3f leftFootVelocity = placeHolderVec;
private float leftFootVelocityMagnitude = 0;
private Vector3f rightFootVelocity = placeHolderVec;
private float rightFootVelocityMagnitude = 0;
private float leftFootAngleDiff = 0;
private float rightFootAngleDiff = 0;
// acceleration
private Vector3f leftFootAcceleration = placeHolderVec;
private float leftFootAccelerationMagnitude = 0;
private Vector3f rightFootAcceleration = placeHolderVec;
private float rightFootAccelerationMagnitude = 0;
// other data
private long timeOfFrame = System.nanoTime();
private LegTweakBuffer parent = null; // frame before this one
private int frameNumber = 0; // higher number is older frame
private int detectionMode = ANKLE_ACCEL; // detection mode
private boolean accelerationAboveThresholdLeft = true;
private boolean accelerationAboveThresholdRight = true;
private float leftFloorLevel;
private float rightFloorLevel;
// hyperparameters
public static final float SKATING_DISTANCE_CUTOFF = 0.225f;
private static final float SKATING_VELOCITY_THRESHOLD = 4.25f;
private static final float SKATING_ACCELERATION_THRESHOLD = 1.15f;
private static final float SKATING_ROTVELOCITY_THRESHOLD = 4.5f;
private static final float SKATING_LOCK_ENGAGE_PERCENT = 0.85f;
private static final float SKATING_ACCELERATION_Y_USE_PERCENT = 0.25f;
private static final float FLOOR_DISTANCE_CUTOFF = 0.125f;
private static final float SIX_TRACKER_TOLLERANCE = 0.10f;
private static final float PARAM_SCALAR_MAX = 2.5f;
private static final float PARAM_SCALAR_MIN = 0.5f;
private static final float PARAM_SCALAR_MID = 1.0f;
// the point at which the scalar is at the max or min depending on accel
private static final float MAX_SCALAR_ACCEL = 0.3f;
private static final float MIN_SCALAR_ACCEL = 0.9f;
// the point at which the scalar is at it max or min in a double locked foot
// situation
private static final float MAX_SCALAR_DORMANT = 0.6f;
private static final float MIN_SCALAR_DORMANT = 2.0f;
// the point at which the scalar is at it max or min in a single locked foot
// situation
private static final float MIN_SCALAR_ACTIVE = 3.0f;
private static final float MAX_SCALAR_ACTIVE = 0.2f;
private float leftFootSensitivityVel = 1.0f;
private float rightFootSensitivityVel = 1.0f;
private float leftFootSensitivityAccel = 1.0f;
private float rightFootSensitivityAccel = 1.0f;
private static final float SKATING_CUTOFF_ENGAGE = SKATING_DISTANCE_CUTOFF
* SKATING_LOCK_ENGAGE_PERCENT;
private static final float SKATING_VELOCITY_CUTOFF_ENGAGE = SKATING_VELOCITY_THRESHOLD
* SKATING_LOCK_ENGAGE_PERCENT;
private static final float SKATING_ACCELERATION_CUTOFF_ENGAGE = SKATING_ACCELERATION_THRESHOLD
* SKATING_LOCK_ENGAGE_PERCENT;
private static final float SKATING_ROTATIONAL_VELOCITY_CUTOFF_ENGAGE = SKATING_ROTVELOCITY_THRESHOLD
* SKATING_LOCK_ENGAGE_PERCENT;
// getters and setters
public Vector3f getLeftFootPosition(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftFootPosition);
}
public void setLeftFootPosition(Vector3f leftFootPosition) {
this.leftFootPosition = leftFootPosition.clone();
}
public Vector3f getRightFootPosition(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightFootPosition);
}
public void setRightFootPosition(Vector3f rightFootPosition) {
this.rightFootPosition = rightFootPosition.clone();
}
public Vector3f getLeftKneePosition(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftKneePosition);
}
public void setLeftKneePosition(Vector3f leftKneePosition) {
this.leftKneePosition = leftKneePosition.clone();
}
public Vector3f getRightKneePosition(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightKneePosition);
}
public void setRightKneePosition(Vector3f rightKneePosition) {
this.rightKneePosition = rightKneePosition.clone();
}
public Vector3f getWaistPosition(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(waistPosition);
}
public void setWaistPosition(Vector3f waistPosition) {
this.waistPosition = waistPosition.clone();
}
public Quaternion getLeftFootRotation(Quaternion quat) {
if (quat == null)
quat = new Quaternion();
return quat.set(leftFootRotation);
}
public void setLeftFootRotation(Quaternion leftFootRotation) {
this.leftFootRotation = leftFootRotation.clone();
}
public Quaternion getRightFootRotation(Quaternion quat) {
if (quat == null)
quat = new Quaternion();
return quat.set(rightFootRotation);
}
public void setRightFootRotation(Quaternion rightFootRotation) {
this.rightFootRotation = rightFootRotation.clone();
}
public Vector3f getLeftFootPositionCorrected(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftFootPositionCorrected);
}
public void setLeftFootPositionCorrected(Vector3f leftFootPositionCorrected) {
this.leftFootPositionCorrected = leftFootPositionCorrected.clone();
}
public Vector3f getRightFootPositionCorrected(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightFootPositionCorrected);
}
public void setRightFootPositionCorrected(Vector3f rightFootPositionCorrected) {
this.rightFootPositionCorrected = rightFootPositionCorrected.clone();
}
public Vector3f getLeftKneePositionCorrected(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftKneePositionCorrected);
}
public void setLeftKneePositionCorrected(Vector3f leftKneePositionCorrected) {
this.leftKneePositionCorrected = leftKneePositionCorrected.clone();
}
public Vector3f getRightKneePositionCorrected(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightKneePositionCorrected);
}
public void setRightKneePositionCorrected(Vector3f rightKneePositionCorrected) {
this.rightKneePositionCorrected = rightKneePositionCorrected.clone();
}
public Vector3f getWaistPositionCorrected(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(waistPositionCorrected);
}
public void setWaistPositionCorrected(Vector3f waistPositionCorrected) {
this.waistPositionCorrected = waistPositionCorrected.clone();
}
public Vector3f getLeftFootVelocity(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftFootVelocity);
}
public Vector3f getRightFootVelocity(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightFootVelocity);
}
public void setLeftFloorLevel(float leftFloorLevel) {
this.leftFloorLevel = leftFloorLevel;
}
public void setRightFloorLevel(float rightFloorLevel) {
this.rightFloorLevel = rightFloorLevel;
}
public int getLeftLegState() {
return leftLegState;
}
public void setLeftLegState(int leftLegState) {
this.leftLegState = leftLegState;
}
public int getRightLegState() {
return rightLegState;
}
public void setRightLegState(int rightLegState) {
this.rightLegState = rightLegState;
}
public void setParent(LegTweakBuffer parent) {
this.parent = parent;
}
public LegTweakBuffer getParent() {
return parent;
}
public void setLeftFootAcceleration(Vector3f leftFootAcceleration) {
this.leftFootAcceleration = leftFootAcceleration.clone();
}
public void setRightFootAcceleration(Vector3f rightFootAcceleration) {
this.rightFootAcceleration = rightFootAcceleration.clone();
}
public Vector3f getLeftFootAcceleration(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(leftFootAcceleration);
}
public Vector3f getRightFootAcceleration(Vector3f vec) {
if (vec == null)
vec = new Vector3f();
return vec.set(rightFootAcceleration);
}
public float getLeftFootAccelerationMagnitude() {
return this.leftFootAcceleration.length();
}
public float getRightFootAccelerationMagnitude() {
return this.rightFootAcceleration.length();
}
public float getLeftFootAccelerationY() {
return this.leftFootAcceleration.y;
}
public float getRightFootAccelerationY() {
return this.rightFootAcceleration.y;
}
public void setDetectionMode(int mode) {
this.detectionMode = mode;
}
// calculate momvent attributes
public void calculateFootAttributes(boolean active) {
updateFrameNumber(0);
// compute attributes of the legs
computeVelocity();
computeAccelerationMagnitude();
// check if the acceleration triggers forced unlock
if (detectionMode == FOOT_ACCEL) {
computeAccelerationAboveThresholdFootTrackers();
} else {
computeAccelerationAboveThresholdAnkleTrackers();
}
// calculate the scalar for other parameters
computeScalar();
// if correction is inactive state is unknown (default to unlocked)
if (!active) {
leftLegState = UNLOCKED;
rightLegState = UNLOCKED;
} else {
computeState();
}
}
// update the frame number of all the frames
public void updateFrameNumber(int frameNumber) {
this.frameNumber = frameNumber;
if (this.frameNumber >= 10) {
this.parent = null; // once a frame is 10 frames old, it is no
// longer
// needed
}
if (parent != null) {
parent.updateFrameNumber(frameNumber + 1);
}
}
// compute the state of the legs
private void computeState() {
// based on the last state of the legs compute their state for this
// individual frame
leftLegState = checkStateLeft();
rightLegState = checkStateRight();
}
// check if a locked foot should stay locked or be released
private int checkStateLeft() {
float timeStep = 1.0f / ((timeOfFrame - parent.timeOfFrame) / NS_CONVERT);
if (parent.leftLegState == UNLOCKED) {
if (
parent.getLeftFootHorizantalDifference() > SKATING_CUTOFF_ENGAGE
|| leftFootVelocityMagnitude * timeStep
> SKATING_VELOCITY_CUTOFF_ENGAGE * leftFootSensitivityVel
|| leftFootAngleDiff * timeStep
> SKATING_ROTATIONAL_VELOCITY_CUTOFF_ENGAGE * leftFootSensitivityVel
|| leftFootPosition.y > leftFloorLevel + FLOOR_DISTANCE_CUTOFF
|| accelerationAboveThresholdLeft
) {
return UNLOCKED;
}
return LOCKED;
}
if (
parent.getLeftFootHorizantalDifference() > SKATING_DISTANCE_CUTOFF
|| leftFootVelocityMagnitude * timeStep
> SKATING_VELOCITY_THRESHOLD * leftFootSensitivityVel
|| leftFootAngleDiff * timeStep
> SKATING_ROTVELOCITY_THRESHOLD * leftFootSensitivityVel
|| leftFootPosition.y > leftFloorLevel + FLOOR_DISTANCE_CUTOFF
|| accelerationAboveThresholdLeft
) {
return UNLOCKED;
}
return LOCKED;
}
// check if a locked foot should stay locked or be released
private int checkStateRight() {
float timeStep = 1.0f / ((timeOfFrame - parent.timeOfFrame) / NS_CONVERT);
if (parent.rightLegState == UNLOCKED) {
if (
parent.getRightFootHorizantalDifference() > SKATING_CUTOFF_ENGAGE
|| rightFootVelocityMagnitude * timeStep
> SKATING_VELOCITY_CUTOFF_ENGAGE * leftFootSensitivityVel
|| rightFootAngleDiff * timeStep
> SKATING_ROTATIONAL_VELOCITY_CUTOFF_ENGAGE * leftFootSensitivityVel
|| rightFootPosition.y > rightFloorLevel + FLOOR_DISTANCE_CUTOFF
|| accelerationAboveThresholdRight
) {
return UNLOCKED;
}
return LOCKED;
}
if (
parent.getRightFootHorizantalDifference() > SKATING_DISTANCE_CUTOFF
|| rightFootVelocityMagnitude * timeStep
> SKATING_VELOCITY_THRESHOLD * rightFootSensitivityVel
|| rightFootAngleDiff * timeStep
> SKATING_ROTVELOCITY_THRESHOLD * rightFootSensitivityVel
|| rightFootPosition.y > rightFloorLevel + FLOOR_DISTANCE_CUTOFF
|| accelerationAboveThresholdRight
) {
return UNLOCKED;
}
return LOCKED;
}
// get the difference in feet position between the kinematic and corrected
// positions of the feet disregarding vertical displacment
private float getLeftFootHorizantalDifference() {
return leftFootPositionCorrected.subtract(leftFootPosition).setY(0).length();
}
// get the difference in feet position between the kinematic and corrected
// positions of the feet
private float getRightFootHorizantalDifference() {
return rightFootPositionCorrected.subtract(rightFootPosition).setY(0).length();
}
// get the angular velocity of the left foot (kinda we just want a scalar)
private float getLeftFootAngularVelocity() {
return leftFootRotation
.getRotationColumn(2)
.distance(parent.leftFootRotation.getRotationColumn(2));
}
// get the angular velocity of the right foot (kinda we just want a scalar)
private float getRightFootAngularVelocity() {
return rightFootRotation
.getRotationColumn(2)
.distance(parent.rightFootRotation.getRotationColumn(2));
}
// compute the velocity of the feet from the position in the last frames
private void computeVelocity() {
if (parent == null)
return;
leftFootVelocity = leftFootPosition.subtract(parent.leftFootPosition);
leftFootVelocityMagnitude = leftFootVelocity.length();
rightFootVelocity = rightFootPosition.subtract(parent.rightFootPosition);
rightFootVelocityMagnitude = rightFootVelocity.length();
leftFootAngleDiff = getLeftFootAngularVelocity();
rightFootAngleDiff = getRightFootAngularVelocity();
}
// get the nth parent of this frame
private LegTweakBuffer getNParent(int n) {
if (n == 0 || parent == null)
return this;
return parent.getNParent(n - 1);
}
// compute the acceleration magnitude of the feet from the acceleration
// given by the imus (exclude y)
private void computeAccelerationMagnitude() {
leftFootAccelerationMagnitude = leftFootAcceleration
.setY(leftFootAcceleration.y * SKATING_ACCELERATION_Y_USE_PERCENT)
.length();
rightFootAccelerationMagnitude = rightFootAcceleration
.setY(rightFootAcceleration.y * SKATING_ACCELERATION_Y_USE_PERCENT)
.length();
}
// for 8 trackers the data from the imus is enough to determine lock/unlock
private void computeAccelerationAboveThresholdFootTrackers() {
accelerationAboveThresholdLeft = leftFootAccelerationMagnitude
> SKATING_ACCELERATION_CUTOFF_ENGAGE * leftFootSensitivityAccel;
accelerationAboveThresholdRight = rightFootAccelerationMagnitude
> SKATING_ACCELERATION_CUTOFF_ENGAGE * rightFootSensitivityAccel;
}
// for any setup without foot trackers the data from the imus is enough to
// determine lock/unlock
private void computeAccelerationAboveThresholdAnkleTrackers() {
accelerationAboveThresholdLeft = leftFootAccelerationMagnitude
> (SKATING_ACCELERATION_THRESHOLD + SIX_TRACKER_TOLLERANCE) * leftFootSensitivityAccel;
accelerationAboveThresholdRight = rightFootAccelerationMagnitude
> (SKATING_ACCELERATION_THRESHOLD + SIX_TRACKER_TOLLERANCE) * rightFootSensitivityAccel;
}
// using the parent lock/unlock states, velocity, and acceleration,
// determine the scalars to apply to the hyperparameters when computing the
// lock state
private void computeScalar() {
// get the first set of scalars that are based on acceleration from the
// imus
float leftFootScalarAccel = getLeftFootScalarAccel();
float rightFootScalarAccel = getRightFootScalarAccel();
// get the second set of scalars that is based of of how close each foot
// is to a lock and dynamically adjusting the scalars
// (based off the assumption that if you are standing one foot is likly
// planted on the ground unless you are moving fast)
float leftFootScalarVel = getLeftFootLockLiklyHood();
float rightFootScalarVel = getRightFootLockLiklyHood();
// combine these two sets of scalars to get the final scalars
// ( -1 just makes is so if both scalars are 1.0 the final scalar is 1.0
// not 2)
leftFootSensitivityVel = leftFootScalarAccel + leftFootScalarVel - 1.0f;
rightFootSensitivityVel = rightFootScalarAccel + rightFootScalarVel - 1.0f;
leftFootSensitivityAccel = leftFootScalarVel;
rightFootSensitivityAccel = rightFootScalarVel;
}
// calculate a scalar using acceleration to apply to the non acceleration
// based hyperparameters when calculating
// lock states
private float getLeftFootScalarAccel() {
if (leftLegState == LOCKED) {
if (leftFootAccelerationMagnitude < MAX_SCALAR_ACCEL) {
return PARAM_SCALAR_MAX;
} else if (leftFootAccelerationMagnitude > MIN_SCALAR_ACCEL) {
return PARAM_SCALAR_MAX
* (leftFootAccelerationMagnitude - MIN_SCALAR_ACCEL)
/ (MAX_SCALAR_ACCEL - MIN_SCALAR_ACCEL);
}
}
return PARAM_SCALAR_MID;
}
private float getRightFootScalarAccel() {
if (rightLegState == LOCKED) {
if (rightFootAccelerationMagnitude < MAX_SCALAR_ACCEL) {
return PARAM_SCALAR_MAX;
} else if (rightFootAccelerationMagnitude > MIN_SCALAR_ACCEL) {
return PARAM_SCALAR_MAX
* (rightFootAccelerationMagnitude - MIN_SCALAR_ACCEL)
/ (MAX_SCALAR_ACCEL - MIN_SCALAR_ACCEL);
}
}
return PARAM_SCALAR_MID;
}
// calculate a scalar using the velocity of the foot trackers and the lock
// states to calculate a scalar to apply to the non acceleration based
// hyperparameters when calculating
// lock states
private float getLeftFootLockLiklyHood() {
if (leftLegState == LOCKED && rightLegState == LOCKED) {
Vector3f velocityDiff = leftFootVelocity.subtract(rightFootVelocity);
velocityDiff.setY(0.0f);
float velocityDiffMagnitude = velocityDiff.length();
if (velocityDiffMagnitude < MAX_SCALAR_DORMANT) {
return PARAM_SCALAR_MAX;
} else if (velocityDiffMagnitude > MIN_SCALAR_DORMANT) {
return PARAM_SCALAR_MAX
* (velocityDiffMagnitude - MIN_SCALAR_DORMANT)
/ (MAX_SCALAR_DORMANT - MIN_SCALAR_DORMANT);
}
}
// calculate the 'unlockedness factor' and use that to
// determine the scalar (go as low as 0.5 as as high as
// param_scalar_max)
float velocityDifAbs = Math.abs(leftFootVelocityMagnitude)
- Math.abs(rightFootVelocityMagnitude);
if (velocityDifAbs > MIN_SCALAR_ACTIVE) {
return PARAM_SCALAR_MIN;
} else if (velocityDifAbs < MAX_SCALAR_ACTIVE) {
return PARAM_SCALAR_MAX;
}
return PARAM_SCALAR_MAX
* (velocityDifAbs - MIN_SCALAR_ACTIVE)
/ (MAX_SCALAR_ACTIVE - MIN_SCALAR_ACTIVE)
- PARAM_SCALAR_MID;
}
private float getRightFootLockLiklyHood() {
if (rightLegState == LOCKED && leftLegState == LOCKED) {
Vector3f velocityDiff = rightFootVelocity.subtract(leftFootVelocity);
velocityDiff.setY(0.0f);
float velocityDiffMagnitude = velocityDiff.length();
if (velocityDiffMagnitude < MAX_SCALAR_DORMANT) {
return PARAM_SCALAR_MAX;
} else if (velocityDiffMagnitude > MIN_SCALAR_DORMANT) {
return PARAM_SCALAR_MAX
* (velocityDiffMagnitude - MIN_SCALAR_DORMANT)
/ (MAX_SCALAR_DORMANT - MIN_SCALAR_DORMANT);
}
}
// calculate the 'unlockedness factor' and use that to
// determine the scalar (go as low as 0.5 as as high as
// param_scalar_max)
float velocityDifAbs = Math.abs(rightFootVelocityMagnitude)
- Math.abs(leftFootVelocityMagnitude);
if (velocityDifAbs > MIN_SCALAR_ACTIVE) {
return PARAM_SCALAR_MIN;
} else if (velocityDifAbs < MAX_SCALAR_ACTIVE) {
return PARAM_SCALAR_MAX;
}
return PARAM_SCALAR_MAX
* (velocityDifAbs - MIN_SCALAR_ACTIVE)
/ (MAX_SCALAR_ACTIVE - MIN_SCALAR_ACTIVE)
- PARAM_SCALAR_MID;
}
}

View File

@@ -0,0 +1,887 @@
package dev.slimevr.vr.processor.skeleton;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
public class LegTweaks {
// state variables
private float floorLevel;
private float waistToFloorDist;
private float currentDisengagementOffset = 0.0f;
private boolean initialized = true;
private boolean enabled = true; // master switch
private boolean floorclipEnabled = false;
private boolean skatingCorrectionEnabled = false;
private boolean active = false;
private boolean rightLegActive = false;
private boolean leftLegActive = false;
// skeleton
private HumanSkeleton skeleton;
// leg data
private Vector3f leftFootPosition = new Vector3f();
private Vector3f rightFootPosition = new Vector3f();
private Vector3f leftKneePosition = new Vector3f();
private Vector3f rightKneePosition = new Vector3f();
private Vector3f waistPosition = new Vector3f();
private Quaternion leftFootRotation = new Quaternion();
private Quaternion rightFootRotation = new Quaternion();
private Vector3f leftWaistUpperLegOffset = new Vector3f();
private Vector3f rightWaistUpperLegOffset = new Vector3f();
private Vector3f leftFootAcceleration = new Vector3f();
private Vector3f rightFootAcceleration = new Vector3f();
private Vector3f leftLowerLegAcceleration = new Vector3f();
private Vector3f rightLowerLegAcceleration = new Vector3f();
// knee placeholder
private Vector3f leftKneePlaceholder = new Vector3f();
private Vector3f rightKneePlaceholder = new Vector3f();
private boolean kneesActive = false;
/**
* here is a explination of each parameter that may need explaining
* STANDING_CUTOFF_VERTICAL is the percentage the waist has to be below its
* position at calibration to register as the user not standing
* MAX_DISENGAGMENT_OFFSET is how much the floor will be shifted to allow an
* offset to happen smoothly DYNAMIC_DISPLACEMENT_CUTOFF is the percent of
* downwards rotation that can contribute to dynamic displacment
* MAX_DYNAMIC_DISPLACMENT is the max amount the floor will be moved up to
* account for the foot rotating downward and needing to be put higher to
* avoid clipping in the gameworld MIN_ACCEPTABLE_ERROR and
* MAX_ACCEPTABLE_ERROR Defines the disitance where CORRECTION_WEIGHT_MIN
* and CORRECTION_WEIGHT_MAX are calculating a percent of velocity to
* correct rather than using the min or max FLOOR_CALIBRATION_OFFSET is the
* amount the floor plane is shifted up. This can help the feet from
* floating slightly above the ground
*/
// hyperparameters (clip correction)
private static final float DYNAMIC_DISPLACEMENT_CUTOFF = 0.8f;
private static final float MAX_DYNAMIC_DISPLACEMENT = 0.08f;
private static final float FLOOR_CALIBRATION_OFFSET = 0.02f;
// hyperparameters (skating correction)
private static final float MIN_ACCEPTABLE_ERROR = 0.05f;
private static final float MAX_ACCEPTABLE_ERROR = LegTweakBuffer.SKATING_DISTANCE_CUTOFF;
private static final float CORRECTION_WEIGHT_MIN = 0.25f;
private static final float CORRECTION_WEIGHT_MAX = 0.70f;
// hyperparameters (floating feet correction)
private static final float FOOT_Y_CORRECTION_WEIGHT = 0.45f;
private static final float FOOT_Y_MAX_ACCELERATION = 0.40f;
private static final float FOOT_Y_DIFF_CUTOFF = 0.15f;
// hyperparameters (knee / waist correction)
private static final float KNEE_CORRECTION_WEIGHT = 0.00f;
private static final float KNEE_LATERAL_WEIGHT = 0.9f;
private static final float WAIST_PUSH_WEIGHT = 0.2f;
// hyperparameters (misc)
private static final float NEARLY_ZERO = 0.005f;
private static final float STANDING_CUTOFF_VERTICAL = 0.65f;
private static final float MAX_DISENGAGMENT_OFFSET = 0.30f;
// buffer for holding previus frames of data
private LegTweakBuffer bufferHead = new LegTweakBuffer();
private boolean bufferInvalid = true;
public LegTweaks(HumanSkeleton skeleton) {
this.skeleton = skeleton;
}
// update the offsets for the waist and upper leg
// this is used for correcting the knee tracker position
public void updateOffsets(Vector3f upperLeftLeg, Vector3f upperRightLeg, Vector3f waist) {
// update the relevant leg data
this.leftWaistUpperLegOffset = upperLeftLeg.subtract(waist);
this.rightWaistUpperLegOffset = upperRightLeg.subtract(waist);
}
public Vector3f getLeftFootPosition() {
return leftFootPosition;
}
public void setLeftFootPosition(Vector3f leftFootPosition) {
this.leftFootPosition = leftFootPosition.clone();
}
public void setLeftFootRotation(Quaternion leftFootRotation) {
this.leftFootRotation = leftFootRotation.clone();
}
public Vector3f getRightFootPosition() {
return rightFootPosition;
}
public void setRightFootPosition(Vector3f rightFootPosition) {
this.rightFootPosition = rightFootPosition.clone();
}
public void setRightFootRotation(Quaternion rightFootRotation) {
this.rightFootRotation = rightFootRotation.clone();
}
public Vector3f getLeftKneePosition() {
return leftKneePosition;
}
public void setLeftKneePosition(Vector3f leftKneePosition) {
this.leftKneePosition = leftKneePosition.clone();
}
public Vector3f getRightKneePosition() {
return rightKneePosition;
}
public void setRightKneePosition(Vector3f rightKneePosition) {
this.rightKneePosition = rightKneePosition.clone();
}
public Vector3f getWaistPosition() {
return waistPosition;
}
public void setWaistPosition(Vector3f waistPosition) {
this.waistPosition = waistPosition.clone();
}
public double getFloorLevel() {
return floorLevel;
}
public void setFloorLevel(float floorLevel) {
this.floorLevel = floorLevel;
}
public void resetFloorLevel() {
this.initialized = false;
}
public boolean getActive() {
return this.active;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setFloorclipEnabled(boolean floorclipEnabled) {
this.floorclipEnabled = floorclipEnabled;
// reset the buffer
this.bufferHead = new LegTweakBuffer();
this.bufferInvalid = true;
}
public void setSkatingReductionEnabled(boolean skatingCorrectionEnabled) {
this.skatingCorrectionEnabled = skatingCorrectionEnabled;
// reset the buffer
this.bufferHead = new LegTweakBuffer();
this.bufferInvalid = true;
}
public boolean getEnabled() {
return this.enabled;
}
public boolean getFloorclipEnabled() {
return this.floorclipEnabled;
}
public boolean getSkatingReductionEnabled() {
return this.skatingCorrectionEnabled;
}
public void resetBuffer() {
bufferInvalid = true;
}
// set the vectors in this object to the vectors in the skeleton
private void setVectors() {
// set the positions of the feet and knees to the skeletons current
// positions
if (skeleton.computedLeftKneeTracker != null || skeleton.computedRightKneeTracker != null) {
kneesActive = true;
leftKneePosition = skeleton.computedLeftKneeTracker.position;
rightKneePosition = skeleton.computedRightKneeTracker.position;
} else {
kneesActive = false;
leftKneePosition = leftKneePlaceholder;
rightKneePosition = rightKneePlaceholder;
}
if (
skeleton.computedLeftFootTracker != null
&& skeleton.computedRightFootTracker != null
&& skeleton.computedWaistTracker != null
) {
leftFootPosition = skeleton.computedLeftFootTracker.position;
rightFootPosition = skeleton.computedRightFootTracker.position;
waistPosition = skeleton.computedWaistTracker.position;
leftFootRotation = skeleton.computedLeftFootTracker.rotation;
rightFootRotation = skeleton.computedRightFootTracker.rotation;
}
// get the vector for acceleration of the feet and knees
// (if feet are not available, fallback to 6 tracker mode)
if (skeleton.leftFootTracker != null && skeleton.rightFootTracker != null) {
skeleton.leftFootTracker.getAcceleration(leftFootAcceleration);
skeleton.rightFootTracker.getAcceleration(rightFootAcceleration);
} else {
leftFootAcceleration.set(0, 0, 0);
rightFootAcceleration.set(0, 0, 0);
}
if (skeleton.leftLowerLegTracker != null && skeleton.rightLowerLegTracker != null) {
skeleton.leftLowerLegTracker.getAcceleration(leftLowerLegAcceleration);
skeleton.rightLowerLegTracker.getAcceleration(rightLowerLegAcceleration);
} else {
leftLowerLegAcceleration.set(0, 0, 0);
rightLowerLegAcceleration.set(0, 0, 0);
}
}
// updates the object with the latest data from the skeleton
private boolean preUpdate() {
// populate the vectors with the latest data
setVectors();
// if not initialized, we need to calculate some values from this frame
// to be used later (must happen immediately after reset)
if (!initialized) {
floorLevel = (leftFootPosition.y + rightFootPosition.y) / 2f + FLOOR_CALIBRATION_OFFSET;
waistToFloorDist = waistPosition.y - floorLevel;
// invalidate the buffer since the non initialized output may be
// very wrong
bufferInvalid = true;
initialized = true;
}
// if not enabled do nothing and return false
if (!enabled)
return false;
// if the user is standing start checking for a good time to enable leg
// tweaks
active = isStanding();
// if the buffer is invalid set it up
if (bufferInvalid) {
bufferHead.setLeftFootPositionCorrected(leftFootPosition);
bufferHead.setRightFootPositionCorrected(rightFootPosition);
bufferHead.setLeftKneePositionCorrected(leftKneePosition);
bufferHead.setRightKneePositionCorrected(rightKneePosition);
bufferHead.setWaistPositionCorrected(waistPosition);
bufferHead.setLeftFootPosition(leftFootPosition);
bufferHead.setRightFootPosition(rightFootPosition);
bufferHead.setLeftKneePosition(leftKneePosition);
bufferHead.setRightKneePosition(rightKneePosition);
bufferHead.setWaistPosition(waistPosition);
bufferHead.setLeftLegState(LegTweakBuffer.UNLOCKED);
bufferHead.setRightLegState(LegTweakBuffer.UNLOCKED);
bufferInvalid = false;
}
// update offsets for knee correction if the knees are not null
if (kneesActive) {
Vector3f temp1 = new Vector3f();
Vector3f temp2 = new Vector3f();
Vector3f temp3 = new Vector3f();
// get offsets from the waist to the upper legs
skeleton.leftHipNode.localTransform.getTranslation(temp1);
skeleton.rightHipNode.localTransform.getTranslation(temp2);
skeleton.trackerWaistNode.localTransform.getTranslation(temp3);
updateOffsets(temp1, temp2, temp3);
}
// update the buffer
LegTweakBuffer currentFrame = new LegTweakBuffer();
currentFrame.setLeftFootPosition(leftFootPosition);
currentFrame.setLeftFootRotation(leftFootRotation);
currentFrame.setLeftKneePosition(leftKneePosition);
currentFrame.setRightFootPosition(rightFootPosition);
currentFrame.setRightFootRotation(rightFootRotation);
currentFrame.setRightKneePosition(rightKneePosition);
currentFrame.setWaistPosition(waistPosition);
this.bufferHead
.setLeftFloorLevel(
(floorLevel + (MAX_DYNAMIC_DISPLACEMENT * getLeftFootOffset()))
- currentDisengagementOffset
);
this.bufferHead
.setRightFloorLevel(
(floorLevel + (MAX_DYNAMIC_DISPLACEMENT * getRightFootOffset()))
- currentDisengagementOffset
);
// put the acceleration vector that is applicable to the tracker
// quantity in the the buffer
// (if feet are not available, fallback to 6 tracker mode)
if (skeleton.leftFootTracker != null && skeleton.rightFootTracker != null) {
currentFrame.setLeftFootAcceleration(leftFootAcceleration);
currentFrame.setRightFootAcceleration(rightFootAcceleration);
currentFrame.setDetectionMode(LegTweakBuffer.FOOT_ACCEL);
} else if (skeleton.leftLowerLegTracker != null && skeleton.rightLowerLegTracker != null) {
currentFrame.setLeftFootAcceleration(leftLowerLegAcceleration);
currentFrame.setRightFootAcceleration(rightLowerLegAcceleration);
currentFrame.setDetectionMode(LegTweakBuffer.ANKLE_ACCEL);
}
currentFrame.setParent(bufferHead);
this.bufferHead = currentFrame;
this.bufferHead.calculateFootAttributes(active);
return true;
}
// tweak the position of the legs based on data from the last frames
public void tweakLegs() {
// update the class with the latest data from the skeleton
// if false is returned something indicated that the legs should not be
// tweaked
if (!preUpdate())
return;
// push the feet up if needed
if (floorclipEnabled)
correctClipping();
// calculate acceleration and velocity of the feet using the buffer
// (only needed if skating correction is enabled)
if (skatingCorrectionEnabled) {
correctSkating();
correctFloat();
}
// determine if either leg is in a position to activate or deactivate
// (use the buffer to get the positions before corrections)
float leftFootDif = bufferHead
.getLeftFootPosition(null)
.subtract(leftFootPosition)
.setX(0)
.setZ(0)
.length();
float rightFootDif = bufferHead
.getRightFootPosition(null)
.subtract(rightFootPosition)
.setX(0)
.setZ(0)
.length();
if (!active && leftFootDif < NEARLY_ZERO) {
leftLegActive = false;
} else if (active && leftFootDif < NEARLY_ZERO) {
leftLegActive = true;
}
if (!active && rightFootDif < NEARLY_ZERO) {
rightLegActive = false;
} else if (active && rightFootDif < NEARLY_ZERO) {
rightLegActive = true;
}
// restore the y positions of inactive legs
if (!leftLegActive) {
leftFootPosition.y = bufferHead.getLeftFootPosition(null).y;
leftKneePosition.y = bufferHead.getLeftKneePosition(null).y;
}
if (!rightLegActive) {
rightFootPosition.y = bufferHead.getRightFootPosition(null).y;
rightKneePosition.y = bufferHead.getRightKneePosition(null).y;
}
// calculate the correction for the knees
if (kneesActive && initialized)
solveLowerBody();
// populate the corrected data into the current frame
this.bufferHead.setLeftFootPositionCorrected(leftFootPosition);
this.bufferHead.setRightFootPositionCorrected(rightFootPosition);
this.bufferHead.setLeftKneePositionCorrected(leftKneePosition);
this.bufferHead.setRightKneePositionCorrected(rightKneePosition);
this.bufferHead.setWaistPositionCorrected(waistPosition);
}
// returns true if the foot is clipped and false if it is not
public boolean isClipped(float leftOffset, float rightOffset) {
return (leftFootPosition.y < floorLevel + leftOffset
|| rightFootPosition.y < floorLevel + rightOffset);
}
// corrects the foot position to be above the floor level that is calculated
// on calibration
private void correctClipping() {
// calculate how angled down the feet are as a scalar value between 0
// and 1 (0 = flat, 1 = max angle)
float leftOffset = getLeftFootOffset();
float rightOffset = getRightFootOffset();
float avgOffset = 0;
// if there is no clipping, or clipping is not enabled, return false
if (!isClipped(leftOffset, rightOffset) || !enabled)
return;
// move the feet to their new positions
if (
leftFootPosition.y
< (floorLevel + (MAX_DYNAMIC_DISPLACEMENT * leftOffset))
- currentDisengagementOffset
) {
float displacement = Math
.abs(
floorLevel
+ (MAX_DYNAMIC_DISPLACEMENT * leftOffset)
- leftFootPosition.y
- currentDisengagementOffset
);
leftFootPosition.y += displacement;
leftKneePosition.y += displacement;
avgOffset += displacement;
}
if (
rightFootPosition.y
< (floorLevel + (MAX_DYNAMIC_DISPLACEMENT * rightOffset))
- currentDisengagementOffset
) {
float displacement = Math
.abs(
floorLevel
+ (MAX_DYNAMIC_DISPLACEMENT * rightOffset)
- rightFootPosition.y
- currentDisengagementOffset
);
rightFootPosition.y += displacement;
rightKneePosition.y += displacement;
avgOffset += displacement;
}
waistPosition.y += (avgOffset / 2) * WAIST_PUSH_WEIGHT;
}
// based on the data from the last frame compute a new position that reduces
// ice skating
private void correctSkating() {
// for either foot that is locked get its position (x and z only we let
// y move freely) and set it to be there
if (bufferHead.getLeftLegState() == LegTweakBuffer.LOCKED) {
leftFootPosition.x = bufferHead
.getParent()
.getLeftFootPositionCorrected(null).x;
leftFootPosition.z = bufferHead
.getParent()
.getLeftFootPositionCorrected(null).z;
}
if (bufferHead.getRightLegState() == LegTweakBuffer.LOCKED) {
rightFootPosition.x = bufferHead
.getParent()
.getRightFootPositionCorrected(null).x;
rightFootPosition.z = bufferHead
.getParent()
.getRightFootPositionCorrected(null).z;
}
// for either foot that is unlocked get its last position and calculate
// its position for this frame. the amount of displacement is based on
// the distance between the last position, the current position, and
// the hyperparameters
correctUnlockedLeftFootTracker();
correctUnlockedRightFootTracker();
}
private void correctUnlockedLeftFootTracker() {
if (bufferHead.getLeftLegState() == LegTweakBuffer.UNLOCKED) {
Vector3f leftFootDif = leftFootPosition
.subtract(bufferHead.getParent().getLeftFootPositionCorrected(null))
.setY(0);
if (leftFootDif.length() > NEARLY_ZERO) {
float leftY = leftFootPosition.y;
Vector3f temp = bufferHead.getParent().getLeftFootPositionCorrected(null);
Vector3f velocity = bufferHead.getLeftFootVelocity(null);
// first add the difference from the last frame to this frame
temp = temp
.subtract(
bufferHead
.getParent()
.getLeftFootPosition(null)
.subtract(bufferHead.getLeftFootPosition(null))
);
leftFootPosition.y = leftY;
leftFootPosition.x = temp.x;
leftFootPosition.z = temp.z;
// if velocity and dif are pointing in the same direction,
// add a small amount of velocity to the dif
// else subtract a small amount of velocity from the dif
// calculate the correction weight
float weight = calculateCorrectionWeight(
leftFootPosition,
bufferHead.getParent().getLeftFootPositionCorrected(null)
);
if (velocity.x * leftFootDif.x > 0) {
leftFootPosition.x += velocity.x * weight;
} else {
leftFootPosition.x -= velocity.x * weight;
}
if (velocity.z * leftFootDif.z > 0) {
leftFootPosition.z += velocity.z * weight;
} else {
leftFootPosition.z -= velocity.z * weight;
}
// if the foot overshot the target, move it back to the target
if (
checkOverShoot(
this.bufferHead.getLeftFootPosition(null).x,
this.bufferHead.getParent().getLeftFootPositionCorrected(null).x,
leftFootPosition.x
)
) {
leftFootPosition.x = bufferHead.getLeftFootPosition(null).x;
}
if (
checkOverShoot(
this.bufferHead.getLeftFootPosition(null).z,
this.bufferHead.getParent().getLeftFootPositionCorrected(null).z,
leftFootPosition.z
)
) {
leftFootPosition.z = bufferHead.getLeftFootPosition(null).z;
}
}
}
}
private void correctUnlockedRightFootTracker() {
if (bufferHead.getRightLegState() == LegTweakBuffer.UNLOCKED) {
Vector3f rightFootDif = rightFootPosition
.subtract(bufferHead.getParent().getRightFootPositionCorrected(null))
.setY(0);
if (rightFootDif.length() > NEARLY_ZERO) {
float rightY = rightFootPosition.y;
Vector3f temp = bufferHead
.getParent()
.getRightFootPositionCorrected(null);
Vector3f velocity = bufferHead.getRightFootVelocity(null);
// first add the difference from the last frame to this frame
temp = temp
.subtract(
bufferHead
.getParent()
.getRightFootPosition(null)
.subtract(bufferHead.getRightFootPosition(null))
);
rightFootPosition.y = rightY;
rightFootPosition.x = temp.x;
rightFootPosition.z = temp.z;
// if velocity and dif are pointing in the same direction,
// add a small amount of velocity to the dif
// else subtract a small amount of velocity from the dif
// calculate the correction weight
float weight = calculateCorrectionWeight(
rightFootPosition,
bufferHead.getParent().getRightFootPositionCorrected(null)
);
if (velocity.x * rightFootDif.x > 0) {
rightFootPosition.x += velocity.x * weight;
} else {
rightFootPosition.x -= velocity.x * weight;
}
if (velocity.z * rightFootDif.z > 0) {
rightFootPosition.z += velocity.z * weight;
} else {
rightFootPosition.z -= velocity.z * weight;
}
// if the foot overshot the target, move it back to the target
if (
checkOverShoot(
this.bufferHead.getRightFootPosition(null).x,
this.bufferHead.getParent().getRightFootPositionCorrected(null).x,
rightFootPosition.x
)
) {
rightFootPosition.x = bufferHead.getRightFootPosition(null).x;
}
if (
checkOverShoot(
this.bufferHead.getRightFootPosition(null).z,
this.bufferHead.getParent().getRightFootPositionCorrected(null).z,
rightFootPosition.z
)
) {
rightFootPosition.z = bufferHead.getRightFootPosition(null).z;
}
}
}
}
// check if the foot is begining to float over the floor when it should not
// be if it is move it back down to the floor
// note: very small corrections are all that is allowed to prevent momemnt
// that looks unrealistic
private void correctFloat() {
boolean correctLeft = true;
boolean correctRight = true;
if (bufferHead.getLeftLegState() == LegTweakBuffer.LOCKED) {
float lastPositionY = bufferHead
.getParent()
.getLeftFootPositionCorrected(null).y;
if (
Math.abs(leftFootPosition.y - lastPositionY) < FOOT_Y_DIFF_CUTOFF
&& leftFootPosition.y > lastPositionY
&& bufferHead.getLeftFootAccelerationY() < FOOT_Y_MAX_ACCELERATION
) {
leftFootPosition.y = lastPositionY;
correctLeft = false;
}
}
if (bufferHead.getRightLegState() == LegTweakBuffer.LOCKED) {
float lastPositionY = bufferHead
.getParent()
.getRightFootPositionCorrected(null).y;
if (
Math.abs(rightFootPosition.y - lastPositionY) < FOOT_Y_DIFF_CUTOFF
&& rightFootPosition.y > lastPositionY
&& bufferHead.getRightFootAccelerationY() < FOOT_Y_MAX_ACCELERATION
) {
rightFootPosition.y = lastPositionY;
correctRight = false;
}
}
// using the velocity correct the position
if (correctLeft)
correctLeftFootTrackerY();
if (correctRight)
correctRightFootTrackerY();
}
private void correctLeftFootTrackerY() {
Vector3f temp;
Vector3f leftFootDif = leftFootPosition
.subtract(bufferHead.getParent().getLeftFootPositionCorrected(null));
if (Math.abs(leftFootDif.y) > NEARLY_ZERO) {
temp = bufferHead
.getParent()
.getLeftFootPositionCorrected(null)
.subtract(
bufferHead
.getParent()
.getLeftFootPosition(null)
.subtract(bufferHead.getLeftFootPosition(null))
);
float leftFloor = (floorLevel + (MAX_DYNAMIC_DISPLACEMENT * getLeftFootOffset()))
- currentDisengagementOffset;
leftFootPosition.y = temp.y < leftFloor ? leftFloor : temp.y;
Vector3f velocity = bufferHead.getLeftFootVelocity(null);
if (velocity.y * leftFootDif.y > 0) {
leftFootPosition.y += velocity.y * FOOT_Y_CORRECTION_WEIGHT;
} else {
leftFootPosition.y -= velocity.y * FOOT_Y_CORRECTION_WEIGHT;
}
// check for overshoot and correct if necessary
if (
checkOverShoot(
this.bufferHead.getLeftFootPosition(null).y,
this.bufferHead.getParent().getLeftFootPositionCorrected(null).y,
leftFootPosition.y
)
) {
leftFootPosition.y = bufferHead.getLeftFootPosition(null).y;
}
}
}
private void correctRightFootTrackerY() {
Vector3f temp;
Vector3f rightFootDif = rightFootPosition
.subtract(bufferHead.getParent().getRightFootPositionCorrected(null));
if (Math.abs(rightFootDif.y) > NEARLY_ZERO) {
temp = bufferHead
.getParent()
.getRightFootPositionCorrected(null)
.subtract(
bufferHead
.getParent()
.getRightFootPosition(null)
.subtract(bufferHead.getRightFootPosition(null))
);
float rightFloor = (floorLevel + (MAX_DYNAMIC_DISPLACEMENT * getRightFootOffset()))
- currentDisengagementOffset;
rightFootPosition.y = temp.y < rightFloor ? rightFloor : temp.y;
Vector3f velocity = bufferHead.getRightFootVelocity(null);
if (velocity.y * rightFootDif.y > 0) {
rightFootPosition.y += velocity.y * FOOT_Y_CORRECTION_WEIGHT;
} else {
rightFootPosition.y -= velocity.y * FOOT_Y_CORRECTION_WEIGHT;
}
// check for overshoot and correct if necessary
if (
checkOverShoot(
this.bufferHead.getRightFootPosition(null).y,
this.bufferHead.getParent().getRightFootPositionCorrected(null).y,
rightFootPosition.y
)
) {
rightFootPosition.y = bufferHead.getRightFootPosition(null).y;
}
}
}
// returns true if it is likely the user is standing
public boolean isStanding() {
// if the waist is below the vertical cutoff, user is not standing
float cutoff = floorLevel
+ waistToFloorDist
- (waistToFloorDist * STANDING_CUTOFF_VERTICAL);
if (waistPosition.y < cutoff) {
currentDisengagementOffset = (1 - waistPosition.y / cutoff)
* MAX_DISENGAGMENT_OFFSET;
return false;
}
currentDisengagementOffset = 0f;
return true;
}
// move the knees in to a position that is closer to the truth
private void solveLowerBody() {
// calculate the left and right waist nodes in standing space
Vector3f leftWaist = waistPosition;// .add(leftWaistUpperLegOffset);
Vector3f rightWaist = waistPosition;// .add(rightWaistUpperLegOffset);
Vector3f tempLeft;
Vector3f tempRight;
// before moveing the knees back closer to the waist nodes offset them
// the same amount the foot trackers where offset
float leftXDif = leftFootPosition.x - bufferHead.getLeftFootPosition(null).x;
float rightXDif = rightFootPosition.x - bufferHead.getRightFootPosition(null).x;
float leftZDif = leftFootPosition.z - bufferHead.getLeftFootPosition(null).z;
float rightZDif = rightFootPosition.z - bufferHead.getRightFootPosition(null).z;
leftKneePosition.x += leftXDif * KNEE_LATERAL_WEIGHT;
leftKneePosition.z += leftZDif * KNEE_LATERAL_WEIGHT;
rightKneePosition.x += rightXDif * KNEE_LATERAL_WEIGHT;
rightKneePosition.z += rightZDif * KNEE_LATERAL_WEIGHT;
// calculate the bone distances
float leftKneeWaist = bufferHead.getLeftKneePosition(null).distance(leftWaist);
float rightKneeWaist = bufferHead.getRightKneePosition(null).distance(rightWaist);
float leftKneeWaistNew = leftKneePosition.distance(leftWaist);
float rightKneeWaistNew = rightKneePosition.distance(rightWaist);
float leftKneeOffset = leftKneeWaistNew - leftKneeWaist;
float rightKneeOffset = rightKneeWaistNew - rightKneeWaist;
// get the vector from the waist to the knee
Vector3f leftKneeVector = leftKneePosition
.subtract(leftWaist)
.normalize()
.mult(leftKneeOffset * KNEE_CORRECTION_WEIGHT);
Vector3f rightKneeVector = rightKneePosition
.subtract(rightWaist)
.normalize()
.mult(rightKneeOffset * KNEE_CORRECTION_WEIGHT);
// correct the knees
tempLeft = leftKneePosition.subtract(leftKneeVector);
tempRight = rightKneePosition.subtract(rightKneeVector);
leftKneePosition.set(tempLeft);
rightKneePosition.set(tempRight);
}
private float getLeftFootOffset() {
float offset = computeUnitVector(this.leftFootRotation).y;
return clamp(0, DYNAMIC_DISPLACEMENT_CUTOFF, offset);
}
private float getRightFootOffset() {
float offset = computeUnitVector(this.rightFootRotation).y;
return clamp(0, DYNAMIC_DISPLACEMENT_CUTOFF, offset);
}
// calculate the weight of foot correction
private float calculateCorrectionWeight(
Vector3f foot,
Vector3f footCorrected
) {
Vector3f footDif = foot.subtract(footCorrected).setY(0);
if (footDif.length() < MIN_ACCEPTABLE_ERROR) {
return CORRECTION_WEIGHT_MIN;
} else if (footDif.length() > MAX_ACCEPTABLE_ERROR) {
return CORRECTION_WEIGHT_MAX;
}
return CORRECTION_WEIGHT_MIN
+ (footDif.length() - MIN_ACCEPTABLE_ERROR)
/ (MAX_ACCEPTABLE_ERROR - MIN_ACCEPTABLE_ERROR)
* (CORRECTION_WEIGHT_MAX - CORRECTION_WEIGHT_MIN);
}
// check if the difference between two floats flipped after correction
private boolean checkOverShoot(float trueVal, float valBefore, float valAfter) {
return (trueVal - valBefore) * (trueVal - valAfter) < 0;
}
// get the unit vector of the given rotation
private Vector3f computeUnitVector(Quaternion quaternion) {
return quaternion.getRotationColumn(2).normalize();
}
// clamp a float between two values
private float clamp(float min, float max, float val) {
return Math.min(max, Math.max(min, val));
}
}

View File

@@ -39,4 +39,16 @@ public abstract class Skeleton {
@VRServerThread
public abstract void resetTrackersYaw();
@VRServerThread
public abstract boolean[] getLegTweaksState();
@VRServerThread
public abstract void setLegTweaksEnabled(boolean value);
@VRServerThread
public abstract void setFloorclipEnabled(boolean value);
@VRServerThread
public abstract void setSkatingCorrectionEnabled(boolean value);
}

View File

@@ -9,7 +9,9 @@ public enum SkeletonConfigToggles {
EXTENDED_SPINE_MODEL(1, "Extended spine model", "extendedSpine", true),
EXTENDED_PELVIS_MODEL(2, "Extended pelvis model", "extendedPelvis", true),
EXTENDED_KNEE_MODEL(3, "Extended knee model", "extendedKnee", true),
FORCE_ARMS_FROM_HMD(4, "Force arms from HMD", "forceArmsFromHMD", true),;
FORCE_ARMS_FROM_HMD(4, "Force arms from HMD", "forceArmsFromHMD", true),
FLOOR_CLIP(5, "Floor clip", "floorClip", true),
SKATING_CORRECTION(6, "Skating correction", "skatingCorrection", true),;
public static final SkeletonConfigToggles[] values = values();
private static final Map<String, SkeletonConfigToggles> byStringVal = new HashMap<>();

View File

@@ -74,6 +74,12 @@ public class ComputedTracker implements Tracker, TrackerWithTPS {
return true;
}
@Override
public boolean getAcceleration(Vector3f store) {
store.set(0, 0, 0);
return false;
}
@Override
public TrackerStatus getStatus() {
return status;

View File

@@ -18,7 +18,7 @@ public class IMUTracker
public static final float MAX_MAG_CORRECTION_ACCURACY = 5 * FastMath.RAD_TO_DEG;
// public final Vector3f gyroVector = new Vector3f();
// public final Vector3f accelVector = new Vector3f();
public final Vector3f accelVector = new Vector3f();
public final Vector3f magVector = new Vector3f();
public final Quaternion rotQuaternion = new Quaternion();
public final Quaternion rotMagQuaternion = new Quaternion();
@@ -170,6 +170,12 @@ public class IMUTracker
return false;
}
@Override
public boolean getAcceleration(Vector3f store) {
store.set(accelVector);
return true;
}
@Override
public boolean getRotation(Quaternion store) {
if (movementFilterTickCount > 0 && movementFilterAmount != 1 && previousRots.size() > 0) {

View File

@@ -108,6 +108,11 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
return true;
}
@Override
public boolean getAcceleration(Vector3f store) {
return tracker.getAcceleration(store);
}
@Override
public boolean getPosition(Vector3f store) {
return tracker.getPosition(store);

View File

@@ -20,6 +20,8 @@ public interface Tracker {
boolean getRotation(Quaternion store);
boolean getAcceleration(Vector3f store);
String getName();
TrackerStatus getStatus();

View File

@@ -12,6 +12,7 @@ import dev.slimevr.vr.trackers.TrackerStatus;
import io.eiren.util.Util;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
import org.apache.commons.lang3.ArrayUtils;
import java.io.IOException;
@@ -425,8 +426,22 @@ public class TrackersUDPServer extends Thread {
break;
tracker.magnetometerAccuracy = magAccuracy.accuracyInfo;
break;
case UDPProtocolParser.PACKET_ACCEL:
if (connection == null)
break;
UDPPacket4Acceleration accelPacket = (UDPPacket4Acceleration) packet;
tracker = connection.getTracker(accelPacket.getSensorId());
if (tracker == null)
break;
Vector3f acceleration = tracker.rotQuaternion.mult(accelPacket.acceleration);
tracker.accelVector.set(acceleration);
break;
case 2: // PACKET_GYRO
case 4: // PACKET_ACCEL
case 5: // PACKET_MAG
case 9: // PACKET_RAW_MAGENTOMETER
break; // None of these packets are used by SlimeVR trackers and

View File

@@ -0,0 +1,44 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import com.jme3.math.Vector3f;
public class UDPPacket4Acceleration extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public Vector3f acceleration = new Vector3f();
public UDPPacket4Acceleration() {
}
@Override
public int getPacketId() {
return 4;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
acceleration.set(buf.getFloat(), buf.getFloat(), buf.getFloat());
try {
sensorId = buf.get() & 0xFF;
} catch (BufferUnderflowException e) {
// for owo track app
sensorId = 0;
}
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -11,7 +11,7 @@ public class UDPProtocolParser {
public static final int PACKET_ROTATION = 1; // Deprecated
// public static final int PACKET_GYRO = 2; // Deprecated
public static final int PACKET_HANDSHAKE = 3;
// public static final int PACKET_ACCEL = 4; // Not parsed by server
public static final int PACKET_ACCEL = 4;
// public static final int PACKET_MAG = 5; // Deprecated
// public static final int PACKET_RAW_CALIBRATION_DATA = 6; // Not parsed by
// server
@@ -105,6 +105,8 @@ public class UDPProtocolParser {
return new UDPPacket3Handshake();
case PACKET_PING_PONG:
return new UDPPacket10PingPong();
case PACKET_ACCEL:
return new UDPPacket4Acceleration();
case PACKET_SERIAL:
return new UDPPacket11Serial();
case PACKET_BATTERY_LEVEL: