feat(server): Expose button remapping settings

This commit is contained in:
Riccardo Zaglia 2023-09-13 15:02:31 +08:00
parent ce7328568f
commit 254d06c197
4 changed files with 275 additions and 149 deletions

View File

@ -84,7 +84,14 @@ pub fn contruct_openvr_config() -> OpenvrConfig {
let controllers_enabled = if let Switch::Enabled(config) = settings.headset.controllers {
controller_is_tracker =
matches!(config.emulation_mode, ControllersEmulationMode::ViveTracker);
_controller_profile = config.emulation_mode as i32;
_controller_profile = match config.emulation_mode {
ControllersEmulationMode::RiftSTouch => 0,
ControllersEmulationMode::Quest2Touch => 1,
ControllersEmulationMode::ValveIndex => 2,
ControllersEmulationMode::ViveWand => 3,
ControllersEmulationMode::ViveTracker => 4,
ControllersEmulationMode::Custom { .. } => 5,
};
true
} else {
@ -892,20 +899,25 @@ fn try_connect(mut client_ips: HashMap<IpAddr, String>) -> ConResult {
});
let control_receive_thread = thread::spawn({
let mut controller_button_mapping_manager = if let Switch::Enabled(config) =
&SERVER_DATA_MANAGER.read().settings().headset.controllers
{
Some(ButtonMappingManager::new_automatic(
&CONTROLLER_PROFILE_INFO
.get(&alvr_common::hash_string(QUEST_CONTROLLER_PROFILE_PATH))
.unwrap()
.button_set,
&config.button_mapping_config,
))
} else {
None
};
// todo: gestures_button_mapping_manager...
let mut controller_button_mapping_manager = SERVER_DATA_MANAGER
.read()
.settings()
.headset
.controllers
.as_option()
.map(|config| {
if let Some(mappings) = &config.button_mappings {
ButtonMappingManager::new_manual(mappings)
} else {
ButtonMappingManager::new_automatic(
&CONTROLLER_PROFILE_INFO
.get(&alvr_common::hash_string(QUEST_CONTROLLER_PROFILE_PATH))
.unwrap()
.button_set,
&config.button_mapping_config,
)
}
});
let control_sender = Arc::clone(&control_sender);
let client_hostname = client_hostname.clone();
@ -1024,10 +1036,14 @@ fn try_connect(mut client_ips: HashMap<IpAddr, String>) -> ConResult {
&SERVER_DATA_MANAGER.read().settings().headset.controllers,
CONTROLLER_PROFILE_INFO.get(&profile_id),
) {
Some(ButtonMappingManager::new_automatic(
&profile_info.button_set,
&config.button_mapping_config,
))
if let Some(mappings) = &config.button_mappings {
Some(ButtonMappingManager::new_manual(mappings))
} else {
Some(ButtonMappingManager::new_automatic(
&profile_info.button_set,
&config.button_mapping_config,
))
}
} else {
None
};

View File

@ -1,11 +1,11 @@
use crate::{bindings::FfiButtonValue, SERVER_DATA_MANAGER};
use alvr_common::{once_cell::sync::Lazy, settings_schema::Switch, *};
use alvr_packets::ButtonValue;
use alvr_session::{AutomaticButtonMappingConfig, ControllersEmulationMode, HysteresisThreshold};
use std::{
collections::{HashMap, HashSet},
ops::Range,
use alvr_session::{
AutomaticButtonMappingConfig, BinaryToScalarStates, ButtonBindingTarget, ButtonMappingType,
ControllersEmulationMode, HysteresisThreshold, Range,
};
use std::collections::{HashMap, HashSet};
pub static REGISTERED_BUTTON_SET: Lazy<HashSet<u64>> = Lazy::new(|| {
let data_manager_lock = SERVER_DATA_MANAGER.read();
@ -14,36 +14,35 @@ pub static REGISTERED_BUTTON_SET: Lazy<HashSet<u64>> = Lazy::new(|| {
return HashSet::new();
};
let profile = match &controllers_config.emulation_mode {
match &controllers_config.emulation_mode {
ControllersEmulationMode::RiftSTouch | ControllersEmulationMode::Quest2Touch => {
&QUEST_CONTROLLER_PROFILE_ID
CONTROLLER_PROFILE_INFO
.get(&QUEST_CONTROLLER_PROFILE_ID)
.unwrap()
.button_set
.clone()
}
ControllersEmulationMode::ValveIndex => &INDEX_CONTROLLER_PROFILE_ID,
ControllersEmulationMode::ViveWand => &VIVE_CONTROLLER_PROFILE_ID,
ControllersEmulationMode::ViveTracker => return HashSet::new(),
};
CONTROLLER_PROFILE_INFO
.get(profile)
.unwrap()
.button_set
.clone()
ControllersEmulationMode::ValveIndex => CONTROLLER_PROFILE_INFO
.get(&INDEX_CONTROLLER_PROFILE_ID)
.unwrap()
.button_set
.clone(),
ControllersEmulationMode::ViveWand => CONTROLLER_PROFILE_INFO
.get(&VIVE_CONTROLLER_PROFILE_ID)
.unwrap()
.button_set
.clone(),
ControllersEmulationMode::ViveTracker => HashSet::new(),
ControllersEmulationMode::Custom { button_set, .. } => button_set
.iter()
.map(|b| alvr_common::hash_string(b))
.collect(),
}
});
pub struct BinaryToScalarStates {
off: f32,
on: f32,
}
pub enum MappingType {
Passthrough,
HysteresisThreshold(HysteresisThreshold),
BinaryToScalar(BinaryToScalarStates),
Remap(Range<f32>), // remaps 0..1 to custom range
}
pub struct BindingTarget {
destination: u64,
mapping_type: MappingType,
mapping_type: ButtonMappingType,
binary_conditions: Vec<u64>,
}
@ -104,7 +103,7 @@ fn ctvf(set: &HashSet<u64>, click: u64, touch: u64, value: u64, force: u64) -> B
fn passthrough(target: u64) -> BindingTarget {
BindingTarget {
destination: target,
mapping_type: MappingType::Passthrough,
mapping_type: ButtonMappingType::Passthrough,
binary_conditions: vec![],
}
}
@ -112,7 +111,7 @@ fn passthrough(target: u64) -> BindingTarget {
fn binary_to_scalar(target: u64, map: BinaryToScalarStates) -> BindingTarget {
BindingTarget {
destination: target,
mapping_type: MappingType::BinaryToScalar(map),
mapping_type: ButtonMappingType::BinaryToScalar(map),
binary_conditions: vec![],
}
}
@ -120,15 +119,15 @@ fn binary_to_scalar(target: u64, map: BinaryToScalarStates) -> BindingTarget {
fn hysteresis_threshold(target: u64, map: HysteresisThreshold) -> BindingTarget {
BindingTarget {
destination: target,
mapping_type: MappingType::HysteresisThreshold(map),
mapping_type: ButtonMappingType::HysteresisThreshold(map),
binary_conditions: vec![],
}
}
fn remap(target: u64, map: Range<f32>) -> BindingTarget {
fn remap(target: u64, map: Range) -> BindingTarget {
BindingTarget {
destination: target,
mapping_type: MappingType::Remap(map),
mapping_type: ButtonMappingType::Remap(map),
binary_conditions: vec![],
}
}
@ -192,7 +191,13 @@ fn map_button_pair_automatic(
}
if source.force.is_none() {
if let Some(destination_force) = destination.force {
targets.push(remap(destination_force, config.force_threshold..1.0));
targets.push(remap(
destination_force,
Range {
min: config.force_threshold,
max: 1.0,
},
));
remap_for_force = true;
}
}
@ -210,7 +215,13 @@ fn map_button_pair_automatic(
} else {
1.0
};
targets.push(remap(destination_value, low..high));
targets.push(remap(
destination_value,
Range {
min: low,
max: high,
},
));
}
}
@ -541,13 +552,34 @@ impl ButtonMappingManager {
}
}
// pub fn new_manual(mappings: HashMap<u64, Vec<BindingTarget>>) -> Self {
// Self {
// mappings,
// binary_source_states: HashMap::new(),
// hysteresis_states: HashMap::new(),
// }
// }
pub fn new_manual(mappings: &[(String, Vec<ButtonBindingTarget>)]) -> Self {
let mappings = mappings
.iter()
.map(|(key, value)| {
(
alvr_common::hash_string(key),
value
.iter()
.map(|b| BindingTarget {
destination: alvr_common::hash_string(&b.destination),
mapping_type: b.mapping_type.clone(),
binary_conditions: b
.binary_conditions
.iter()
.map(|c| alvr_common::hash_string(c))
.collect(),
})
.collect(),
)
})
.collect();
Self {
mappings,
binary_source_states: HashMap::new(),
hysteresis_states: HashMap::new(),
}
}
// Apply any button changes that are mapped to this specific button
pub fn report_button(&mut self, source_id: u64, source_value: ButtonValue) {
@ -564,8 +596,11 @@ impl ButtonMappingManager {
if let Some(mappings) = self.mappings.get(&source_id) {
'mapping: for mapping in mappings {
let destination_value = match (&mapping.mapping_type, source_value) {
(MappingType::Passthrough, value) => value,
(MappingType::HysteresisThreshold(threshold), ButtonValue::Scalar(value)) => {
(ButtonMappingType::Passthrough, value) => value,
(
ButtonMappingType::HysteresisThreshold(threshold),
ButtonValue::Scalar(value),
) => {
let state = self
.hysteresis_states
.entry(source_id)
@ -584,15 +619,15 @@ impl ButtonMappingManager {
ButtonValue::Binary(*state)
}
(MappingType::BinaryToScalar(levels), ButtonValue::Binary(value)) => {
(ButtonMappingType::BinaryToScalar(levels), ButtonValue::Binary(value)) => {
if value {
ButtonValue::Scalar(levels.on)
} else {
ButtonValue::Scalar(levels.off)
}
}
(MappingType::Remap(range), ButtonValue::Scalar(value)) => {
let value = (value - range.start) / (range.end - range.start);
(ButtonMappingType::Remap(range), ButtonValue::Scalar(value)) => {
let value = (value - range.min) / (range.max - range.min);
ButtonValue::Scalar(value.clamp(0.0, 1.0))
}
_ => {

View File

@ -72,6 +72,7 @@ fn serial_number(device_id: u64) -> String {
ControllersEmulationMode::ValveIndex => "ALVR Remote Controller",
ControllersEmulationMode::ViveWand => "ALVR Remote Controller",
ControllersEmulationMode::ViveTracker => "ALVR Remote Controller",
ControllersEmulationMode::Custom { serial_number, .. } => serial_number,
};
if device_id == *LEFT_HAND_ID {
@ -142,11 +143,7 @@ pub extern "C" fn set_device_openvr_props(device_id: u64) {
set_prop(RegisteredDeviceType("vive".into()));
set_prop(DriverVersion("".into()));
}
HeadsetEmulationMode::Custom { props, .. } => {
for prop in props {
set_prop(prop.clone());
}
}
HeadsetEmulationMode::Custom { .. } => (),
}
set_prop(UserIpdMeters(0.063));
@ -454,6 +451,7 @@ pub extern "C" fn set_device_openvr_props(device_id: u64) {
"{htc}/icons/tracker_status_ready_low.png".into(),
));
}
ControllersEmulationMode::Custom { .. } => todo!(),
}
set_prop(SerialNumber(serial_number(device_id)));

View File

@ -571,7 +571,6 @@ pub enum HeadsetEmulationMode {
Vive,
Custom {
serial_number: String,
props: Vec<OpenvrProperty>,
},
}
@ -608,6 +607,10 @@ pub enum ControllersEmulationMode {
ValveIndex,
ViveWand,
ViveTracker,
Custom {
serial_number: String,
button_set: Vec<String>,
},
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)]
@ -618,6 +621,38 @@ pub struct HysteresisThreshold {
pub deviation: f32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)]
pub struct BinaryToScalarStates {
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub off: f32,
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub on: f32,
}
// Remaps 0..1 to custom range
#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)]
pub struct Range {
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub min: f32,
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub max: f32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
pub enum ButtonMappingType {
Passthrough,
HysteresisThreshold(HysteresisThreshold),
BinaryToScalar(BinaryToScalarStates),
Remap(Range),
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
pub struct ButtonBindingTarget {
pub destination: String,
pub mapping_type: ButtonMappingType,
pub binary_conditions: Vec<String>,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct AutomaticButtonMappingConfig {
@ -626,81 +661,6 @@ pub struct AutomaticButtonMappingConfig {
pub force_threshold: f32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct HapticsConfig {
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 5.0, step = 0.1)))]
pub intensity_multiplier: f32,
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub amplitude_curve: f32,
#[schema(strings(display_name = "Minimum duration"))]
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 0.1, step = 0.001)), suffix = "s")]
pub min_duration_s: f32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct ControllersConfig {
#[schema(strings(help = "Turning this off will make the controllers appear powered off."))]
#[schema(flag = "real-time")]
pub tracked: bool,
#[schema(flag = "steamvr-restart")]
pub emulation_mode: ControllersEmulationMode,
#[schema(flag = "steamvr-restart")]
pub extra_openvr_props: Vec<OpenvrProperty>,
pub button_mapping_config: AutomaticButtonMappingConfig,
pub hand_tracking: HandTrackingConfig,
#[schema(strings(
display_name = "Prediction",
help = r"Higher values make the controllers track smoother.
Technically, this is the time (counted in frames) between pose submitted to SteamVR and the corresponding virtual vsync happens.
Currently this cannot be reliably estimated automatically. The correct value should be 2 but 3 is default for smoother tracking at the cost of slight lag."
))]
#[schema(gui(slider(min = 1.0, max = 10.0, logarithmic)), suffix = "frames")]
pub steamvr_pipeline_frames: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)), suffix = "m/s")]
pub linear_velocity_cutoff: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = 0.0, max = 100.0, step = 1.0)), suffix = "°/s")]
pub angular_velocity_cutoff: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = -0.5, max = 0.5, step = 0.001)), suffix = "m")]
pub left_controller_position_offset: [f32; 3],
#[schema(flag = "real-time")]
#[schema(gui(slider(min = -180.0, max = 180.0, step = 1.0)), suffix = "°")]
pub left_controller_rotation_offset: [f32; 3],
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = -0.5, max = 0.5, step = 0.001)), suffix = "m")]
pub left_hand_tracking_position_offset: [f32; 3],
#[schema(flag = "real-time")]
#[schema(gui(slider(min = -180.0, max = 180.0, step = 1.0)), suffix = "°")]
pub left_hand_tracking_rotation_offset: [f32; 3],
#[schema(flag = "real-time")]
pub haptics: Switch<HapticsConfig>,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct HandTrackingConfig {
@ -788,6 +748,84 @@ pub struct HandGestureConfig {
pub repeat_delay: u32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct HapticsConfig {
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 5.0, step = 0.1)))]
pub intensity_multiplier: f32,
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)))]
pub amplitude_curve: f32,
#[schema(strings(display_name = "Minimum duration"))]
#[schema(flag = "real-time")]
#[schema(gui(slider(min = 0.0, max = 0.1, step = 0.001)), suffix = "s")]
pub min_duration_s: f32,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[schema(collapsible)]
pub struct ControllersConfig {
#[schema(strings(help = "Turning this off will make the controllers appear powered off."))]
#[schema(flag = "real-time")]
pub tracked: bool,
#[schema(flag = "steamvr-restart")]
pub emulation_mode: ControllersEmulationMode,
#[schema(flag = "steamvr-restart")]
pub extra_openvr_props: Vec<OpenvrProperty>,
#[schema(strings(help = "List of OpenXR-syle paths"))]
pub button_mappings: Option<Vec<(String, Vec<ButtonBindingTarget>)>>,
pub button_mapping_config: AutomaticButtonMappingConfig,
pub hand_tracking: HandTrackingConfig,
#[schema(strings(
display_name = "Prediction",
help = r"Higher values make the controllers track smoother.
Technically, this is the time (counted in frames) between pose submitted to SteamVR and the corresponding virtual vsync happens.
Currently this cannot be reliably estimated automatically. The correct value should be 2 but 3 is default for smoother tracking at the cost of slight lag."
))]
#[schema(gui(slider(min = 1.0, max = 10.0, logarithmic)), suffix = "frames")]
pub steamvr_pipeline_frames: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = 0.0, max = 1.0, step = 0.01)), suffix = "m/s")]
pub linear_velocity_cutoff: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = 0.0, max = 100.0, step = 1.0)), suffix = "°/s")]
pub angular_velocity_cutoff: f32,
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = -0.5, max = 0.5, step = 0.001)), suffix = "m")]
pub left_controller_position_offset: [f32; 3],
#[schema(flag = "real-time")]
#[schema(gui(slider(min = -180.0, max = 180.0, step = 1.0)), suffix = "°")]
pub left_controller_rotation_offset: [f32; 3],
#[schema(flag = "real-time")]
// note: logarithmic scale seems to be glitchy for this control
#[schema(gui(slider(min = -0.5, max = 0.5, step = 0.001)), suffix = "m")]
pub left_hand_tracking_position_offset: [f32; 3],
#[schema(flag = "real-time")]
#[schema(gui(slider(min = -180.0, max = 180.0, step = 1.0)), suffix = "°")]
pub left_hand_tracking_rotation_offset: [f32; 3],
#[schema(flag = "real-time")]
pub haptics: Switch<HapticsConfig>,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy)]
pub enum PositionRecenteringMode {
Disabled,
@ -1276,7 +1314,6 @@ pub fn session_settings_default() -> SettingsDefault {
emulation_mode: HeadsetEmulationModeDefault {
Custom: HeadsetEmulationModeCustomDefault {
serial_number: "Unknown".into(),
props: default_custom_openvr_props.clone(),
},
variant: HeadsetEmulationModeDefaultVariant::Quest2,
},
@ -1303,11 +1340,51 @@ pub fn session_settings_default() -> SettingsDefault {
enabled: true,
content: ControllersConfigDefault {
gui_collapsed: false,
tracked: true,
emulation_mode: ControllersEmulationModeDefault {
Custom: ControllersEmulationModeCustomDefault {
serial_number: "ALVR Controller".into(),
button_set: VectorDefault {
gui_collapsed: false,
element: "/user/hand/left/input/a/click".into(),
content: vec![],
},
},
variant: ControllersEmulationModeDefaultVariant::Quest2Touch,
},
tracked: true,
extra_openvr_props: default_custom_openvr_props,
button_mappings: OptionalDefault {
set: false,
content: DictionaryDefault {
gui_collapsed: false,
key: "/user/hand/left/input/a/click".into(),
value: VectorDefault {
gui_collapsed: false,
element: ButtonBindingTargetDefault {
destination: "/user/hand/left/input/a/click".into(),
mapping_type: ButtonMappingTypeDefault {
HysteresisThreshold: HysteresisThresholdDefault {
value: 0.5,
deviation: 0.05,
},
BinaryToScalar: BinaryToScalarStatesDefault {
off: 0.0,
on: 1.0,
},
Remap: RangeDefault { min: 0.0, max: 1.0 },
variant: ButtonMappingTypeDefaultVariant::Passthrough,
},
binary_conditions: VectorDefault {
gui_collapsed: true,
element: "/user/hand/left/input/trigger/touch".into(),
content: vec![],
},
},
content: vec![],
},
content: vec![],
},
},
button_mapping_config: AutomaticButtonMappingConfigDefault {
gui_collapsed: true,
click_threshold: HysteresisThresholdDefault {