diff --git a/src/lib.rs b/src/lib.rs index 5cce1f4..f413249 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,3 +181,25 @@ pub struct ControllerState { pub button_touched: u64, pub axis: [ControllerAxis; 5], } + +pub mod button_id { + use super::sys; + pub const SYSTEM: sys::EVRButtonId = sys::EVRButtonId_k_EButton_System; + pub const APPLICATION_MENU: sys::EVRButtonId = sys::EVRButtonId_k_EButton_ApplicationMenu; + pub const GRIP: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Grip; + pub const DPAD_LEFT: sys::EVRButtonId = sys::EVRButtonId_k_EButton_DPad_Left; + pub const DPAD_UP: sys::EVRButtonId = sys::EVRButtonId_k_EButton_DPad_Up; + pub const DPAD_RIGHT: sys::EVRButtonId = sys::EVRButtonId_k_EButton_DPad_Right; + pub const DPAD_DOWN: sys::EVRButtonId = sys::EVRButtonId_k_EButton_DPad_Down; + pub const A: sys::EVRButtonId = sys::EVRButtonId_k_EButton_A; + pub const PROXIMITY_SENSOR: sys::EVRButtonId = sys::EVRButtonId_k_EButton_ProximitySensor; + pub const AXIS0: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Axis0; + pub const AXIS1: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Axis1; + pub const AXIS2: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Axis2; + pub const AXIS3: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Axis3; + pub const AXIS4: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Axis4; + pub const STEAM_VR_TOUCHPAD: sys::EVRButtonId = sys::EVRButtonId_k_EButton_SteamVR_Touchpad; + pub const STEAM_VR_TRIGGER: sys::EVRButtonId = sys::EVRButtonId_k_EButton_SteamVR_Trigger; + pub const DASHBOARD_BACK: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Dashboard_Back; + pub const MAX: sys::EVRButtonId = sys::EVRButtonId_k_EButton_Max; +} diff --git a/src/system/event.rs b/src/system/event.rs index 253cf96..8149cfc 100644 --- a/src/system/event.rs +++ b/src/system/event.rs @@ -271,6 +271,7 @@ pub enum Event { Notification_BeginInteraction, Notification_Destroyed, + /// The application has been asked to quit Quit(Process), ProcessQuit(Process), QuitAborted_UserPrompt(Process), diff --git a/src/system/mod.rs b/src/system/mod.rs index b0acfab..c40111a 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -1,8 +1,9 @@ //! The `System` interface provides access to display configuration information, tracking data, controller state, //! events, and device properties. It is the main interface of OpenVR. -use std::mem; +use std::{mem, slice, ptr}; use std::ffi::CString; +use std::marker::PhantomData; use openvr_sys as sys; @@ -210,6 +211,25 @@ impl<'a> System<'a> { } } + /// Returns the hidden area mesh for the current HMD. + /// + /// The pixels covered by this mesh will never be seen by the user after the lens distortion is applied based on + /// visibility to the panels. If this HMD does not have a hidden area mesh, None is returned. This mesh is meant to + /// be rendered into the stencil buffer (or into the depth buffer setting nearz) before rendering each eye's view. + /// This will improve performance by letting the GPU early-reject pixels the user will never see before running the + /// pixel shader. + /// + /// NOTE: Render this mesh with backface culling disabled since the winding order of the vertices can + /// be different per-HMD or per-eye. + /// + /// Passing `HiddenAreaMeshType::Inverse` will produce the visible area mesh that is commonly used in place of + /// full-screen quads. The visible area mesh covers all of the pixels the hidden area mesh does not cover. + // TODO: Handle line loops with a separate method and return type, since HiddenAreaMesh assumes triangles. + pub fn hidden_area_mesh(&self, eye: Eye, ty: HiddenAreaMeshType) -> Option { + let mesh = unsafe { self.0.GetHiddenAreaMesh.unwrap()(eye as sys::EVREye, ty as sys::EHiddenAreaMeshType) }; + if mesh.pVertexData == ptr::null_mut() { None } else { Some(HiddenAreaMesh { mesh, _phantom: PhantomData }) } + } + /// Looks up the current input state of a controller. /// /// Returns None if the device is not a controller, or if the user is currently in the system menu. @@ -226,6 +246,46 @@ impl<'a> System<'a> { } } } + + pub fn controller_state_with_pose(&self, origin: TrackingUniverseOrigin, device: TrackedDeviceIndex) -> Option<(ControllerState, TrackedDevicePose)> { + unsafe { + let mut state = mem::uninitialized(); + let mut pose = mem::uninitialized(); + if self.0.GetControllerStateWithPose.unwrap()( + origin as sys::ETrackingUniverseOrigin, + device, &mut state as *mut _ as *mut _, mem::size_of_val(&state) as u32, + &mut pose) { + Some((state, pose.into())) + } else { + None + } + } + } + + /// Trigger a single haptic pulse on a controller. + /// + /// After this call the application may not trigger another haptic pulse on this controller and axis combination for + /// 5ms. + /// + /// Vive controller haptics respond to axis 0. OpenVR seems to reject durations longer than 3999us. + pub fn trigger_haptic_pulse(&self, device: TrackedDeviceIndex, axis: u32, microseconds: u16) { + unsafe { self.0.TriggerHapticPulse.unwrap()(device, axis, microseconds) } + } + + /// Call this to acknowledge to the system that `Event::Quit` has been received and that the process is exiting. + /// + /// This extends the timeout until the process is killed. + pub fn acknowledge_quit_exiting(&self) { + unsafe { self.0.AcknowledgeQuit_Exiting.unwrap()(); } + } + + /// Call this to tell the system that the user is being prompted to save data. + /// + /// This halts the timeout and dismisses the dashboard (if it was up). Applications should be sure to actually + /// prompt the user to save and then exit afterward, otherwise the user will be left in a confusing state. + pub fn acknowledge_quit_user_prompt(&self) { + unsafe { self.0.AcknowledgeQuit_Exiting.unwrap()(); } + } } /// Values represent the tangents of the half-angles from the center view axis @@ -300,3 +360,30 @@ impl fmt::Display for TrackedPropertyError { f.pad(::std::error::Error::description(self)) } } + +pub enum HiddenAreaMeshType { + /// The mesh that covers pixels which cannot be seen by the wearer of the HMD for optical reasons. + Standard = sys::EHiddenAreaMeshType_k_eHiddenAreaMesh_Standard as isize, + /// The inverse of `Standard`, useful for doing full-screen render passes such as postprocessing. + Inverse = sys::EHiddenAreaMeshType_k_eHiddenAreaMesh_Inverse as isize, +} + +impl Default for HiddenAreaMeshType { + fn default() -> Self { HiddenAreaMeshType::Standard } +} + +/// A triangle mesh containing geometry determined by `HiddenAreaMeshType`. +/// +/// Render this mesh with backface culling disabled since the winding order of the vertices can be different per-HMD or +/// per-eye. +pub struct HiddenAreaMesh<'a> { + mesh: sys::HiddenAreaMesh_t, + _phantom: PhantomData<&'a [[f32; 2]]>, +} + +impl<'a> ::std::ops::Deref for HiddenAreaMesh<'a> { + type Target = [[f32; 2]]; + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(&(*self.mesh.pVertexData).v, self.mesh.unTriangleCount as usize * 3) } + } +} diff --git a/src/tracking.rs b/src/tracking.rs index 05ab0ad..9daa006 100644 --- a/src/tracking.rs +++ b/src/tracking.rs @@ -30,6 +30,10 @@ impl TrackedDevicePose { pub fn device_is_connected(&self) -> bool { self.0.bDeviceIsConnected } } +impl From for TrackedDevicePose { + fn from(x: sys::TrackedDevicePose_t) -> Self { TrackedDevicePose(x) } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum TrackingResult { Uninitialized = sys::ETrackingResult_TrackingResult_Uninitialized as isize,