Files
wayvr/src/backend/overlay.rs
2024-02-20 20:54:06 +01:00

303 lines
9.0 KiB
Rust

use std::{
f32::consts::PI,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use vulkano::image::view::ImageView;
use crate::state::AppState;
use super::input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit};
static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
pub trait OverlayBackend: OverlayRenderer + InteractionHandler {}
pub struct OverlayState {
pub id: usize,
pub name: Arc<str>,
pub want_visible: bool,
pub show_hide: bool,
pub grabbable: bool,
pub interactable: bool,
pub recenter: bool,
pub dirty: bool,
pub alpha: f32,
pub transform: Affine3A,
pub saved_point: Option<Vec3A>,
pub spawn_scale: f32, // aka width
pub spawn_point: Vec3A,
pub spawn_rotation: Quat,
pub relative_to: RelativeTo,
pub primary_pointer: Option<usize>,
pub interaction_transform: Affine2,
}
impl Default for OverlayState {
fn default() -> Self {
OverlayState {
id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed),
name: Arc::from(""),
want_visible: false,
show_hide: false,
grabbable: false,
recenter: false,
interactable: false,
dirty: true,
alpha: 1.0,
relative_to: RelativeTo::None,
saved_point: None,
spawn_scale: 1.0,
spawn_point: Vec3A::NEG_Z,
spawn_rotation: Quat::IDENTITY,
transform: Affine3A::IDENTITY,
primary_pointer: None,
interaction_transform: Affine2::IDENTITY,
}
}
}
pub struct OverlayData<T>
where
T: Default,
{
pub state: OverlayState,
pub backend: Box<dyn OverlayBackend>,
pub primary_pointer: Option<usize>,
pub data: T,
}
impl<T> Default for OverlayData<T>
where
T: Default,
{
fn default() -> Self {
OverlayData {
state: Default::default(),
backend: Box::<SplitOverlayBackend>::default(),
primary_pointer: None,
data: Default::default(),
}
}
}
impl OverlayState {
pub fn parent_transform(&self, app: &AppState) -> Option<Affine3A> {
match self.relative_to {
RelativeTo::None => None,
RelativeTo::Head => Some(app.input_state.hmd),
RelativeTo::Hand(idx) => Some(app.input_state.pointers[idx].pose),
}
}
pub fn auto_movement(&mut self, app: &mut AppState) {
if let Some(parent) = self.parent_transform(app) {
let point = self.saved_point.unwrap_or(self.spawn_point);
self.transform = parent
* Affine3A::from_scale_rotation_translation(
Vec3::ONE * self.spawn_scale,
self.spawn_rotation
* Quat::from_rotation_x(f32::to_radians(-180.0))
* Quat::from_rotation_z(f32::to_radians(180.0)),
point.into(),
);
self.dirty = true;
}
}
pub fn reset(&mut self, app: &mut AppState, hard_reset: bool) {
let scale = if hard_reset {
self.saved_point = None;
self.spawn_scale
} else {
self.transform.x_axis.length()
};
let point = self.saved_point.unwrap_or(self.spawn_point);
let translation = app.input_state.hmd.transform_point3a(point);
self.transform = Affine3A::from_scale_rotation_translation(
Vec3::ONE * scale,
Quat::IDENTITY,
translation.into(),
);
self.realign(&app.input_state.hmd);
self.dirty = true;
}
pub fn realign(&mut self, hmd: &Affine3A) {
let to_hmd = hmd.translation - self.transform.translation;
let up_dir: Vec3A;
if hmd.x_axis.dot(Vec3A::Y).abs() > 0.2 {
// Snap upright
up_dir = hmd.y_axis;
} else {
let dot = to_hmd.normalize().dot(hmd.z_axis);
let z_dist = to_hmd.length();
let y_dist = (self.transform.translation.y - hmd.translation.y).abs();
let x_angle = (y_dist / z_dist).asin();
if dot < -f32::EPSILON {
// facing down
let up_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::Y;
up_dir = (up_point - self.transform.translation).normalize();
} else if dot > f32::EPSILON {
// facing up
let dn_point = hmd.translation + z_dist / x_angle.cos() * Vec3A::NEG_Y;
up_dir = (self.transform.translation - dn_point).normalize();
} else {
// perfectly upright
up_dir = Vec3A::Y;
}
}
let scale = self.transform.x_axis.length();
let col_z = (self.transform.translation - hmd.translation).normalize();
let col_y = up_dir;
let col_x = col_y.cross(col_z);
let col_y = col_z.cross(col_x).normalize();
let col_x = col_x.normalize();
let rot = Mat3A::from_quat(self.spawn_rotation)
* Mat3A::from_quat(Quat::from_axis_angle(Vec3::Y, PI));
self.transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot;
}
}
impl<T> OverlayData<T>
where
T: Default,
{
pub fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.state.reset(app, true);
self.backend.init(app)
}
pub fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.backend.render(app)
}
pub fn view(&mut self) -> Option<Arc<ImageView>> {
self.backend.view()
}
pub fn set_visible(&mut self, app: &mut AppState, visible: bool) -> anyhow::Result<()> {
let old_visible = self.state.want_visible;
self.state.want_visible = visible;
if visible != old_visible {
if visible {
self.backend.resume(app)?;
} else {
self.backend.pause(app)?;
}
}
Ok(())
}
}
pub trait OverlayRenderer {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn view(&mut self) -> Option<Arc<ImageView>>;
}
pub struct FallbackRenderer;
impl OverlayRenderer for FallbackRenderer {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn render(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
fn view(&mut self) -> Option<Arc<ImageView>> {
None
}
}
// Boilerplate and dummies
#[derive(Clone, Copy, Debug, Default)]
pub enum RelativeTo {
#[default]
None,
Head,
Hand(usize),
}
pub struct SplitOverlayBackend {
pub renderer: Box<dyn OverlayRenderer>,
pub interaction: Box<dyn InteractionHandler>,
}
impl Default for SplitOverlayBackend {
fn default() -> SplitOverlayBackend {
SplitOverlayBackend {
renderer: Box::new(FallbackRenderer),
interaction: Box::new(DummyInteractionHandler),
}
}
}
impl OverlayBackend for SplitOverlayBackend {}
impl OverlayRenderer for SplitOverlayBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.init(app)
}
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.pause(app)
}
fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.resume(app)
}
fn render(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.renderer.render(app)
}
fn view(&mut self) -> Option<Arc<ImageView>> {
self.renderer.view()
}
}
impl InteractionHandler for SplitOverlayBackend {
fn on_left(&mut self, app: &mut AppState, pointer: usize) {
self.interaction.on_left(app, pointer);
}
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
self.interaction.on_hover(app, hit)
}
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta: f32) {
self.interaction.on_scroll(app, hit, delta);
}
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
self.interaction.on_pointer(app, hit, pressed);
}
}
pub fn ui_transform(extent: &[u32; 2]) -> Affine2 {
let center = Vec2 { x: 0.5, y: 0.5 };
if extent[1] > extent[0] {
Affine2::from_cols(
Vec2::X * (extent[1] as f32 / extent[0] as f32),
Vec2::NEG_Y,
center,
)
} else {
Affine2::from_cols(
Vec2::X,
Vec2::NEG_Y * (extent[0] as f32 / extent[1] as f32),
center,
)
}
}