-
-
Notifications
You must be signed in to change notification settings - Fork 526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Publish tracking data through VMC Protocol #2461
Changes from all commits
730a4dc
83f52fc
93e75bb
b0aa69d
52f4fe2
d4e0fb6
38ed6cc
82f6298
1cbf482
6e23ce2
236a754
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
use alvr_common::{ | ||
anyhow::Result, glam::Quat, once_cell::sync::Lazy, DeviceMotion, Pose, BODY_CHEST_ID, | ||
BODY_HIPS_ID, BODY_LEFT_ELBOW_ID, BODY_LEFT_FOOT_ID, BODY_LEFT_KNEE_ID, BODY_RIGHT_ELBOW_ID, | ||
BODY_RIGHT_FOOT_ID, BODY_RIGHT_KNEE_ID, HAND_LEFT_ID, HAND_RIGHT_ID, HEAD_ID, | ||
}; | ||
use rosc::{OscMessage, OscPacket, OscType}; | ||
use std::{collections::HashMap, net::UdpSocket}; | ||
|
||
use alvr_session::VMCConfig; | ||
|
||
pub use crate::tracking::HandType; | ||
|
||
// Transform DeviceMotion into Unity HumanBodyBones | ||
// https://docs.unity3d.com/ScriptReference/HumanBodyBones.html | ||
static DEVICE_MOTIONS_VMC_MAP: Lazy<HashMap<u64, &'static str>> = Lazy::new(|| { | ||
HashMap::from([ | ||
(*HAND_LEFT_ID, "LeftHand"), | ||
(*HAND_RIGHT_ID, "RightHand"), | ||
(*BODY_CHEST_ID, "Chest"), | ||
(*BODY_HIPS_ID, "Hips"), | ||
(*BODY_LEFT_ELBOW_ID, "LeftLowerArm"), | ||
(*BODY_RIGHT_ELBOW_ID, "RightLowerArm"), | ||
(*BODY_LEFT_KNEE_ID, "LeftLowerLeg"), | ||
(*BODY_LEFT_FOOT_ID, "LeftFoot"), | ||
(*BODY_RIGHT_KNEE_ID, "RightLowerLeg"), | ||
(*BODY_RIGHT_FOOT_ID, "RightFoot"), | ||
(*HEAD_ID, "Head"), | ||
]) | ||
}); | ||
|
||
static DEVICE_MOTIONS_ROTATION_MAP: Lazy<HashMap<u64, Quat>> = Lazy::new(|| { | ||
HashMap::from([ | ||
( | ||
*HAND_LEFT_ID, | ||
Quat::from_xyzw(-0.03538, 0.25483, -0.00000, -0.96634), | ||
), | ||
( | ||
*HAND_RIGHT_ID, | ||
Quat::from_xyzw(-0.05859, -0.20524, -0.00000, 0.97696), | ||
), | ||
( | ||
*BODY_CHEST_ID, | ||
Quat::from_xyzw(-0.49627, 0.49516, -0.43469, -0.56531), | ||
), | ||
( | ||
*BODY_HIPS_ID, | ||
Quat::from_xyzw(-0.49274, 0.49568, -0.42416, -0.57584), | ||
), | ||
( | ||
*BODY_RIGHT_ELBOW_ID, | ||
Quat::from_xyzw(-0.63465, -0.11567, 0.00000, 0.76410), | ||
), | ||
( | ||
*BODY_LEFT_KNEE_ID, | ||
Quat::from_xyzw(0.51049, 0.47862, 0.42815, -0.57185), | ||
), | ||
( | ||
*BODY_LEFT_FOOT_ID, | ||
Quat::from_xyzw(-0.59103, 0.38818, 0.00000, -0.7071100), | ||
), | ||
( | ||
*BODY_RIGHT_KNEE_ID, | ||
Quat::from_xyzw(-0.52823, 0.45434, -0.58530, -0.41470), | ||
), | ||
( | ||
*BODY_RIGHT_FOOT_ID, | ||
Quat::from_xyzw(0.70228, -0.08246, 0.7071100, 0.00000), | ||
), | ||
]) | ||
}); | ||
|
||
static HAND_SKELETON_VMC_MAP: Lazy<[[(usize, &'static str); 1]; 2]> = | ||
Lazy::new(|| [[(0, "LeftHand")], [(0, "RightHand")]]); | ||
|
||
static HAND_SKELETON_ROTATIONS: Lazy<[HashMap<usize, Quat>; 2]> = Lazy::new(|| { | ||
[ | ||
HashMap::from([(0, Quat::from_xyzw(-0.03566, 0.25481, 0.00000, -0.96633))]), | ||
HashMap::from([(0, Quat::from_xyzw(-0.05880, -0.20574, -0.00000, 0.97684))]), | ||
] | ||
}); | ||
|
||
pub struct VMCSink { | ||
socket: Option<UdpSocket>, | ||
} | ||
|
||
impl VMCSink { | ||
pub fn new(config: VMCConfig) -> Result<Self> { | ||
let socket = UdpSocket::bind("0.0.0.0:0")?; | ||
socket.connect(format!("{}:{}", config.host, config.port))?; | ||
|
||
Ok(Self { | ||
socket: Some(socket), | ||
}) | ||
} | ||
|
||
fn send_osc_message(&self, path: &str, args: Vec<OscType>) { | ||
if let Some(socket) = &self.socket { | ||
socket | ||
.send( | ||
&rosc::encoder::encode(&OscPacket::Message(OscMessage { | ||
addr: path.into(), | ||
args, | ||
})) | ||
.unwrap(), | ||
) | ||
.ok(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to log errors that occur here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I based myself on the one implemented here https://github.com/alvr-org/ALVR/blob/master/alvr/server_core/src/tracking/body.rs#L48-L60 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tbh it's probably fine without error logging, was just a hunch |
||
} | ||
} | ||
|
||
pub fn send_hand_tracking( | ||
&mut self, | ||
hand_type: HandType, | ||
skeleton: [Pose; 26], | ||
orientation_correction: bool, | ||
) { | ||
let hand_id = hand_type as usize; | ||
for (part, vmc_str) in HAND_SKELETON_VMC_MAP[hand_id] { | ||
let corrected_orientation = { | ||
let mut q = skeleton[part].orientation; | ||
if orientation_correction { | ||
if HAND_SKELETON_ROTATIONS[hand_id].contains_key(&part) { | ||
q *= *HAND_SKELETON_ROTATIONS[hand_id].get(&part).unwrap(); | ||
} | ||
q.z = -q.z; | ||
q.w = -q.w; | ||
} | ||
q | ||
}; | ||
|
||
self.send_osc_message( | ||
"/VMC/Ext/Bone/Pos", | ||
vec![ | ||
OscType::String(vmc_str.to_string()), | ||
OscType::Float(skeleton[part].position.x), | ||
OscType::Float(skeleton[part].position.y), | ||
OscType::Float(skeleton[part].position.z), | ||
OscType::Float(corrected_orientation.x), | ||
OscType::Float(corrected_orientation.y), | ||
OscType::Float(corrected_orientation.z), | ||
OscType::Float(corrected_orientation.w), | ||
], | ||
); | ||
} | ||
} | ||
|
||
pub fn send_tracking( | ||
&mut self, | ||
device_motions: &[(u64, DeviceMotion)], | ||
orientation_correction: bool, | ||
) { | ||
for (id, motion) in device_motions { | ||
if DEVICE_MOTIONS_VMC_MAP.contains_key(id) { | ||
let corrected_orientation = { | ||
let mut q = motion.pose.orientation; | ||
if orientation_correction { | ||
if DEVICE_MOTIONS_ROTATION_MAP.contains_key(id) { | ||
q *= *DEVICE_MOTIONS_ROTATION_MAP.get(id).unwrap(); | ||
} | ||
q.z = -q.z; | ||
q.w = -q.w; | ||
} | ||
q | ||
}; | ||
|
||
self.send_osc_message( | ||
"/VMC/Ext/Bone/Pos", | ||
vec![ | ||
OscType::String(DEVICE_MOTIONS_VMC_MAP.get(id).unwrap().to_string()), | ||
OscType::Float(motion.pose.position.x), | ||
OscType::Float(motion.pose.position.y), | ||
OscType::Float(motion.pose.position.z), | ||
OscType::Float(corrected_orientation.x), | ||
OscType::Float(corrected_orientation.y), | ||
OscType::Float(corrected_orientation.z), | ||
OscType::Float(corrected_orientation.w), | ||
], | ||
); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't really have the time to actually look right now, how does this interact with hand tracking/multi-input protocol (I forgot what it's called sry). I.e. does it miss the hand poses? And is this before and after the removal of the hand poses in the case of protocol compat?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see the multimodal compat is at line 352. that already handles everything we need. after that point you don't care if the client does or doesn't support multimodal protocol, it will always be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Well then I think hands won't get exported properly if you're currently using hand tracking. Or I'm missing something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only sending hand position/rotation information. Even if the protocol supports hand poses (through bone position/rotation), I haven't checked how to integrate that. So if there is any data about that, the sink should just ignore it for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is that if you're using hand tracking then the palm/wrist pose is carried as part of the hand tracking info, meaning your code will miss it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look how it behaves with hand tracking when I get home and report back then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@The-personified-devil Ah i got it now, you're right. if hand tracking is active, we don't send hand DeviceMotions, you should at least get the palm pose (first element of the hand skeleton array)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I see what you mean! I started toying with hand tracking and integrating it wasn't hard, but noticed that they require their own rotation corrections :p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's odd. controllers do need a pose offset fix, which we do from settings. since you are getting the data from the tracking_manager then the pose is already fixed, and also recentered. same for hands. so i'm not sure a separate pose fix for hands
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the end it was an off calibration of the Right Elbow what was off. It's kinda hard to calibrate without shoulder/upper arm data, but now it should be fine.