openxr: add triple-click option
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
use std::time::{Duration, Instant};
|
use std::{
|
||||||
|
array::from_fn,
|
||||||
|
mem::transmute,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use glam::{bool, Affine3A, Quat, Vec3};
|
use glam::{bool, Affine3A, Quat, Vec3};
|
||||||
use openxr as xr;
|
use openxr::{self as xr, Quaternionf, Vector3f};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -13,7 +17,12 @@ use crate::{
|
|||||||
use super::XrState;
|
use super::XrState;
|
||||||
|
|
||||||
type XrSession = xr::Session<xr::Vulkan>;
|
type XrSession = xr::Session<xr::Vulkan>;
|
||||||
static DOUBLE_CLICK_TIME: Duration = Duration::from_millis(500);
|
|
||||||
|
static CLICK_TIMES: [Duration; 3] = [
|
||||||
|
Duration::ZERO,
|
||||||
|
Duration::from_millis(500),
|
||||||
|
Duration::from_millis(750),
|
||||||
|
];
|
||||||
|
|
||||||
pub(super) struct OpenXrAction {}
|
pub(super) struct OpenXrAction {}
|
||||||
|
|
||||||
@@ -27,47 +36,91 @@ pub(super) struct OpenXrHand {
|
|||||||
space: xr::Space,
|
space: xr::Space,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CustomClickAction {
|
pub struct MultiClickHandler<const COUNT: usize> {
|
||||||
|
name: String,
|
||||||
action_f32: xr::Action<f32>,
|
action_f32: xr::Action<f32>,
|
||||||
action_bool: xr::Action<bool>,
|
action_bool: xr::Action<bool>,
|
||||||
action_f32_double: xr::Action<f32>,
|
previous: [Instant; COUNT],
|
||||||
action_bool_double: xr::Action<bool>,
|
held_active: bool,
|
||||||
last_click: Option<Instant>,
|
held_inactive: bool,
|
||||||
held: bool,
|
}
|
||||||
|
|
||||||
|
impl<const COUNT: usize> MultiClickHandler<COUNT> {
|
||||||
|
fn new(action_set: &xr::ActionSet, action_name: &str, side: &str) -> anyhow::Result<Self> {
|
||||||
|
let name = format!("{}_{}-{}", side, COUNT, action_name);
|
||||||
|
let name_f32 = format!("{}_value", &name);
|
||||||
|
|
||||||
|
let action_bool = action_set.create_action::<bool>(&name, &name, &[])?;
|
||||||
|
let action_f32 = action_set.create_action::<f32>(&name_f32, &name_f32, &[])?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
action_f32,
|
||||||
|
action_bool,
|
||||||
|
previous: from_fn(|_| Instant::now()),
|
||||||
|
held_active: false,
|
||||||
|
held_inactive: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn check<G>(&mut self, session: &xr::Session<G>, threshold: f32) -> anyhow::Result<bool> {
|
||||||
|
let res = self.action_bool.state(session, xr::Path::NULL)?;
|
||||||
|
let mut state = res.is_active && res.current_state;
|
||||||
|
|
||||||
|
if !state {
|
||||||
|
let res = self.action_f32.state(session, xr::Path::NULL)?;
|
||||||
|
state = res.is_active && res.current_state > threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state {
|
||||||
|
self.held_active = false;
|
||||||
|
self.held_inactive = false;
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.held_active {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.held_inactive {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let passed = self
|
||||||
|
.previous
|
||||||
|
.iter()
|
||||||
|
.all(|instant| instant.elapsed() < CLICK_TIMES[COUNT]);
|
||||||
|
|
||||||
|
if passed {
|
||||||
|
log::trace!("{}: passed", self.name);
|
||||||
|
self.held_active = true;
|
||||||
|
self.held_inactive = false;
|
||||||
|
} else if COUNT > 0 {
|
||||||
|
log::trace!("{}: rotate", self.name);
|
||||||
|
self.previous.rotate_right(1);
|
||||||
|
self.previous[0] = Instant::now();
|
||||||
|
self.held_inactive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(passed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CustomClickAction {
|
||||||
|
single: MultiClickHandler<0>,
|
||||||
|
double: MultiClickHandler<1>,
|
||||||
|
triple: MultiClickHandler<2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomClickAction {
|
impl CustomClickAction {
|
||||||
pub fn new(action_set: &xr::ActionSet, name: &str, side: &str) -> anyhow::Result<Self> {
|
pub fn new(action_set: &xr::ActionSet, name: &str, side: &str) -> anyhow::Result<Self> {
|
||||||
let action_f32 = action_set.create_action::<f32>(
|
let single = MultiClickHandler::new(action_set, name, side)?;
|
||||||
&format!("{}_{}_value", side, name),
|
let double = MultiClickHandler::new(action_set, name, side)?;
|
||||||
&format!("{} hand {} value", side, name),
|
let triple = MultiClickHandler::new(action_set, name, side)?;
|
||||||
&[],
|
|
||||||
)?;
|
|
||||||
let action_f32_double = action_set.create_action::<f32>(
|
|
||||||
&format!("{}_{}_value_double", side, name),
|
|
||||||
&format!("{} hand {} value double", side, name),
|
|
||||||
&[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let action_bool = action_set.create_action::<bool>(
|
|
||||||
&format!("{}_{}", side, name),
|
|
||||||
&format!("{} hand {}", side, name),
|
|
||||||
&[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let action_bool_double = action_set.create_action::<bool>(
|
|
||||||
&format!("{}_{}_double", side, name),
|
|
||||||
&format!("{} hand {} double", side, name),
|
|
||||||
&[],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
action_f32,
|
single,
|
||||||
action_f32_double,
|
double,
|
||||||
action_bool,
|
triple,
|
||||||
action_bool_double,
|
|
||||||
last_click: None,
|
|
||||||
held: false,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn state(
|
pub fn state(
|
||||||
@@ -76,61 +129,15 @@ impl CustomClickAction {
|
|||||||
state: &XrState,
|
state: &XrState,
|
||||||
session: &AppSession,
|
session: &AppSession,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
let res = self.action_bool.state(&state.session, xr::Path::NULL)?;
|
|
||||||
if res.is_active && res.current_state {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = self
|
|
||||||
.action_bool_double
|
|
||||||
.state(&state.session, xr::Path::NULL)?;
|
|
||||||
if res.is_active
|
|
||||||
&& ((before && res.current_state) || self.check_double_click(res.current_state))
|
|
||||||
{
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let threshold = if before {
|
let threshold = if before {
|
||||||
session.config.xr_click_sensitivity_release
|
session.config.xr_click_sensitivity_release
|
||||||
} else {
|
} else {
|
||||||
session.config.xr_click_sensitivity
|
session.config.xr_click_sensitivity
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = self.action_f32.state(&state.session, xr::Path::NULL)?;
|
Ok(self.single.check(&state.session, threshold)?
|
||||||
if res.is_active && res.current_state > threshold {
|
|| self.double.check(&state.session, threshold)?
|
||||||
return Ok(true);
|
|| self.triple.check(&state.session, threshold)?)
|
||||||
}
|
|
||||||
|
|
||||||
let res = self.action_f32.state(&state.session, xr::Path::NULL)?;
|
|
||||||
if res.is_active
|
|
||||||
&& ((before && res.current_state > threshold)
|
|
||||||
|| self.check_double_click(res.current_state > threshold))
|
|
||||||
{
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// submit a click. returns true if it should count as a double click
|
|
||||||
fn check_double_click(&mut self, state: bool) -> bool {
|
|
||||||
if !state {
|
|
||||||
self.held = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.held {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
let double_click = match self.last_click {
|
|
||||||
Some(last_click) => now - last_click < DOUBLE_CLICK_TIME,
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
self.last_click = if double_click { None } else { Some(now) };
|
|
||||||
self.held = true;
|
|
||||||
double_click
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +226,12 @@ impl OpenXrHand {
|
|||||||
.location_flags
|
.location_flags
|
||||||
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
|
.contains(xr::SpaceLocationFlags::ORIENTATION_VALID)
|
||||||
{
|
{
|
||||||
let quat = unsafe { std::mem::transmute::<_, Quat>(location.pose.orientation) };
|
let (quat, pos) = unsafe {
|
||||||
let pos = unsafe { std::mem::transmute::<_, Vec3>(location.pose.position) };
|
(
|
||||||
|
transmute::<Quaternionf, Quat>(location.pose.orientation),
|
||||||
|
transmute::<Vector3f, Vec3>(location.pose.position),
|
||||||
|
)
|
||||||
|
};
|
||||||
pointer.pose = Affine3A::from_rotation_translation(quat, pos);
|
pointer.pose = Affine3A::from_rotation_translation(quat, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,31 +364,39 @@ macro_rules! add_custom {
|
|||||||
if let Some(action) = $action.as_ref() {
|
if let Some(action) = $action.as_ref() {
|
||||||
if let Some(p) = to_path(&action.left, $instance) {
|
if let Some(p) = to_path(&action.left, $instance) {
|
||||||
if is_bool(&action.left) {
|
if is_bool(&action.left) {
|
||||||
if action.double_click.unwrap_or(false) {
|
if action.triple_click.unwrap_or(false) {
|
||||||
$bindings.push(xr::Binding::new(&$left.action_bool_double, p));
|
$bindings.push(xr::Binding::new(&$right.triple.action_bool, p));
|
||||||
|
} else if action.double_click.unwrap_or(false) {
|
||||||
|
$bindings.push(xr::Binding::new(&$left.double.action_bool, p));
|
||||||
} else {
|
} else {
|
||||||
$bindings.push(xr::Binding::new(&$left.action_bool, p));
|
$bindings.push(xr::Binding::new(&$left.single.action_bool, p));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if action.double_click.unwrap_or(false) {
|
if action.triple_click.unwrap_or(false) {
|
||||||
$bindings.push(xr::Binding::new(&$left.action_f32_double, p));
|
$bindings.push(xr::Binding::new(&$right.triple.action_f32, p));
|
||||||
|
} else if action.double_click.unwrap_or(false) {
|
||||||
|
$bindings.push(xr::Binding::new(&$left.double.action_f32, p));
|
||||||
} else {
|
} else {
|
||||||
$bindings.push(xr::Binding::new(&$left.action_f32, p));
|
$bindings.push(xr::Binding::new(&$left.single.action_f32, p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(p) = to_path(&action.right, $instance) {
|
if let Some(p) = to_path(&action.right, $instance) {
|
||||||
if is_bool(&action.right) {
|
if is_bool(&action.right) {
|
||||||
if action.double_click.unwrap_or(false) {
|
if action.triple_click.unwrap_or(false) {
|
||||||
$bindings.push(xr::Binding::new(&$right.action_bool_double, p));
|
$bindings.push(xr::Binding::new(&$right.triple.action_bool, p));
|
||||||
|
} else if action.double_click.unwrap_or(false) {
|
||||||
|
$bindings.push(xr::Binding::new(&$right.double.action_bool, p));
|
||||||
} else {
|
} else {
|
||||||
$bindings.push(xr::Binding::new(&$right.action_bool, p));
|
$bindings.push(xr::Binding::new(&$right.single.action_bool, p));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if action.double_click.unwrap_or(false) {
|
if action.triple_click.unwrap_or(false) {
|
||||||
$bindings.push(xr::Binding::new(&$right.action_f32_double, p));
|
$bindings.push(xr::Binding::new(&$right.triple.action_f32, p));
|
||||||
|
} else if action.double_click.unwrap_or(false) {
|
||||||
|
$bindings.push(xr::Binding::new(&$right.double.action_f32, p));
|
||||||
} else {
|
} else {
|
||||||
$bindings.push(xr::Binding::new(&$right.action_f32, p));
|
$bindings.push(xr::Binding::new(&$right.single.action_f32, p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,6 +532,7 @@ struct OpenXrActionConfAction {
|
|||||||
right: Option<String>,
|
right: Option<String>,
|
||||||
threshold: Option<[f32; 2]>,
|
threshold: Option<[f32; 2]>,
|
||||||
double_click: Option<bool>,
|
double_click: Option<bool>,
|
||||||
|
triple_click: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use std::{
|
|||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
usize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
|
use vulkano::{command_buffer::CommandBufferUsage, format::Format, image::view::ImageView};
|
||||||
|
|||||||
Reference in New Issue
Block a user