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