From af4f9a96bf8cd4cb9bb518f9f97e0b674a4cd6e4 Mon Sep 17 00:00:00 2001 From: Erimel Date: Wed, 31 Jul 2024 23:05:34 -0400 Subject: [PATCH] Computed head reset (#1057) --- gui/public/i18n/en/translation.ftl | 3 + .../settings/pages/GeneralSettings.tsx | 65 +++++++++++++------ .../src/main/java/dev/slimevr/VRServer.kt | 3 +- .../java/dev/slimevr/config/ResetsConfig.kt | 7 +- .../main/java/dev/slimevr/config/VRConfig.kt | 2 +- .../rpc/settings/RPCSettingsBuilder.java | 6 +- .../rpc/settings/RPCSettingsHandler.kt | 1 + .../tracking/processor/HumanPoseManager.kt | 10 --- .../processor/skeleton/HumanSkeleton.kt | 44 ++++++------- .../dev/slimevr/tracking/trackers/Tracker.kt | 4 +- .../tracking/trackers/TrackerResetsHandler.kt | 63 +++++++++++++----- .../dev/slimevr/unit/MountingResetTests.kt | 2 - .../slimevr/unit/ReferenceAdjustmentsTests.kt | 3 - .../slimevr/desktop/platform/SteamVRBridge.kt | 17 +++-- solarxr-protocol | 2 +- 15 files changed, 140 insertions(+), 92 deletions(-) diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index a13ca40e4..6066bc1b0 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -355,6 +355,9 @@ settings-general-fk_settings-leg_fk-reset_mounting_feet = Feet Mounting Reset settings-general-fk_settings-arm_fk = Arm tracking settings-general-fk_settings-arm_fk-description = Force arms to be tracked from the headset (HMD) even if positional hand data is available. settings-general-fk_settings-arm_fk-force_arms = Force arms from HMD +settings-general-fk_settings-reset_settings = Reset settings +settings-general-fk_settings-reset_settings-reset_hmd_pitch-description = Reset the HMD's pitch (vertical rotation) upon doing a full reset. Useful if wearing an HMD on the forehead for VTubing or mocap. Do not enable for VR. +settings-general-fk_settings-reset_settings-reset_hmd_pitch = Reset HMD pitch settings-general-fk_settings-arm_fk-reset_mode-description = Change which arm pose is expected for mounting reset. settings-general-fk_settings-arm_fk-back = Back settings-general-fk_settings-arm_fk-back-description = The default mode, with the upper arms going back and lower arms going forward. diff --git a/gui/src/components/settings/pages/GeneralSettings.tsx b/gui/src/components/settings/pages/GeneralSettings.tsx index ebc9c3057..a4136e715 100644 --- a/gui/src/components/settings/pages/GeneralSettings.tsx +++ b/gui/src/components/settings/pages/GeneralSettings.tsx @@ -96,6 +96,7 @@ interface SettingsForm { armsMountingResetMode: number; yawResetSmoothTime: number; saveMountingReset: boolean; + resetHmdPitch: boolean; }; } @@ -158,6 +159,7 @@ const defaultValues: SettingsForm = { armsMountingResetMode: 0, yawResetSmoothTime: 0.0, saveMountingReset: false, + resetHmdPitch: false, }, }; @@ -294,6 +296,7 @@ export function GeneralSettings() { values.resetsSettings.yawResetSmoothTime; resetsSettings.saveMountingReset = values.resetsSettings.saveMountingReset; + resetsSettings.resetHmdPitch = values.resetsSettings.resetHmdPitch; settings.resetsSettings = resetsSettings; } @@ -859,24 +862,6 @@ export function GeneralSettings() { )} /> -
- - {l10n.getString( - 'settings-general-fk_settings-leg_fk-reset_mounting_feet-description' - )} - -
-
- -
@@ -900,12 +885,53 @@ export function GeneralSettings() { />
+
+ + {l10n.getString('settings-general-fk_settings-reset_settings')} + +
+
+ + {l10n.getString( + 'settings-general-fk_settings-reset_settings-reset_hmd_pitch-description' + )} + +
+
+ +
+
+ + {l10n.getString( + 'settings-general-fk_settings-leg_fk-reset_mounting_feet-description' + )} + +
+
+ +
{l10n.getString( 'settings-general-fk_settings-arm_fk-reset_mode-description' )} -
+
+ {config?.debug && ( <>
diff --git a/server/core/src/main/java/dev/slimevr/VRServer.kt b/server/core/src/main/java/dev/slimevr/VRServer.kt index 6ae919fe8..4bd35390e 100644 --- a/server/core/src/main/java/dev/slimevr/VRServer.kt +++ b/server/core/src/main/java/dev/slimevr/VRServer.kt @@ -102,6 +102,8 @@ class VRServer @JvmOverloads constructor( init { // UwU + instance = this + configManager = ConfigManager(configPath) configManager.loadConfig() deviceManager = DeviceManager(this) @@ -161,7 +163,6 @@ class VRServer @JvmOverloads constructor( for (tracker in computedTrackers) { registerTracker(tracker) } - instance = this } fun hasBridge(bridgeClass: Class): Boolean { diff --git a/server/core/src/main/java/dev/slimevr/config/ResetsConfig.kt b/server/core/src/main/java/dev/slimevr/config/ResetsConfig.kt index b599dc0ae..0945ea7da 100644 --- a/server/core/src/main/java/dev/slimevr/config/ResetsConfig.kt +++ b/server/core/src/main/java/dev/slimevr/config/ResetsConfig.kt @@ -43,11 +43,12 @@ class ResetsConfig { // Save automatic mounting reset calibration var saveMountingReset = false + // Reset the HMD's pitch upon full reset + var resetHmdPitch = false + fun updateTrackersResetsSettings() { for (t in VRServer.instance.allTrackers) { - if (t.needsReset) { - t.resetsHandler.readResetConfig(this) - } + t.resetsHandler.readResetConfig(this) } } } diff --git a/server/core/src/main/java/dev/slimevr/config/VRConfig.kt b/server/core/src/main/java/dev/slimevr/config/VRConfig.kt index 9ce4e334c..873ce37e6 100644 --- a/server/core/src/main/java/dev/slimevr/config/VRConfig.kt +++ b/server/core/src/main/java/dev/slimevr/config/VRConfig.kt @@ -101,8 +101,8 @@ class VRConfig { val config = getTracker(tracker) tracker.readConfig(config) if (tracker.isImu()) tracker.resetsHandler.readDriftCompensationConfig(driftCompensation) + tracker.resetsHandler.readResetConfig(resetsConfig) if (tracker.needsReset) { - tracker.resetsHandler.readResetConfig(resetsConfig) tracker.saveMountingResetOrientation(config) } if (tracker.allowFiltering) { diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java index b2d5fe8be..c1adf80bc 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilder.java @@ -189,7 +189,8 @@ public class RPCSettingsBuilder { humanPoseManager.getToggle(SkeletonConfigToggles.VIVE_EMULATION), humanPoseManager.getToggle(SkeletonConfigToggles.TOE_SNAP), humanPoseManager.getToggle(SkeletonConfigToggles.FOOT_PLANT), - humanPoseManager.getToggle(SkeletonConfigToggles.SELF_LOCALIZATION) + humanPoseManager.getToggle(SkeletonConfigToggles.SELF_LOCALIZATION), + false ); int ratiosOffset = ModelRatios .createModelRatios( @@ -342,7 +343,8 @@ public class RPCSettingsBuilder { resetsConfig.getResetMountingFeet(), resetsConfig.getMode().getId(), resetsConfig.getYawResetSmoothTime(), - resetsConfig.getSaveMountingReset() + resetsConfig.getSaveMountingReset(), + resetsConfig.getResetHmdPitch() ); } diff --git a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.kt b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.kt index 4d7b18ec8..b01db3894 100644 --- a/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.kt +++ b/server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsHandler.kt @@ -337,6 +337,7 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) { resetsConfig.resetMountingFeet = req.resetsSettings().resetMountingFeet() resetsConfig.saveMountingReset = req.resetsSettings().saveMountingReset() resetsConfig.yawResetSmoothTime = req.resetsSettings().yawResetSmoothTime() + resetsConfig.resetHmdPitch = req.resetsSettings().resetHmdPitch() resetsConfig.updateTrackersResetsSettings() } diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/HumanPoseManager.kt b/server/core/src/main/java/dev/slimevr/tracking/processor/HumanPoseManager.kt index c23f79413..ba57581ff 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/HumanPoseManager.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/HumanPoseManager.kt @@ -340,16 +340,6 @@ class HumanPoseManager(val server: VRServer?) { @ThreadSafe fun getComputedTracker(trackerRole: TrackerRole): Tracker = skeleton.getComputedTracker(trackerRole) - /** - * Returns all trackers if VRServer is non-null. Else, returns the - * skeleton's assigned trackers. - * - * @return a list of trackers to use for reset. - */ - val trackersToReset: List - get() = - server?.allTrackers ?: skeleton.localTrackers - /** * @return the head bone, which is the root of the skeleton */ diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt index 1c30d108f..55ebed9e2 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/skeleton/HumanSkeleton.kt @@ -784,8 +784,8 @@ class HumanSkeleton( } /** - * Rotates the first Quaternion to match its yaw and roll to the rotation of - * the average of the second and third quaternions. + * Rotates the third Quaternion to match its yaw and roll to the rotation of + * the average of the first and second quaternions. * * @param leftKnee the first Quaternion * @param rightKnee the second Quaternion @@ -1033,7 +1033,8 @@ class HumanSkeleton( */ val isTrackingRightArmFromController: Boolean get() = rightHandTracker != null && rightHandTracker!!.hasPosition && !forceArmsFromHMD - val localTrackers: List + + val trackersToReset: List get() = listOf( neckTracker, chestTracker, @@ -1056,19 +1057,16 @@ class HumanSkeleton( ) fun resetTrackersFull(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset - - // Resets all axis of the trackers with the HMD as reference. var referenceRotation = IDENTITY headTracker?.let { - if (it.needsReset) { - it.resetsHandler.resetFull(referenceRotation) - } else { - referenceRotation = it.getRotation() - } + // Always reset the head (ifs in resetsHandler) + it.resetsHandler.resetFull(referenceRotation) + referenceRotation = it.getRotation() } + // Resets all axes of the trackers with the HMD as reference. for (tracker in trackersToReset) { - if (tracker != null && tracker.needsReset) { + // Only reset if tracker needsReset + if (tracker != null && (tracker.needsReset || tracker.isHmd)) { tracker.resetsHandler.resetFull(referenceRotation) } } @@ -1085,18 +1083,17 @@ class HumanSkeleton( @VRServerThread fun resetTrackersYaw(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset - // Resets the yaw of the trackers with the head as reference. var referenceRotation = IDENTITY headTracker?.let { - if (it.needsReset) { + // Only reset if head needsReset and isn't computed + if (it.needsReset && !it.isComputed) { it.resetsHandler.resetYaw(referenceRotation) - } else { - referenceRotation = it.getRotation() } + referenceRotation = it.getRotation() } for (tracker in trackersToReset) { + // Only reset if tracker needsReset if (tracker != null && tracker.needsReset) { tracker.resetsHandler.resetYaw(referenceRotation) } @@ -1107,19 +1104,17 @@ class HumanSkeleton( @VRServerThread fun resetTrackersMounting(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset - - // Resets the mounting orientation of the trackers with the HMD as - // reference. + // Resets the mounting orientation of the trackers with the HMD as reference. var referenceRotation = IDENTITY headTracker?.let { - if (it.needsMounting) { + // Only reset if head needsMounting or is computed but not HMD + if (it.needsMounting || (it.isComputed && !it.isHmd)) { it.resetsHandler.resetMounting(referenceRotation) - } else { - referenceRotation = it.getRotation() } + referenceRotation = it.getRotation() } for (tracker in trackersToReset) { + // Only reset if tracker needsMounting if (tracker != null && tracker.needsMounting) { tracker.resetsHandler.resetMounting(referenceRotation) } @@ -1131,7 +1126,6 @@ class HumanSkeleton( @VRServerThread fun clearTrackersMounting(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset headTracker?.let { if (it.needsMounting) it.resetsHandler.clearMounting() } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt index f015c990a..564dd4b85 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/Tracker.kt @@ -323,7 +323,7 @@ class Tracker @JvmOverloads constructor( _rotation } - if (needsReset && !(isComputed && trackerPosition == TrackerPosition.HEAD)) { + if (needsReset || (isComputed && !isInternal)) { // Adjust to reset, mounting and drift compensation rot = resetsHandler.getReferenceAdjustedDriftRotationFrom(rot) } @@ -354,7 +354,7 @@ class Tracker @JvmOverloads constructor( _rotation } - if (needsReset && trackerPosition != TrackerPosition.HEAD) { + if (needsReset || (isComputed && trackerPosition == TrackerPosition.HEAD)) { // Adjust to reset and mounting rot = resetsHandler.getIdentityAdjustedDriftRotationFrom(rot) } diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerResetsHandler.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerResetsHandler.kt index 4f24994de..dc054c3ec 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerResetsHandler.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerResetsHandler.kt @@ -43,6 +43,7 @@ class TrackerResetsHandler(val tracker: Tracker) { private var yawResetSmoothTime = 0.0f private lateinit var fpsTimer: NanoTimer var saveMountingReset = false + var resetHmdPitch = false var allowDriftCompensation = false var lastResetQuaternion: Quaternion? = null @@ -123,6 +124,7 @@ class TrackerResetsHandler(val tracker: Tracker) { fpsTimer = VRServer.instance.fpsTimer } saveMountingReset = config.saveMountingReset + resetHmdPitch = config.resetHmdPitch } fun trySetMountingReset(quat: Quaternion) { @@ -155,7 +157,9 @@ class TrackerResetsHandler(val tracker: Tracker) { */ private fun adjustToReference(rotation: Quaternion): Quaternion { var rot = rotation - rot *= mountingOrientation + if (!tracker.isHmd || tracker.trackerPosition != TrackerPosition.HEAD) { + rot *= mountingOrientation + } rot = gyroFix * rot rot *= attachmentFix rot = mountRotFix.inv() * (rot * mountRotFix) @@ -216,29 +220,57 @@ class TrackerResetsHandler(val tracker: Tracker) { Quaternion.IDENTITY } + // Old rot for drift compensation val oldRot = adjustToReference(tracker.getRawRotation()) lastResetQuaternion = oldRot + // Adjust raw rotation to mountingOrientation val mountingAdjustedRotation = tracker.getRawRotation() * mountingOrientation - if (tracker.needsMounting) { - gyroFix = fixGyroscope(mountingAdjustedRotation * tposeDownFix) - } else { - // Set mounting to the HMD's yaw so that the non-mounting-adjusted - // tracker goes forward. + // Gyrofix + if (tracker.needsMounting || (tracker.trackerPosition == TrackerPosition.HEAD && !tracker.isHmd)) { + gyroFix = if (tracker.isComputed) { + fixGyroscope(tracker.getRawRotation()) + } else { + fixGyroscope(mountingAdjustedRotation * tposeDownFix) + } + } + + // Mounting for computed trackers + if (tracker.isComputed && tracker.trackerPosition != TrackerPosition.HEAD) { + // Set mounting to the reference's yaw so that a computed + // tracker goes forward according to the head tracker. mountRotFix = getYawQuaternion(reference) } - attachmentFix = fixAttachment(mountingAdjustedRotation) - // Rotate attachmentFix by 180 degrees as a workaround for tpose (down) + // Attachment fix + attachmentFix = if (tracker.trackerPosition == TrackerPosition.HEAD && tracker.isHmd) { + if (resetHmdPitch) { + // Reset the HMD's pitch if it's assigned to head and resetHmdPitch is true + // Get rotation without yaw (make sure to use the raw rotation directly!) + val rotBuf = getYawQuaternion(tracker.getRawRotation()).inv() * tracker.getRawRotation() + // Isolate pitch + Quaternion(rotBuf.w, -rotBuf.x, 0f, 0f).unit() + } else { + // Don't reset the HMD at all + Quaternion.IDENTITY + } + } else { + fixAttachment(mountingAdjustedRotation) + } + + // Rotate attachmentFix by 180 degrees as a workaround for t-pose (down) if (tposeDownFix != Quaternion.IDENTITY && tracker.needsMounting) { attachmentFix *= HalfHorizontal } makeIdentityAdjustmentQuatsFull() - yawFix = fixYaw(mountingAdjustedRotation, reference) - yawResetSmoothTimeRemain = 0.0f + // (don't adjust yaw if head and computed) + if (tracker.trackerPosition != TrackerPosition.HEAD || tracker.isComputed) { + yawFix = fixYaw(mountingAdjustedRotation, reference) + yawResetSmoothTimeRemain = 0.0f + } calculateDrift(oldRot) @@ -255,6 +287,7 @@ class TrackerResetsHandler(val tracker: Tracker) { * position should be corrected in the source. */ fun resetYaw(reference: Quaternion) { + // Old rot for drift compensation val oldRot = adjustToReference(tracker.getRawRotation()) lastResetQuaternion = oldRot @@ -286,9 +319,7 @@ class TrackerResetsHandler(val tracker: Tracker) { * and stores it in mountRotFix, and adjusts yawFix */ fun resetMounting(reference: Quaternion) { - if (!resetMountingFeet && isFootTracker()) { - return - } + if (!resetMountingFeet && isFootTracker()) return // Get the current calibrated rotation var rotBuf = adjustToDrift(tracker.getRawRotation() * mountingOrientation) @@ -320,10 +351,8 @@ class TrackerResetsHandler(val tracker: Tracker) { } // Adjust for forward/back arms and thighs - val isLowerArmBack = - armsResetMode == ArmsResetModes.BACK && (isLeftLowerArmTracker() || isRightLowerArmTracker()) - val isArmForward = - armsResetMode == ArmsResetModes.FORWARD && (isLeftArmTracker() || isRightArmTracker()) + val isLowerArmBack = armsResetMode == ArmsResetModes.BACK && (isLeftLowerArmTracker() || isRightLowerArmTracker()) + val isArmForward = armsResetMode == ArmsResetModes.FORWARD && (isLeftArmTracker() || isRightArmTracker()) if (!isThighTracker() && !isArmForward && !isLowerArmBack) { // Tracker goes back yawAngle -= FastMath.PI diff --git a/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt b/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt index 89c555701..c693097d2 100644 --- a/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt +++ b/server/core/src/test/java/dev/slimevr/unit/MountingResetTests.kt @@ -43,7 +43,6 @@ class MountingResetTests { "test", null, hasRotation = true, - isInternal = true, imuType = IMUType.UNKNOWN, needsReset = true, needsMounting = true, @@ -128,7 +127,6 @@ class MountingResetTests { "test", null, hasRotation = true, - isInternal = true, imuType = IMUType.UNKNOWN, needsReset = true, needsMounting = true, diff --git a/server/core/src/test/java/dev/slimevr/unit/ReferenceAdjustmentsTests.kt b/server/core/src/test/java/dev/slimevr/unit/ReferenceAdjustmentsTests.kt index a8bce6ed9..a28d8c85e 100644 --- a/server/core/src/test/java/dev/slimevr/unit/ReferenceAdjustmentsTests.kt +++ b/server/core/src/test/java/dev/slimevr/unit/ReferenceAdjustmentsTests.kt @@ -92,7 +92,6 @@ class ReferenceAdjustmentsTests { "test", null, hasRotation = true, - isInternal = true, imuType = IMUType.UNKNOWN, needsReset = true, ) @@ -125,7 +124,6 @@ class ReferenceAdjustmentsTests { "test", null, hasRotation = true, - isInternal = true, imuType = IMUType.UNKNOWN, needsReset = true, ) @@ -154,7 +152,6 @@ class ReferenceAdjustmentsTests { "test", null, hasRotation = true, - isInternal = true, imuType = IMUType.UNKNOWN, needsReset = true, ) diff --git a/server/desktop/src/main/java/dev/slimevr/desktop/platform/SteamVRBridge.kt b/server/desktop/src/main/java/dev/slimevr/desktop/platform/SteamVRBridge.kt index 9f8d59623..3f3d9bcd2 100644 --- a/server/desktop/src/main/java/dev/slimevr/desktop/platform/SteamVRBridge.kt +++ b/server/desktop/src/main/java/dev/slimevr/desktop/platform/SteamVRBridge.kt @@ -153,6 +153,7 @@ abstract class SteamVRBridge( "OpenVR", // TODO : We need the manufacturer ) + // Display name, needsReset and isHmd val displayName: String val (needsReset, isHmd) = if (trackerAdded.trackerId == 0) { displayName = if (trackerAdded.trackerName == "HMD") { @@ -160,19 +161,27 @@ abstract class SteamVRBridge( } else { "Feeder App HMD" } - // TODO support needsReset = true for VTubing (GUI toggle?) false to true } else { displayName = trackerAdded.trackerName true to false } + // trackerPosition + val role = getById(trackerAdded.trackerRole) + val trackerPosition = if (role != null) { + getByTrackerRole(role) + } else { + null + } + + // Make the tracker val tracker = Tracker( device, getNextLocalTrackerId(), trackerAdded.trackerSerial, displayName, - null, + trackerPosition, trackerAdded.trackerId, hasPosition = true, hasRotation = true, @@ -184,10 +193,6 @@ abstract class SteamVRBridge( device.trackers[0] = tracker instance.deviceManager.addDevice(device) - val role = getById(trackerAdded.trackerRole) - if (role != null) { - tracker.trackerPosition = getByTrackerRole(role) - } return tracker } diff --git a/solarxr-protocol b/solarxr-protocol index f17e716c1..b182bec2c 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit f17e716c19540a522c705cfe20336ae312ceb829 +Subproject commit b182bec2cad042fdbfdeb27236a310cb0bd40617