diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl index ce4026ce68..6be5b28512 100644 --- a/gui/public/i18n/en/translation.ftl +++ b/gui/public/i18n/en/translation.ftl @@ -44,6 +44,7 @@ body_part-LEFT_HAND = Left hand body_part-LEFT_UPPER_LEG = Left thigh body_part-LEFT_LOWER_LEG = Left ankle body_part-LEFT_FOOT = Left foot +body_part-PLAYSPACE = Playspace (motion compensation) ## Proportions skeleton_bone-NONE = None diff --git a/gui/src/components/commons/BodyPartIcon.tsx b/gui/src/components/commons/BodyPartIcon.tsx index 8bfbbde485..a87f8d7c1c 100644 --- a/gui/src/components/commons/BodyPartIcon.tsx +++ b/gui/src/components/commons/BodyPartIcon.tsx @@ -86,6 +86,9 @@ export const mapPart: Record< ), [BodyPart.WAIST]: ({ width }) => , + [BodyPart.PLAYSPACE]: ({ width }) => ( + + ), // TODO }; export function BodyPartIcon({ diff --git a/gui/src/components/onboarding/BodyAssignment.tsx b/gui/src/components/onboarding/BodyAssignment.tsx index 90af2f731c..812a6cda1a 100644 --- a/gui/src/components/onboarding/BodyAssignment.tsx +++ b/gui/src/components/onboarding/BodyAssignment.tsx @@ -307,6 +307,16 @@ export function BodyAssignment({ onClick={() => onRoleSelected(SIDES[right].foot)} direction="left" /> + {advanced && ( + onRoleSelected(BodyPart.PLAYSPACE)} + direction="left" + /> + )} } diff --git a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx index d29dad03d8..10e5f18fc1 100644 --- a/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx +++ b/gui/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx @@ -99,6 +99,7 @@ export const mapPart: Record< ), [BodyPart.WAIST]: ({ width }) => , + [BodyPart.PLAYSPACE]: ({ width }) => , }; export function MountingBodyPartIcon({ diff --git a/gui/src/utils/skeletonHelper.ts b/gui/src/utils/skeletonHelper.ts index 578174b758..a6234d6a00 100644 --- a/gui/src/utils/skeletonHelper.ts +++ b/gui/src/utils/skeletonHelper.ts @@ -185,6 +185,7 @@ export class BoneKind extends Bone { get boneColor(): Color { switch (this.boneT.bodyPart) { + case BodyPart.PLAYSPACE: case BodyPart.NONE: throw 'Unexpected body part'; case BodyPart.HEAD: @@ -230,6 +231,7 @@ export class BoneKind extends Bone { static children(part: BodyPart): BodyPart[] { switch (part) { + case BodyPart.PLAYSPACE: case BodyPart.NONE: throw 'Unexpected body part'; case BodyPart.HEAD: @@ -283,6 +285,7 @@ export class BoneKind extends Bone { static parent(part: BodyPart): BodyPart | null { switch (part) { + case BodyPart.PLAYSPACE: case BodyPart.NONE: throw 'Unexpected body part'; case BodyPart.HEAD: diff --git a/server/core/src/main/java/dev/slimevr/tracking/processor/Bone.kt b/server/core/src/main/java/dev/slimevr/tracking/processor/Bone.kt index 3a291f49a4..a63186ba2b 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/processor/Bone.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/processor/Bone.kt @@ -69,12 +69,19 @@ class Bone(val boneType: BoneType) { fun getLocalRotation(): Quaternion = headNode.localTransform.rotation /** - * Sets the global rotation of the bone + * Sets the global rotation of the bone with the rotationOffset */ fun setRotation(rotation: Quaternion) { headNode.localTransform.rotation = rotation * rotationOffset } + /** + * Sets the global rotation of the bone + */ + fun setRawRotation(rotation: Quaternion) { + headNode.localTransform.rotation = rotation + } + /** * Returns the global position of the head of the bone */ 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 22a15cf387..287f8d6e99 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 @@ -342,7 +342,7 @@ class HumanPoseManager(val server: VRServer?) { */ val trackersToReset: List get() = - server?.allTrackers ?: skeleton.localTrackers + server?.allTrackers ?: skeleton.trackersToReset /** * @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 a0f351eb4a..50955a89f4 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 @@ -103,6 +103,7 @@ class HumanSkeleton( var rightHandTracker: Tracker? = null var leftShoulderTracker: Tracker? = null var rightShoulderTracker: Tracker? = null + var playspaceTracker: Tracker? = null // Output trackers var computedHeadTracker: Tracker? = null @@ -278,6 +279,7 @@ class HumanSkeleton( rightHandTracker = getTrackerForSkeleton(trackers, TrackerPosition.RIGHT_HAND) leftShoulderTracker = getTrackerForSkeleton(trackers, TrackerPosition.LEFT_SHOULDER) rightShoulderTracker = getTrackerForSkeleton(trackers, TrackerPosition.RIGHT_SHOULDER) + playspaceTracker = getTrackerForSkeleton(trackers, TrackerPosition.PLAYSPACE) // Check for specific conditions and store them in booleans. hasSpineTracker = upperChestTracker != null || chestTracker != null || waistTracker != null || hipTracker != null @@ -354,18 +356,45 @@ class HumanSkeleton( */ @VRServerThread fun updatePose() { + // Check taps for resets tapDetectionManager.update() + // Do FK updateTransforms() + + // Update bones and output trackers updateBones() updateComputedTrackers() // Don't run post-processing if the tracking is paused if (pauseTracking) return + // Run Legtweaks legTweaks.tweakLegs() + + // Run Mocap mode localizer.update() + + // Run Vive hip emulation viveEmulation.update() + + // Playspace motion compensation + playspaceTracker?.let { + if (it.hasRotation) { + val motionCompensationRotOffset = it.getRotation().inv() + for (bone in allBones) { + bone.setRawRotation(motionCompensationRotOffset * bone.getLocalRotation()) + } + } + if (it.hasPosition) { + // TODO motion compensate for position as well? + // headBone.setPosition(headBone.getPosition() - it.position) + } + + // Update bones and output trackers + updateBones() + updateComputedTrackers() + } } /** @@ -390,6 +419,7 @@ class HumanSkeleton( // Spine updateSpineTransforms() + // Left leg updateLegTransforms( leftUpperLegBone, @@ -401,6 +431,7 @@ class HumanSkeleton( leftLowerLegTracker, leftFootTracker, ) + // Right leg updateLegTransforms( rightUpperLegBone, @@ -412,6 +443,7 @@ class HumanSkeleton( rightLowerLegTracker, rightFootTracker, ) + // Left arm updateArmTransforms( isTrackingLeftArmFromController, @@ -426,6 +458,7 @@ class HumanSkeleton( leftLowerArmTracker, leftHandTracker, ) + // Right arm updateArmTransforms( isTrackingRightArmFromController, @@ -982,6 +1015,34 @@ class HumanSkeleton( rightHandBone, ) + /** + * Returns an array of all the bones, trackers or not + */ + private val allBones: Array + get() = arrayOf( + headBone, + neckBone, + headTrackerBone, + upperChestBone, + chestBone, + chestTrackerBone, + waistBone, + hipBone, + hipTrackerBone, + leftHipBone, + rightHipBone, + leftUpperLegBone, + leftKneeTrackerBone, + rightUpperLegBone, + rightKneeTrackerBone, + leftLowerLegBone, + rightLowerLegBone, + leftFootBone, + leftFootTrackerBone, + rightFootBone, + rightFootTrackerBone, + ) + allArmBones + /** * Returns all the arm bones, tracker or not. */ @@ -1023,7 +1084,7 @@ class HumanSkeleton( */ val isTrackingRightArmFromController: Boolean get() = rightHandTracker != null && rightHandTracker!!.hasPosition && !forceArmsFromHMD - val localTrackers: List + val trackersToReset: List get() = listOf( neckTracker, chestTracker, @@ -1046,10 +1107,8 @@ class HumanSkeleton( ) fun resetTrackersFull(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset - - // Resets all axis of the trackers with the HMD as reference. var referenceRotation = IDENTITY + // Reset the head tracker from identity if needed, else store its rotation headTracker?.let { if (it.needsReset) { it.resetsHandler.resetFull(referenceRotation) @@ -1057,11 +1116,14 @@ class HumanSkeleton( referenceRotation = it.getRotation() } } + // Resets the trackers with the HMD as reference. for (tracker in trackersToReset) { if (tracker != null && tracker.needsReset) { tracker.resetsHandler.resetFull(referenceRotation) } } + // Reset the playspace tracker from identity rotation + playspaceTracker?.resetsHandler?.resetFull(IDENTITY) // Tell floorclip to reset its floor level on the next update // of the computed trackers @@ -1075,10 +1137,8 @@ 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 + // Reset the head tracker from identity if needed, else store its rotation headTracker?.let { if (it.needsReset) { it.resetsHandler.resetYaw(referenceRotation) @@ -1086,22 +1146,23 @@ class HumanSkeleton( referenceRotation = it.getRotation() } } + // Resets the trackers with the HMD as reference. for (tracker in trackersToReset) { if (tracker != null && tracker.needsReset) { tracker.resetsHandler.resetYaw(referenceRotation) } } + // Reset the playspace tracker from identity rotation + playspaceTracker?.resetsHandler?.resetYaw(IDENTITY) + legTweaks.resetBuffer() LogManager.info(String.format("[HumanSkeleton] Reset: yaw (%s)", resetSourceName)) } @VRServerThread fun resetTrackersMounting(resetSourceName: String?) { - val trackersToReset = humanPoseManager.trackersToReset - - // Resets the mounting orientation of the trackers with the HMD as - // reference. var referenceRotation = IDENTITY + // Reset the head tracker from identity if needed, else store its rotation headTracker?.let { if (it.needsMounting) { it.resetsHandler.resetMounting(referenceRotation) @@ -1109,11 +1170,13 @@ class HumanSkeleton( referenceRotation = it.getRotation() } } + // Resets the trackers with the HMD as reference. for (tracker in trackersToReset) { if (tracker != null && tracker.needsMounting) { tracker.resetsHandler.resetMounting(referenceRotation) } } + legTweaks.resetBuffer() localizer.reset() LogManager.info(String.format("[HumanSkeleton] Reset: mounting (%s)", resetSourceName)) @@ -1121,7 +1184,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/TrackerPosition.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt index 559aea8995..6ed95e4d13 100644 --- a/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt +++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerPosition.kt @@ -36,6 +36,7 @@ enum class TrackerPosition( RIGHT_HAND("body:right_hand", TrackerRole.RIGHT_HAND, BodyPart.RIGHT_HAND), LEFT_SHOULDER("body:left_shoulder", TrackerRole.LEFT_SHOULDER, BodyPart.LEFT_SHOULDER), RIGHT_SHOULDER("body:right_shoulder", TrackerRole.RIGHT_SHOULDER, BodyPart.RIGHT_SHOULDER), + PLAYSPACE("body:playspace", null, BodyPart.PLAYSPACE), ; /** diff --git a/solarxr-protocol b/solarxr-protocol index b1ae56c26a..81215ad3df 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit b1ae56c26a7b1e262bf051589620433de1eea88f +Subproject commit 81215ad3df67612313ca7da6540b09cc64f355b0