diff --git a/Cargo.toml b/Cargo.toml index 3835e21..7f6c888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,19 @@ edition = "2021" anyhow = "1.0.75" ash = "0.37.3" bevy = { git = "https://github.com/awtterpip/bevy", default-features = false, features = ["bevy_render"] } -openxr = { version = "0.17.1" } +openxr = { version = "0.17.1", features = ["mint"] } +mint = "0.5.9" wgpu = "0.16.0" wgpu-core = { version = "0.16.0", features = ["vulkan"] } wgpu-hal = "0.16.0" [dev-dependencies] bevy = { git = "https://github.com/awtterpip/bevy" } +color-eyre = "0.6.2" [[example]] name = "xr" path = "examples/xr.rs" + +[profile.release] +debug = true \ No newline at end of file diff --git a/examples/xr.rs b/examples/xr.rs index 1bac15f..230103b 100644 --- a/examples/xr.rs +++ b/examples/xr.rs @@ -1,27 +1,44 @@ -//! A simple 3D scene with light shining over a cube sitting on a plane. -use bevy_openxr::{DefaultXrPlugins, LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE}; +use bevy::core_pipeline::core_3d; +use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy::ecs::prelude::{Bundle, Component, ReflectComponent}; + +use bevy::math::Mat4; +use bevy::prelude::Camera3d; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::view::ColorGrading; +use bevy::render::{ + camera::{Camera, CameraProjection, CameraRenderGraph}, + primitives::Frustum, + view::VisibleEntities, +}; +use bevy::transform::components::{GlobalTransform, Transform}; +// mostly copied from https://github.com/blaind/bevy_openxr/tree/main/crates/bevy_openxr/src/render_graph/camera +use openxr::Fovf; + +use bevy::render::camera::CameraProjectionPlugin; +use bevy::render::view::{update_frusta, VisibilitySystems}; +use bevy::transform::TransformSystem; use bevy::{prelude::*, render::camera::RenderTarget}; -use bevy::prelude::Component; -use bevy::render::camera::Viewport; use bevy_openxr::input::XrInput; -use bevy_openxr::resources::{XrInstance, XrSession, XrViews}; +use bevy_openxr::resources::{XrFrameState, XrSession, XrViews}; +use bevy_openxr::xr_input::controllers::XrControllerType; +use bevy_openxr::xr_input::oculus_touch::OculusController; +use bevy_openxr::xr_input::{OpenXrInput, QuatConv, Vec3Conv}; +use bevy_openxr::{DefaultXrPlugins, LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE}; +use openxr::ActiveActionSet; fn main() { + color_eyre::install().unwrap(); + + info!("Running `openxr-6dof` skill"); App::new() .add_plugins(DefaultXrPlugins) + .add_plugins(OpenXrInput::new(XrControllerType::OculusTouch)) .add_systems(Startup, setup) - .add_systems(Update, head_movement) + .add_systems(Update, hands) .run(); } -#[derive(Component)] -enum CameraType { - Left, - Right, - Middle, -} - - /// set up a simple 3D scene fn setup( mut commands: Commands, @@ -36,7 +53,7 @@ fn setup( }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() @@ -55,116 +72,37 @@ fn setup( commands.spawn((Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() - }, CameraType::Middle)); - - // let viewport = Viewport{ - // physical_position: Default::default(), - // physical_size: UVec2::splat(2000), - // depth: 0.0..1.0, - // }; - - commands.spawn((Camera3dBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - order: -1, - target: RenderTarget::TextureView(LEFT_XR_TEXTURE_HANDLE), - viewport: None, - ..default() - }, - ..default() - }, CameraType::Left)); - commands.spawn((Camera3dBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - order: -1, - target: RenderTarget::TextureView(RIGHT_XR_TEXTURE_HANDLE), - viewport: None, - ..default() - }, - ..default() - }, CameraType::Right)); -} -fn head_movement(views: ResMut, mut query: Query<(&mut Transform, &Camera, &CameraType)>) { - let views = views.lock().unwrap(); - let mut f = || -> Option<()> { - let midpoint = (views.get(0)?.pose.position.to_vec3() - + views.get(1)?.pose.position.to_vec3()) - / 2.; - for (mut t, _, camera_type) in query.iter_mut() { - match camera_type { - CameraType::Left => { - t.translation = views.get(0)?.pose.position.to_vec3() - }, - CameraType::Right => { - t.translation = views.get(1)?.pose.position.to_vec3() - }, - CameraType::Middle => { - t.translation = midpoint; - }, - } - } - let left_rot = views.get(0).unwrap().pose.orientation.to_quat(); - let right_rot = views.get(1).unwrap().pose.orientation.to_quat(); - let mid_rot = if left_rot.dot(right_rot) >= 0. { - left_rot.slerp(right_rot, 0.5) - } else { - right_rot.slerp(left_rot, 0.5) - }; - for (mut t, _, camera_type) in query.iter_mut() { - match camera_type { - CameraType::Left => { - t.rotation = left_rot - }, - CameraType::Right => { - t.rotation = right_rot - }, - CameraType::Middle => { - t.rotation = mid_rot; - }, - } - } - - - // for (mut projection, mut transform, eye) in cam.iter_mut() { - // let view_idx = match eye { - // Eye::Left => 0, - // Eye::Right => 1, - // }; - // let view = views.get(view_idx).unwrap(); - // - // projection.fov = view.fov; - // - // transform.rotation = view.pose.orientation.to_quat(); - // let pos = view.pose.position; - // transform.translation = pos.to_vec3(); - // } - - Some(()) - }; - f(); -} -pub trait Vec3Conv { - fn to_vec3(&self) -> Vec3; + },)); } -impl Vec3Conv for openxr::Vector3f { - fn to_vec3(&self) -> Vec3 { - Vec3::new(self.x, self.y, self.z) - } -} -pub trait QuatConv { - fn to_quat(&self) -> Quat; -} +fn hands( + mut gizmos: Gizmos, + oculus_controller: Res, + frame_state: Res, + xr_input: Res, +) { + let frame_state = *frame_state.lock().unwrap(); -impl QuatConv for openxr::Quaternionf { - fn to_quat(&self) -> Quat { - Quat::from_xyzw(self.x, self.y, self.z, self.w) - } + let right_controller = oculus_controller + .grip_space + .right + .relate(&**&xr_input.stage, frame_state.predicted_display_time) + .unwrap(); + let left_controller = oculus_controller + .grip_space + .left + .relate(&**&xr_input.stage, frame_state.predicted_display_time) + .unwrap(); + gizmos.rect( + right_controller.0.pose.position.to_vec3(), + right_controller.0.pose.orientation.to_quat(), + Vec2::new(0.05, 0.2), + Color::YELLOW_GREEN, + ); + gizmos.rect( + left_controller.0.pose.position.to_vec3(), + left_controller.0.pose.orientation.to_quat(), + Vec2::new(0.05, 0.2), + Color::YELLOW_GREEN, + ); } - -// fn head_movement(right_camera: Query<(&mut Transform, &RightCamera), Without>, left_camera: Query<(&mut Transform, &LeftCamera), Without>, xr_input: Res, instance: Res, session: Res) { -// -// // let stage = -// // session.create_reference_space(openxr::ReferenceSpaceType::VIEW, openxr::Posef::IDENTITY).unwrap(); -// // eprintln!("a: {:#?}", stage.locate(&xr_input.stage, xr_input.action_set.).unwrap().pose); -// } \ No newline at end of file diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 5d1deed..23c5b3e 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -1,12 +1,34 @@ mod vulkan; -use bevy::render::renderer::{RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter}; +use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use bevy::window::RawHandleWrapper; use wgpu::Instance; use crate::input::XrInput; -use crate::resources::{XrInstance, XrSession, XrEnvironmentBlendMode, XrSessionRunning, XrFrameWaiter, XrSwapchain, XrViews, XrFrameState}; +use crate::resources::{ + XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance, XrSession, XrSessionRunning, + XrSwapchain, XrViews, XrResolution, XrFormat, +}; -pub fn initialize_xr_graphics(window: Option) -> anyhow::Result<(RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter, Instance, XrInstance, XrSession, XrEnvironmentBlendMode, XrSessionRunning, XrFrameWaiter, XrSwapchain, XrInput, XrViews, XrFrameState)>{ +pub fn initialize_xr_graphics( + window: Option, +) -> anyhow::Result<( + RenderDevice, + RenderQueue, + RenderAdapterInfo, + RenderAdapter, + Instance, + XrInstance, + XrSession, + XrEnvironmentBlendMode, + XrResolution, + XrFormat, + XrSessionRunning, + XrFrameWaiter, + XrSwapchain, + XrInput, + XrViews, + XrFrameState, +)> { vulkan::initialize_xr_graphics(window) -} \ No newline at end of file +} diff --git a/src/graphics/vulkan.rs b/src/graphics/vulkan.rs index 9b77667..f247e84 100644 --- a/src/graphics/vulkan.rs +++ b/src/graphics/vulkan.rs @@ -1,6 +1,6 @@ use std::ffi::{c_void, CString}; -use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex}; use anyhow::Context; use ash::vk::{self, Handle}; @@ -13,11 +13,14 @@ use wgpu::{Instance, Texture}; use crate::input::XrInput; use crate::resources::{ - XrEnvironmentBlendMode, XrFrameWaiter, XrInstance, XrSession, XrSessionRunning, XrSwapchain, Swapchain, SwapchainInner, XrViews, XrFrameState, + Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFrameState, XrFrameWaiter, XrInstance, + XrSession, XrSessionRunning, XrSwapchain, XrViews, XrResolution, XrFormat, }; use crate::VIEW_TYPE; -pub fn initialize_xr_graphics(window: Option) -> anyhow::Result<( +pub fn initialize_xr_graphics( + window: Option, +) -> anyhow::Result<( RenderDevice, RenderQueue, RenderAdapterInfo, @@ -26,6 +29,8 @@ pub fn initialize_xr_graphics(window: Option) -> anyhow::Resul XrInstance, XrSession, XrEnvironmentBlendMode, + XrResolution, + XrFormat, XrSessionRunning, XrFrameWaiter, XrSwapchain, @@ -277,11 +282,14 @@ pub fn initialize_xr_graphics(window: Option) -> anyhow::Resul .expect("Failed to create wgpu surface") }); let swapchain_format = surface - .as_ref() - .map(|surface| surface.get_capabilities(&wgpu_adapter).formats[0]) - .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb); + .as_ref() + .map(|surface| surface.get_capabilities(&wgpu_adapter).formats[0]) + .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb); - let resolution = uvec2(views[0].recommended_image_rect_width, views[0].recommended_image_rect_height); + let resolution = uvec2( + views[0].recommended_image_rect_width, + views[0].recommended_image_rect_height, + ); let handle = session .create_swapchain(&xr::SwapchainCreateInfo { @@ -303,54 +311,54 @@ pub fn initialize_xr_graphics(window: Option) -> anyhow::Resul let images = handle.enumerate_images().unwrap(); let buffers = images - .into_iter() - .map(|color_image| { - let color_image = vk::Image::from_raw(color_image); - let wgpu_hal_texture = unsafe { - ::Device::texture_from_raw( - color_image, - &wgpu_hal::TextureDescriptor { - label: Some("VR Swapchain"), - size: wgpu::Extent3d { - width: resolution.x, - height: resolution.y, - depth_or_array_layers: 2, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: swapchain_format, - usage: wgpu_hal::TextureUses::COLOR_TARGET - | wgpu_hal::TextureUses::COPY_DST, - memory_flags: wgpu_hal::MemoryFlags::empty(), - view_formats: vec![], + .into_iter() + .map(|color_image| { + let color_image = vk::Image::from_raw(color_image); + let wgpu_hal_texture = unsafe { + ::Device::texture_from_raw( + color_image, + &wgpu_hal::TextureDescriptor { + label: Some("VR Swapchain"), + size: wgpu::Extent3d { + width: resolution.x, + height: resolution.y, + depth_or_array_layers: 2, }, - None, - ) - }; - let texture = unsafe { - wgpu_device.create_texture_from_hal::( - wgpu_hal_texture, - &wgpu::TextureDescriptor { - label: Some("VR Swapchain"), - size: wgpu::Extent3d { - width: resolution.x, - height: resolution.y, - depth_or_array_layers: 2, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: swapchain_format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST, - view_formats: &[], + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: swapchain_format, + usage: wgpu_hal::TextureUses::COLOR_TARGET + | wgpu_hal::TextureUses::COPY_DST, + memory_flags: wgpu_hal::MemoryFlags::empty(), + view_formats: vec![], + }, + None, + ) + }; + let texture = unsafe { + wgpu_device.create_texture_from_hal::( + wgpu_hal_texture, + &wgpu::TextureDescriptor { + label: Some("VR Swapchain"), + size: wgpu::Extent3d { + width: resolution.x, + height: resolution.y, + depth_or_array_layers: 2, }, - ) - }; - texture - }) - .collect(); + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: swapchain_format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }, + ) + }; + texture + }) + .collect(); Ok(( wgpu_device.into(), @@ -361,19 +369,24 @@ pub fn initialize_xr_graphics(window: Option) -> anyhow::Resul xr_instance.clone().into(), session.clone().into_any_graphics().into(), blend_mode.into(), + resolution.into(), + swapchain_format.into(), AtomicBool::new(false).into(), Mutex::new(frame_wait).into(), - Mutex::new(Swapchain::Vulkan(SwapchainInner { - stream: frame_stream, - handle, - resolution, - format: swapchain_format, + Swapchain::Vulkan(SwapchainInner { + stream: Mutex::new(frame_stream), + handle: Mutex::new(handle), buffers, - image_index: 0, - })).into(), + image_index: Mutex::new(0), + }) + .into(), XrInput::new(xr_instance, session.into_any_graphics())?, Mutex::default().into(), - Mutex::default().into(), + Mutex::new(xr::FrameState { + predicted_display_time: xr::Time::from_nanos(1), + predicted_display_period: xr::Duration::from_nanos(1), + should_render: true, + }).into(), )) } @@ -441,4 +454,4 @@ fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> vk::Format { wgpu::TextureFormat::Astc { .. } => panic!("please god kill me now"), _ => panic!("fuck no") } -} \ No newline at end of file +} diff --git a/src/input.rs b/src/input.rs index 1a1564a..54f43bb 100644 --- a/src/input.rs +++ b/src/input.rs @@ -7,57 +7,58 @@ type XrPose = (Vec3, Quat); #[derive(Clone, Resource)] pub struct XrInput { - pub action_set: xr::ActionSet, - pub hand_pose: xr::Action, - pub right_space: Arc, - pub left_space: Arc, + //pub action_set: xr::ActionSet, + //pub hand_pose: xr::Action, + //pub right_space: Arc, + //pub left_space: Arc, pub stage: Arc, } impl XrInput { - pub fn new( - instance: xr::Instance, - session: xr::Session, - ) -> xr::Result { - let action_set = instance.create_action_set("input", "input pose information", 0)?; - let left_hand_subaction_path = instance.string_to_path("/user/hand/left").unwrap(); - let right_hand_subaction_path = instance.string_to_path("/user/hand/right").unwrap(); - let left_hand_grip_pose_path = instance - .string_to_path("/user/hand/left/input/grip/pose") - .unwrap(); - let right_hand_grip_pose_path = instance - .string_to_path("/user/hand/right/input/grip/pose") - .unwrap(); - let hand_pose = - action_set.create_action::("hand_pose", "Hand Pose", &[left_hand_subaction_path, right_hand_subaction_path])?; - /* let left_action = - action_set.create_action::("left_hand", "Left Hand Controller", &[])?;*/ - instance.suggest_interaction_profile_bindings( - instance.string_to_path("/interaction_profiles/khr/simple_controller")?, - &[ - xr::Binding::new( - &hand_pose, - right_hand_grip_pose_path, - ), - xr::Binding::new( - &hand_pose, - left_hand_grip_pose_path, - ), - ], - )?; - - let right_space = - hand_pose.create_space(session.clone(), right_hand_subaction_path, xr::Posef::IDENTITY)?; - let left_space = - hand_pose.create_space(session.clone(), left_hand_subaction_path, xr::Posef::IDENTITY)?; + pub fn new(instance: xr::Instance, session: xr::Session) -> xr::Result { + // let action_set = instance.create_action_set("input", "input pose information", 0)?; + // let left_hand_subaction_path = instance.string_to_path("/user/hand/left").unwrap(); + // let right_hand_subaction_path = instance.string_to_path("/user/hand/right").unwrap(); + // let left_hand_grip_pose_path = instance + // .string_to_path("/user/hand/left/input/grip/pose") + // .unwrap(); + // let right_hand_grip_pose_path = instance + // .string_to_path("/user/hand/right/input/grip/pose") + // .unwrap(); + // let hand_pose = action_set.create_action::( + // "hand_pose", + // "Hand Pose", + // &[left_hand_subaction_path, right_hand_subaction_path], + // )?; + // /* let left_action = + // action_set.create_action::("left_hand", "Left Hand Controller", &[])?;*/ + // instance.suggest_interaction_profile_bindings( + // instance.string_to_path("/interaction_profiles/khr/simple_controller")?, + // &[ + // xr::Binding::new(&hand_pose, right_hand_grip_pose_path), + // xr::Binding::new(&hand_pose, left_hand_grip_pose_path), + // ], + // )?; + // + // let right_space = hand_pose.create_space( + // session.clone(), + // right_hand_subaction_path, + // xr::Posef::IDENTITY, + // )?; + // let left_space = hand_pose.create_space( + // session.clone(), + // left_hand_subaction_path, + // xr::Posef::IDENTITY, + // )?; let stage = session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?; - session.attach_action_sets(&[&action_set])?; + //session.attach_action_sets(&[&action_set])?; + //session.attach_action_sets(&[])?; Ok(Self { - action_set, - hand_pose, - right_space: Arc::new(right_space), - left_space: Arc::new(left_space), + //action_set, + //hand_pose, + // right_space: Arc::new(right_space), + // left_space: Arc::new(left_space), stage: Arc::new(stage), }) } diff --git a/src/lib.rs b/src/lib.rs index 3c9d4b0..38b0495 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,27 @@ +mod graphics; pub mod input; pub mod resource_macros; pub mod resources; -mod graphics; +pub mod xr_input; use std::sync::{Arc, Mutex}; +use crate::xr_input::oculus_touch::ActionSets; +use bevy::app::PluginGroupBuilder; use bevy::ecs::system::SystemState; use bevy::prelude::*; -use bevy::render::camera::{ManualTextureViews, ManualTextureView, ManualTextureViewHandle}; -use bevy::render::{FutureRendererResources, RenderPlugin, RenderApp, Render, RenderSet}; +use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews}; +use bevy::render::pipelined_rendering::RenderExtractApp; +use bevy::render::renderer::{ + render_system, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, +}; +use bevy::render::settings::RenderSettings; +use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet}; use bevy::window::{PrimaryWindow, RawHandleWrapper}; use input::XrInput; -use resources::*; use openxr as xr; +use resources::*; +use wgpu::Instance; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; @@ -25,12 +34,14 @@ pub struct OpenXrPlugin; #[derive(Resource)] pub struct FutureXrResources( - pub Arc< + pub Arc< Mutex< Option<( XrInstance, XrSession, XrEnvironmentBlendMode, + XrResolution, + XrFormat, XrSessionRunning, XrFrameWaiter, XrSwapchain, @@ -44,36 +55,57 @@ pub struct FutureXrResources( impl Plugin for OpenXrPlugin { fn build(&self, app: &mut App) { - let future_renderer_resources_wrapper = Arc::new(Mutex::new(None)); - app.insert_resource(FutureRendererResources( - future_renderer_resources_wrapper.clone(), - )); - let future_xr_resources_wrapper = Arc::new(Mutex::new(None)); - app.insert_resource(FutureXrResources( - future_xr_resources_wrapper.clone() - )); + app.insert_resource(FutureXrResources(future_xr_resources_wrapper.clone())); let mut system_state: SystemState>> = SystemState::new(&mut app.world); let primary_window = system_state.get(&app.world).get_single().ok().cloned(); - bevy::tasks::IoTaskPool::get() - .spawn_local(async move { - let (device, queue, adapter_info, render_adapter, instance, xr_instance, session, blend_mode, session_running, frame_waiter, swapchain, input, views, frame_state) = graphics::initialize_xr_graphics(primary_window).unwrap(); - debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); - debug!("Configured wgpu adapter Features: {:#?}", device.features()); - let mut future_renderer_resources_inner = - future_renderer_resources_wrapper.lock().unwrap(); - *future_renderer_resources_inner = - Some((device, queue, adapter_info, render_adapter, instance)); - let mut future_xr_resources_inner = future_xr_resources_wrapper.lock().unwrap(); - *future_xr_resources_inner = - Some((xr_instance, session, blend_mode, session_running, frame_waiter, swapchain, input, views, frame_state)); - }) - .detach(); - - + let ( + device, + queue, + adapter_info, + render_adapter, + instance, + xr_instance, + session, + blend_mode, + resolution, + format, + session_running, + frame_waiter, + swapchain, + input, + views, + frame_state, + ) = graphics::initialize_xr_graphics(primary_window).unwrap(); + debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); + debug!("Configured wgpu adapter Features: {:#?}", device.features()); + let mut future_xr_resources_inner = future_xr_resources_wrapper.lock().unwrap(); + *future_xr_resources_inner = Some(( + xr_instance, + session, + blend_mode, + resolution, + format, + session_running, + frame_waiter, + swapchain, + input, + views, + frame_state, + )); + app.insert_resource(ActionSets(vec![])); + app.add_plugins(DefaultPlugins.set(RenderPlugin { + render_settings: RenderSettings::Manual( + device, + queue, + adapter_info, + render_adapter, + Mutex::new(instance), + ), + })); } fn ready(&self, app: &App) -> bool { @@ -84,166 +116,218 @@ impl Plugin for OpenXrPlugin { } fn finish(&self, app: &mut App) { - if let Some(future_renderer_resources) = - app.world.remove_resource::() - { - let (instance, session, blend_mode, session_running, frame_waiter, swapchain, input, views, frame_state) = - future_renderer_resources.0.lock().unwrap().take().unwrap(); + if let Some(future_renderer_resources) = app.world.remove_resource::() { + let ( + xr_instance, + session, + blend_mode, + resolution, + format, + session_running, + frame_waiter, + swapchain, + input, + views, + frame_state, + ) = future_renderer_resources.0.lock().unwrap().take().unwrap(); - app.insert_resource(instance.clone()) + let action_sets = app.world.resource::().clone(); + + app.insert_resource(xr_instance.clone()) .insert_resource(session.clone()) .insert_resource(blend_mode.clone()) + .insert_resource(resolution.clone()) + .insert_resource(format.clone()) .insert_resource(session_running.clone()) .insert_resource(frame_waiter.clone()) .insert_resource(swapchain.clone()) .insert_resource(input.clone()) .insert_resource(views.clone()) - .insert_resource(frame_state.clone()); + .insert_resource(frame_state.clone()) + .insert_resource(action_sets.clone()); - let swapchain_mut = swapchain.lock().unwrap(); - let (left, right) = swapchain_mut.get_render_views(); - let format = swapchain_mut.format(); + let (left, right) = swapchain.get_render_views(); let left = ManualTextureView { texture_view: left.into(), - size: swapchain_mut.resolution(), - format, + size: *resolution, + format: *format, }; let right = ManualTextureView { texture_view: right.into(), - size: swapchain_mut.resolution(), - format, + size: *resolution, + format: *format, }; let mut manual_texture_views = app.world.resource_mut::(); manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); drop(manual_texture_views); - drop(swapchain_mut); + let pipeline_app = app.sub_app_mut(RenderExtractApp); + pipeline_app + .insert_resource(xr_instance.clone()) + .insert_resource(session.clone()) + .insert_resource(blend_mode.clone()) + .insert_resource(resolution.clone()) + .insert_resource(format.clone()) + .insert_resource(session_running.clone()) + .insert_resource(frame_waiter.clone()) + .insert_resource(swapchain.clone()) + .insert_resource(input.clone()) + .insert_resource(views.clone()) + .insert_resource(frame_state.clone()) + .insert_resource(action_sets.clone()); + drop(pipeline_app); let render_app = app.sub_app_mut(RenderApp); - render_app.insert_resource(instance) + render_app + .insert_resource(xr_instance) .insert_resource(session) .insert_resource(blend_mode) + .insert_resource(resolution) + .insert_resource(format) .insert_resource(session_running) .insert_resource(frame_waiter) .insert_resource(swapchain) .insert_resource(input) .insert_resource(views) - .insert_resource(frame_state); + .insert_resource(frame_state) + .insert_resource(action_sets); - render_app.add_systems(Render, (pre_frame.in_set(RenderSet::Prepare).before(post_frame), post_frame.in_set(RenderSet::Prepare), post_queue_submit.in_set(RenderSet::Cleanup))); + render_app.add_systems( + Render, + ( + begin_frame.before(render_system).after(RenderSet::ExtractCommands), + locate_views.before(render_system), + end_frame.after(render_system), + ), + ); } - } } pub struct DefaultXrPlugins; impl PluginGroup for DefaultXrPlugins { - fn build(self) -> bevy::app::PluginGroupBuilder { - DefaultPlugins - .build() - .add_before::(OpenXrPlugin) + fn build(self) -> PluginGroupBuilder { + let mut group = PluginGroupBuilder::start::(); + group = group.add(OpenXrPlugin); + group } } -pub fn pre_frame( +pub fn begin_frame( instance: Res, session: Res, session_running: Res, - frame_state: Res, - frame_waiter: Res, + resolution: Res, + format: Res, swapchain: Res, - xr_input: Res, + frame_waiter: Res, + frame_state: Res, mut manual_texture_views: ResMut, -){ - while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() { - use xr::Event::*; - match event { - SessionStateChanged(e) => { - // Session state change is where we can begin and end sessions, as well as - // find quit messages! - info!("entered XR state {:?}", e.state()); - match e.state() { - xr::SessionState::READY => { - session.begin(VIEW_TYPE).unwrap(); - session_running.store(true, std::sync::atomic::Ordering::Relaxed); +) { + { + let _span = info_span!("xr_poll_events"); + while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() { + use xr::Event::*; + match event { + SessionStateChanged(e) => { + // Session state change is where we can begin and end sessions, as well as + // find quit messages! + info!("entered XR state {:?}", e.state()); + match e.state() { + xr::SessionState::READY => { + session.begin(VIEW_TYPE).unwrap(); + session_running.store(true, std::sync::atomic::Ordering::Relaxed); + } + xr::SessionState::STOPPING => { + session.end().unwrap(); + session_running.store(false, std::sync::atomic::Ordering::Relaxed); + } + xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return, + _ => {} } - xr::SessionState::STOPPING => { - session.end().unwrap(); - session_running.store(false, std::sync::atomic::Ordering::Relaxed); - } - xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => { - return - } - _ => {} } + InstanceLossPending(_) => return, + EventsLost(e) => { + warn!("lost {} XR events", e.lost_event_count()); + } + _ => {} } - InstanceLossPending(_) => { - return - } - EventsLost(e) => { - warn!("lost {} XR events", e.lost_event_count()); - } - _ => {} } } - if !session_running.load(std::sync::atomic::Ordering::Relaxed) { - // Don't grind up the CPU - std::thread::sleep(std::time::Duration::from_millis(10)); - return + { + let _span = info_span!("xr_wait_frame").entered(); + *frame_state.lock().unwrap() = frame_waiter.lock().unwrap().wait().unwrap(); } - - *frame_state.lock().unwrap() = Some(frame_waiter.lock().unwrap().wait().unwrap()); - - let mut swapchain = swapchain.lock().unwrap(); - - swapchain.begin().unwrap(); - swapchain.update_render_views(); - let (left, right) = swapchain.get_render_views(); - let active_action_set = xr::ActiveActionSet::new(&xr_input.action_set); - match session.sync_actions(&[active_action_set]) { - Err(err) => { - eprintln!("{}", err); - } - _ => {} + { + let _span = info_span!("xr_begin_frame").entered(); + swapchain.begin().unwrap() + } + { + let _span = info_span!("xr_acquire_image").entered(); + swapchain.acquire_image().unwrap() + } + { + let _span = info_span!("xr_wait_image").entered(); + swapchain.wait_image().unwrap(); + } + { + let _span = info_span!("xr_update_manual_texture_views").entered(); + let (left, right) = swapchain.get_render_views(); + let left = ManualTextureView { + texture_view: left.into(), + size: **resolution, + format: **format, + }; + let right = ManualTextureView { + texture_view: right.into(), + size: **resolution, + format: **format, + }; + manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); + manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); } - let format = swapchain.format(); - let left = ManualTextureView { - texture_view: left.into(), - size: swapchain.resolution(), - format, - }; - let right = ManualTextureView { - texture_view: right.into(), - size: swapchain.resolution(), - format, - }; - manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left); - manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right); } -pub fn post_frame( +pub fn end_frame( + xr_frame_state: Res, + views: Res, + input: Res, + swapchain: Res, + resolution: Res, + environment_blend_mode: Res, +) { + { + let _span = info_span!("xr_release_image").entered(); + swapchain.release_image().unwrap(); + } + { + let _span = info_span!("xr_end_frame").entered(); + swapchain + .end( + xr_frame_state.lock().unwrap().predicted_display_time, + &*views.lock().unwrap(), + &input.stage, + **resolution, + **environment_blend_mode, + ) + .unwrap(); + } +} + +pub fn locate_views( views: Res, input: Res, session: Res, xr_frame_state: Res, ) { - *views.lock().unwrap() = session.locate_views( - VIEW_TYPE, - xr_frame_state.lock().unwrap().unwrap().predicted_display_time, - &input.stage, - ).unwrap().1; + let _span = info_span!("xr_locate_views").entered(); + *views.lock().unwrap() = session + .locate_views( + VIEW_TYPE, + xr_frame_state.lock().unwrap().predicted_display_time, + &input.stage, + ) + .unwrap() + .1; } - -pub fn post_queue_submit( - xr_frame_state: Res, - views: Res, - input: Res, - swapchain: Res, - environment_blend_mode: Res, -) { - let xr_frame_state = xr_frame_state.lock().unwrap().unwrap(); - let views = &*views.lock().unwrap(); - let stage = &input.stage; - swapchain.lock().unwrap().post_queue_submit(xr_frame_state, views, stage, **environment_blend_mode).unwrap(); -} \ No newline at end of file diff --git a/src/resource_macros.rs b/src/resource_macros.rs index 358b6ee..5f4df4e 100644 --- a/src/resource_macros.rs +++ b/src/resource_macros.rs @@ -23,7 +23,7 @@ macro_rules! xr_resource_wrapper { Self::new(value) } } - } + }; } #[macro_export] @@ -51,8 +51,8 @@ macro_rules! xr_arc_resource_wrapper { Self::new(value) } } - } + }; } +pub use xr_arc_resource_wrapper; pub use xr_resource_wrapper; -pub use xr_arc_resource_wrapper; \ No newline at end of file diff --git a/src/resources.rs b/src/resources.rs index cc8185e..e7913b6 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -8,11 +8,12 @@ use openxr as xr; xr_resource_wrapper!(XrInstance, xr::Instance); xr_resource_wrapper!(XrSession, xr::Session); xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode); -xr_resource_wrapper!(XrViewConfigurationViews, Vec); +xr_resource_wrapper!(XrResolution, UVec2); +xr_resource_wrapper!(XrFormat, wgpu::TextureFormat); xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool); xr_arc_resource_wrapper!(XrFrameWaiter, Mutex); -xr_arc_resource_wrapper!(XrSwapchain, Mutex); -xr_arc_resource_wrapper!(XrFrameState, Mutex>); +xr_arc_resource_wrapper!(XrSwapchain, Swapchain); +xr_arc_resource_wrapper!(XrFrameState, Mutex); xr_arc_resource_wrapper!(XrViews, Mutex>); pub enum Swapchain { @@ -20,65 +21,70 @@ pub enum Swapchain { } impl Swapchain { - pub(crate) fn begin(&mut self) -> xr::Result<()> { + pub(crate) fn begin(&self) -> xr::Result<()> { match self { - Swapchain::Vulkan(swap) => swap.begin(), - } - } - - pub(crate) fn update_render_views(&mut self) { - match self { - Swapchain::Vulkan(swap) => swap.update_render_views(), + Swapchain::Vulkan(swapchain) => swapchain.begin(), } } pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) { match self { - Swapchain::Vulkan(swap) => swap.get_render_views(), + Swapchain::Vulkan(swapchain) => swapchain.get_render_views(), } } - pub(crate) fn format(&self) -> wgpu::TextureFormat { + pub(crate) fn acquire_image(&self) -> xr::Result<()> { match self { - Swapchain::Vulkan(swap) => swap.format + Swapchain::Vulkan(swapchain) => swapchain.acquire_image(), } } - pub(crate) fn resolution(&self) -> UVec2 { + pub(crate) fn wait_image(&self) -> xr::Result<()> { match self { - Swapchain::Vulkan(swap) => swap.resolution, + Swapchain::Vulkan(swapchain) => swapchain.wait_image(), } } - pub(crate) fn post_queue_submit( - &mut self, - xr_frame_state: xr::FrameState, + pub(crate) fn release_image(&self) -> xr::Result<()> { + match self { + Swapchain::Vulkan(swapchain) => swapchain.release_image(), + } + } + + pub(crate) fn end( + &self, + predicted_display_time: xr::Time, views: &[openxr::View], stage: &xr::Space, + resolution: UVec2, environment_blend_mode: xr::EnvironmentBlendMode, ) -> xr::Result<()> { match self { - Swapchain::Vulkan(swap) => swap.post_queue_submit(xr_frame_state, views, stage, environment_blend_mode), + Swapchain::Vulkan(swapchain) => swapchain.end( + predicted_display_time, + views, + stage, + resolution, + environment_blend_mode, + ), } } } pub struct SwapchainInner { - pub(crate) stream: xr::FrameStream, - pub(crate) handle: xr::Swapchain, - pub(crate) resolution: UVec2, - pub(crate) format: wgpu::TextureFormat, + pub(crate) stream: Mutex>, + pub(crate) handle: Mutex>, pub(crate) buffers: Vec, - pub(crate) image_index: usize, + pub(crate) image_index: Mutex, } impl SwapchainInner { - fn begin(&mut self) -> xr::Result<()> { - self.stream.begin() + fn begin(&self) -> xr::Result<()> { + self.stream.lock().unwrap().begin() } fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) { - let texture = &self.buffers[self.image_index]; + let texture = &self.buffers[*self.image_index.lock().unwrap()]; ( texture.create_view(&wgpu::TextureViewDescriptor { @@ -95,51 +101,62 @@ impl SwapchainInner { ) } - fn update_render_views(&mut self) { - let image_index = self.handle.acquire_image().unwrap(); - self.handle.wait_image(xr::Duration::INFINITE).unwrap(); - - self.image_index = image_index as _; + fn acquire_image(&self) -> xr::Result<()> { + let image_index = self.handle.lock().unwrap().acquire_image()?; + *self.image_index.lock().unwrap() = image_index as _; + Ok(()) } - fn post_queue_submit( - &mut self, - xr_frame_state: xr::FrameState, + fn wait_image(&self) -> xr::Result<()> { + self.handle + .lock() + .unwrap() + .wait_image(xr::Duration::INFINITE) + } + + fn release_image(&self) -> xr::Result<()> { + self.handle.lock().unwrap().release_image() + } + + fn end( + &self, + predicted_display_time: xr::Time, views: &[openxr::View], stage: &xr::Space, + resolution: UVec2, environment_blend_mode: xr::EnvironmentBlendMode, ) -> xr::Result<()> { - self.handle.release_image().unwrap(); - let rect = xr::Rect2Di { - offset: xr::Offset2Di { x: 0, y: 0 }, - extent: xr::Extent2Di { - width: self.resolution.x as _, - height: self.resolution.y as _, - }, - }; - self.stream.end( - xr_frame_state.predicted_display_time, - environment_blend_mode, - &[&xr::CompositionLayerProjection::new().space(stage).views(&[ - xr::CompositionLayerProjectionView::new() - .pose(views[0].pose) - .fov(views[0].fov) - .sub_image( - xr::SwapchainSubImage::new() - .swapchain(&self.handle) - .image_array_index(0) - .image_rect(rect), - ), - xr::CompositionLayerProjectionView::new() - .pose(views[1].pose) - .fov(views[1].fov) - .sub_image( - xr::SwapchainSubImage::new() - .swapchain(&self.handle) - .image_array_index(1) - .image_rect(rect), - ), - ])], - ) + let rect = xr::Rect2Di { + offset: xr::Offset2Di { x: 0, y: 0 }, + extent: xr::Extent2Di { + width: resolution.x as _, + height: resolution.y as _, + }, + }; + let swapchain = self.handle.lock().unwrap(); + self.stream.lock().unwrap().end( + predicted_display_time, + environment_blend_mode, + &[&xr::CompositionLayerProjection::new().space(stage).views(&[ + xr::CompositionLayerProjectionView::new() + .pose(views[0].pose) + .fov(views[0].fov) + .sub_image( + xr::SwapchainSubImage::new() + .swapchain(&swapchain) + .image_array_index(0) + .image_rect(rect), + ), + xr::CompositionLayerProjectionView::new() + .pose(views[1].pose) + .fov(views[1].fov) + .sub_image( + xr::SwapchainSubImage::new() + .swapchain(&swapchain) + .image_array_index(1) + .image_rect(rect), + ), + ])], + ) } } diff --git a/src/xr_input/controllers.rs b/src/xr_input/controllers.rs new file mode 100644 index 0000000..dd3c743 --- /dev/null +++ b/src/xr_input/controllers.rs @@ -0,0 +1,14 @@ +use openxr::{Action, ActionTy}; + +pub struct Touchable { + pub inner: Action, + pub touch: Action, +} +pub struct Handed { + pub left: T, + pub right: T, +} +#[derive(Copy, Clone)] +pub enum XrControllerType { + OculusTouch, +} diff --git a/src/xr_input/mod.rs b/src/xr_input/mod.rs new file mode 100644 index 0000000..244cffe --- /dev/null +++ b/src/xr_input/mod.rs @@ -0,0 +1,88 @@ +pub mod controllers; +pub mod oculus_touch; +pub mod xr_camera; + +use crate::resources::XrSession; +use crate::xr_input::controllers::XrControllerType; +use crate::xr_input::oculus_touch::{setup_oculus_controller, ActionSets}; +use crate::xr_input::xr_camera::{ + xr_camera_head_sync, Eye, XRProjection, XrCameraBundle, XrCamerasBundle, +}; +use bevy::app::{App, PostUpdate, Startup}; +use bevy::log::{info, warn}; +use bevy::prelude::IntoSystemConfigs; +use bevy::prelude::{ + Commands, Component, IntoSystemSetConfigs, Plugin, PreUpdate, Quat, Res, Resource, Vec3, +}; +use bevy::render::camera::CameraProjectionPlugin; +use bevy::render::view::{update_frusta, VisibilitySystems}; +use bevy::transform::TransformSystem; +use openxr::{Action, ActionSet, ActionTy}; + +#[derive(Copy, Clone)] +pub struct OpenXrInput { + pub controller_type: XrControllerType, +} +impl OpenXrInput { + pub fn new(controller_type: XrControllerType) -> Self { + Self { controller_type } + } +} + +impl Plugin for OpenXrInput { + fn build(&self, app: &mut App) { + app.add_plugins(CameraProjectionPlugin::::default()); + match self.controller_type { + XrControllerType::OculusTouch => { + app.add_systems(Startup, setup_oculus_controller); + } + } + app.add_systems(PreUpdate, action_set_system); + app.add_systems(PreUpdate, xr_camera_head_sync); + app.add_systems( + PostUpdate, + update_frusta:: + .after(TransformSystem::TransformPropagate) + .before(VisibilitySystems::UpdatePerspectiveFrusta), + ); + app.add_systems(Startup, setup_xr_cameras); + } +} + +fn setup_xr_cameras(mut commands: Commands) { + commands.spawn(XrCameraBundle::new(Eye::Right)); + commands.spawn(XrCameraBundle::new(Eye::Left)); +} + +fn action_set_system(action_sets: Res, session: Res) { + let mut active_action_sets = vec![]; + for i in &action_sets.0 { + active_action_sets.push(openxr::ActiveActionSet::new(i)); + } + info!("action sets: {:#?}", action_sets.0.len()); + match session.sync_actions(&active_action_sets) { + Err(err) => { + warn!("{}", err); + } + _ => {} + } +} + +pub trait Vec3Conv { + fn to_vec3(&self) -> Vec3; +} + +impl Vec3Conv for openxr::Vector3f { + fn to_vec3(&self) -> Vec3 { + Vec3::new(self.x, self.y, self.z) + } +} +pub trait QuatConv { + fn to_quat(&self) -> Quat; +} + +impl QuatConv for openxr::Quaternionf { + fn to_quat(&self) -> Quat { + Quat::from_xyzw(self.x, self.y, self.z, self.w) + } +} diff --git a/src/xr_input/oculus_touch.rs b/src/xr_input/oculus_touch.rs new file mode 100644 index 0000000..8c01817 --- /dev/null +++ b/src/xr_input/oculus_touch.rs @@ -0,0 +1,254 @@ +use crate::resources::{XrInstance, XrSession}; +use crate::xr_input::controllers::{Handed, Touchable}; +use crate::FutureXrResources; +use bevy::prelude::{Commands, Res, Resource}; +use openxr::{Action, ActionSet, AnyGraphics, Binding, Haptic, Instance, Posef, Session, Space}; +use std::any::Any; + +pub fn setup_oculus_controller( + mut commands: Commands, + instance: Res, + session: Res, +) { + let mut action_sets = vec![]; + let oculus_controller = OculusController::new( + Instance::clone(&instance), + Session::clone(&session), + &mut action_sets, + ) + .unwrap(); + session + .attach_action_sets(&action_sets.iter().map(|a| a).collect::>()) + .unwrap(); + commands.insert_resource(oculus_controller); + commands.insert_resource(ActionSets(action_sets)); +} + +#[derive(Resource, Clone)] +pub struct ActionSets(pub Vec); + +#[derive(Resource)] +pub struct OculusController { + pub grip_space: Handed, + pub aim_space: Handed, + pub grip_pose: Action, + pub aim_pose: Action, + pub squeeze: Action, + pub trigger: Touchable, + pub haptic_feedback: Action, + pub x_button: Touchable, + pub y_button: Touchable, + pub menu_button: Action, + pub a_button: Touchable, + pub b_button: Touchable, + pub thumbstick_x: Action, + pub thumbstick_y: Action, + pub thumbstick_touch: Action, + pub thumbstick_click: Action, + pub thumbrest_touch: Action, +} +impl OculusController { + pub fn new( + instance: Instance, + session: Session, + action_sets: &mut Vec, + ) -> anyhow::Result { + let action_set = + instance.create_action_set("oculus_input", "Oculus Touch Controller Input", 0)?; + let left_path = instance.string_to_path("/user/hand/left").unwrap(); + let right_path = instance.string_to_path("/user/hand/right").unwrap(); + let hands = [left_path, right_path]; + let grip_pose = action_set.create_action::("hand_pose", "Hand Pose", &hands)?; + let aim_pose = action_set.create_action::("pointer_pose", "Pointer Pose", &hands)?; + + let this = OculusController { + grip_space: Handed { + left: grip_pose.create_space(session.clone(), right_path, Posef::IDENTITY)?, + right: grip_pose.create_space(session.clone(), left_path, Posef::IDENTITY)?, + }, + aim_space: Handed { + left: aim_pose.create_space(session.clone(), right_path, Posef::IDENTITY)?, + right: aim_pose.create_space(session.clone(), left_path, Posef::IDENTITY)?, + }, + grip_pose, + aim_pose, + squeeze: action_set.create_action("squeeze", "Grip Pull", &hands)?, + trigger: Touchable { + inner: action_set.create_action("trigger", "Trigger Pull", &hands)?, + touch: action_set.create_action("trigger_touched", "Trigger Touch", &hands)?, + }, + haptic_feedback: action_set.create_action( + "haptic_feedback", + "Haptic Feedback", + &hands, + )?, + x_button: Touchable { + inner: action_set.create_action("x_button", "X Button", &[])?, + touch: action_set.create_action("x_button_touch", "X Button Touch", &[])?, + }, + y_button: Touchable { + inner: action_set.create_action("y_button", "Y Button", &[])?, + touch: action_set.create_action("y_button_touch", "Y Button Touch", &[])?, + }, + menu_button: action_set.create_action("menu_button", "Menu Button", &[])?, + a_button: Touchable { + inner: action_set.create_action("a_button", "A Button", &[])?, + touch: action_set.create_action("a_button_touch", "A Button Touch", &[])?, + }, + b_button: Touchable { + inner: action_set.create_action("b_button", "B Button", &[])?, + touch: action_set.create_action("b_button_touch", "B Button Touch", &[])?, + }, + thumbstick_x: action_set.create_action("thumbstick_x", "Thumbstick X", &hands)?, + thumbstick_y: action_set.create_action("thumbstick_y", "Thumbstick Y", &hands)?, + thumbstick_touch: action_set.create_action( + "thumbstick_touch", + "Thumbstick Touch", + &hands, + )?, + thumbstick_click: action_set.create_action( + "thumbstick_click", + "Thumbstick Click", + &hands, + )?, + thumbrest_touch: action_set.create_action( + "thumbrest_touch", + "Thumbrest Touch", + &hands, + )?, + }; + let i = instance; + i.suggest_interaction_profile_bindings( + i.string_to_path("/interaction_profiles/oculus/touch_controller")?, + &[ + Binding::new( + &this.grip_pose, + i.string_to_path("/user/hand/left/input/grip/pose")?, + ), + Binding::new( + &this.grip_pose, + i.string_to_path("/user/hand/right/input/grip/pose")?, + ), + Binding::new( + &this.aim_pose, + i.string_to_path("/user/hand/left/input/aim/pose")?, + ), + Binding::new( + &this.aim_pose, + i.string_to_path("/user/hand/left/input/aim/pose")?, + ), + Binding::new( + &this.squeeze, + i.string_to_path("/user/hand/left/input/squeeze/value")?, + ), + Binding::new( + &this.squeeze, + i.string_to_path("/user/hand/right/input/squeeze/value")?, + ), + Binding::new( + &this.trigger.inner, + i.string_to_path("/user/hand/right/input/trigger/value")?, + ), + Binding::new( + &this.trigger.inner, + i.string_to_path("/user/hand/left/input/trigger/value")?, + ), + Binding::new( + &this.trigger.touch, + i.string_to_path("/user/hand/right/input/trigger/touch")?, + ), + Binding::new( + &this.trigger.touch, + i.string_to_path("/user/hand/left/input/trigger/touch")?, + ), + Binding::new( + &this.haptic_feedback, + i.string_to_path("/user/hand/right/output/haptic")?, + ), + Binding::new( + &this.haptic_feedback, + i.string_to_path("/user/hand/left/output/haptic")?, + ), + Binding::new( + &this.x_button.inner, + i.string_to_path("/user/hand/left/input/x/click")?, + ), + Binding::new( + &this.x_button.touch, + i.string_to_path("/user/hand/left/input/x/touch")?, + ), + Binding::new( + &this.y_button.inner, + i.string_to_path("/user/hand/left/input/y/click")?, + ), + Binding::new( + &this.y_button.touch, + i.string_to_path("/user/hand/left/input/y/touch")?, + ), + Binding::new( + &this.menu_button, + i.string_to_path("/user/hand/left/input/menu/click")?, + ), + Binding::new( + &this.a_button.inner, + i.string_to_path("/user/hand/right/input/a/click")?, + ), + Binding::new( + &this.a_button.touch, + i.string_to_path("/user/hand/right/input/a/touch")?, + ), + Binding::new( + &this.b_button.inner, + i.string_to_path("/user/hand/right/input/b/click")?, + ), + Binding::new( + &this.b_button.touch, + i.string_to_path("/user/hand/right/input/b/touch")?, + ), + Binding::new( + &this.thumbstick_x, + i.string_to_path("/user/hand/left/input/thumbstick/x")?, + ), + Binding::new( + &this.thumbstick_x, + i.string_to_path("/user/hand/right/input/thumbstick/x")?, + ), + Binding::new( + &this.thumbstick_y, + i.string_to_path("/user/hand/left/input/thumbstick/y")?, + ), + Binding::new( + &this.thumbstick_y, + i.string_to_path("/user/hand/right/input/thumbstick/y")?, + ), + Binding::new( + &this.thumbstick_click, + i.string_to_path("/user/hand/left/input/thumbstick/click")?, + ), + Binding::new( + &this.thumbstick_click, + i.string_to_path("/user/hand/right/input/thumbstick/click")?, + ), + Binding::new( + &this.thumbstick_touch, + i.string_to_path("/user/hand/left/input/thumbstick/touch")?, + ), + Binding::new( + &this.thumbstick_touch, + i.string_to_path("/user/hand/right/input/thumbstick/touch")?, + ), + Binding::new( + &this.thumbrest_touch, + i.string_to_path("/user/hand/left/input/thumbrest/touch")?, + ), + Binding::new( + &this.thumbrest_touch, + i.string_to_path("/user/hand/right/input/thumbrest/touch")?, + ), + ], + )?; + + action_sets.push(action_set); + Ok(this) + } +} diff --git a/src/xr_input/xr_camera.rs b/src/xr_input/xr_camera.rs new file mode 100644 index 0000000..fbd2a90 --- /dev/null +++ b/src/xr_input/xr_camera.rs @@ -0,0 +1,239 @@ +use crate::xr_input::{QuatConv, Vec3Conv}; +use crate::{LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE}; +use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping}; +use bevy::prelude::*; +use bevy::render::camera::{CameraProjection, CameraRenderGraph, RenderTarget}; +use bevy::render::primitives::Frustum; +use bevy::render::view::{ColorGrading, VisibleEntities}; +use openxr::Fovf; + +#[derive(Bundle)] +pub struct XrCamerasBundle { + pub left: XrCameraBundle, + pub right: XrCameraBundle, +} +impl XrCamerasBundle { + pub fn new() -> Self { + Self::default() + } +} +impl Default for XrCamerasBundle { + fn default() -> Self { + Self { + left: XrCameraBundle::new(Eye::Left), + right: XrCameraBundle::new(Eye::Right), + } + } +} + +#[derive(Bundle)] +pub struct XrCameraBundle { + pub camera: Camera, + pub camera_render_graph: CameraRenderGraph, + pub xr_projection: XRProjection, + pub visible_entities: VisibleEntities, + pub frustum: Frustum, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub camera_3d: Camera3d, + pub tonemapping: Tonemapping, + pub dither: DebandDither, + pub color_grading: ColorGrading, + pub xr_camera_type: XrCameraType, +} +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Component)] +pub enum XrCameraType { + Xr(Eye), + Flatscreen, +} +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Eye { + Left = 0, + Right = 1, +} + +impl XrCameraBundle { + pub fn new(eye: Eye) -> Self { + Self { + camera: Camera { + order: -1, + target: RenderTarget::TextureView(match eye { + Eye::Left => LEFT_XR_TEXTURE_HANDLE, + Eye::Right => RIGHT_XR_TEXTURE_HANDLE, + }), + viewport: None, + ..default() + }, + camera_render_graph: CameraRenderGraph::new(bevy::core_pipeline::core_3d::graph::NAME), + xr_projection: Default::default(), + visible_entities: Default::default(), + frustum: Default::default(), + transform: Default::default(), + global_transform: Default::default(), + camera_3d: Default::default(), + tonemapping: Default::default(), + dither: DebandDither::Enabled, + color_grading: Default::default(), + xr_camera_type: XrCameraType::Xr(eye), + } + } +} + +#[derive(Debug, Clone, Component, Reflect)] +#[reflect(Component, Default)] +pub struct XRProjection { + pub near: f32, + pub far: f32, + #[reflect(ignore)] + pub fov: Fovf, +} + +impl Default for XRProjection { + fn default() -> Self { + Self { + near: 0.1, + far: 1000., + fov: Default::default(), + } + } +} + +impl XRProjection { + pub fn new(near: f32, far: f32, fov: Fovf) -> Self { + XRProjection { near, far, fov } + } +} + +impl CameraProjection for XRProjection { + // ============================================================================= + // math code adapted from + // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/common/xr_linear.h + // Copyright (c) 2017 The Khronos Group Inc. + // Copyright (c) 2016 Oculus VR, LLC. + // SPDX-License-Identifier: Apache-2.0 + // ============================================================================= + fn get_projection_matrix(&self) -> Mat4 { + // symmetric perspective for debugging + // let x_fov = (self.fov.angle_left.abs() + self.fov.angle_right.abs()); + // let y_fov = (self.fov.angle_up.abs() + self.fov.angle_down.abs()); + // return Mat4::perspective_infinite_reverse_rh(y_fov, x_fov / y_fov, self.near); + + let fov = self.fov; + let is_vulkan_api = false; // FIXME wgpu probably abstracts this + let near_z = self.near; + let far_z = -1.; // use infinite proj + // let far_z = self.far; + + let tan_angle_left = fov.angle_left.tan(); + let tan_angle_right = fov.angle_right.tan(); + + let tan_angle_down = fov.angle_down.tan(); + let tan_angle_up = fov.angle_up.tan(); + + let tan_angle_width = tan_angle_right - tan_angle_left; + + // Set to tanAngleDown - tanAngleUp for a clip space with positive Y + // down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with + // positive Y up (OpenGL / D3D / Metal). + // const float tanAngleHeight = + // graphicsApi == GRAPHICS_VULKAN ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown); + let tan_angle_height = if is_vulkan_api { + tan_angle_down - tan_angle_up + } else { + tan_angle_up - tan_angle_down + }; + + // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES). + // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal). + // const float offsetZ = + // (graphicsApi == GRAPHICS_OPENGL || graphicsApi == GRAPHICS_OPENGL_ES) ? nearZ : 0; + // FIXME handle enum of graphics apis + let offset_z = 0.; + + let mut cols: [f32; 16] = [0.0; 16]; + + if far_z <= near_z { + // place the far plane at infinity + cols[0] = 2. / tan_angle_width; + cols[4] = 0.; + cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width; + cols[12] = 0.; + + cols[1] = 0.; + cols[5] = 2. / tan_angle_height; + cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height; + cols[13] = 0.; + + cols[2] = 0.; + cols[6] = 0.; + cols[10] = -1.; + cols[14] = -(near_z + offset_z); + + cols[3] = 0.; + cols[7] = 0.; + cols[11] = -1.; + cols[15] = 0.; + + // bevy uses the _reverse_ infinite projection + // https://dev.theomader.com/depth-precision/ + let z_reversal = Mat4::from_cols_array_2d(&[ + [1f32, 0., 0., 0.], + [0., 1., 0., 0.], + [0., 0., -1., 0.], + [0., 0., 1., 1.], + ]); + + return z_reversal * Mat4::from_cols_array(&cols); + } else { + // normal projection + cols[0] = 2. / tan_angle_width; + cols[4] = 0.; + cols[8] = (tan_angle_right + tan_angle_left) / tan_angle_width; + cols[12] = 0.; + + cols[1] = 0.; + cols[5] = 2. / tan_angle_height; + cols[9] = (tan_angle_up + tan_angle_down) / tan_angle_height; + cols[13] = 0.; + + cols[2] = 0.; + cols[6] = 0.; + cols[10] = -(far_z + offset_z) / (far_z - near_z); + cols[14] = -(far_z * (near_z + offset_z)) / (far_z - near_z); + + cols[3] = 0.; + cols[7] = 0.; + cols[11] = -1.; + cols[15] = 0.; + } + + Mat4::from_cols_array(&cols) + } + + fn update(&mut self, _width: f32, _height: f32) {} + + fn far(&self) -> f32 { + self.far + } +} + +pub fn xr_camera_head_sync( + views: ResMut, + mut query: Query<(&mut Transform, &XrCameraType, &mut XRProjection)>, +) { + let mut f = || -> Option<()> { + for (mut transform, camera_type, mut xr_projection) in query.iter_mut() { + let view_idx = match camera_type { + XrCameraType::Xr(eye) => *eye as usize, + XrCameraType::Flatscreen => return None, + }; + let v = views.lock().unwrap(); + let view = v.get(view_idx)?; + xr_projection.fov = view.fov; + transform.rotation = view.pose.orientation.to_quat(); + transform.translation = view.pose.position.to_vec3(); + } + Some(()) + }; + let _ = f(); +}