begin refactor
This commit is contained in:
35
src/backend/graphics/mod.rs
Normal file
35
src/backend/graphics/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub mod vulkan;
|
||||
|
||||
use bevy::window::RawHandleWrapper;
|
||||
use openxr::{FrameStream, FrameWaiter, Instance, Swapchain, ViewConfigurationType};
|
||||
|
||||
use crate::{error::XrError, types::GraphicsFeatures};
|
||||
|
||||
use super::OXrSession;
|
||||
|
||||
const VIEW_TYPE: ViewConfigurationType = ViewConfigurationType::PRIMARY_STEREO;
|
||||
|
||||
pub enum OXrGraphics {
|
||||
#[cfg(feature = "vulkan")]
|
||||
Vulkan {
|
||||
swapchain: Swapchain<openxr::Vulkan>,
|
||||
frame_stream: FrameStream<openxr::Vulkan>,
|
||||
frame_waiter: FrameWaiter,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn init_oxr_graphics(
|
||||
instance: Instance,
|
||||
graphics: GraphicsFeatures,
|
||||
window: Option<RawHandleWrapper>,
|
||||
) -> Result<OXrSession, XrError> {
|
||||
#[cfg(feature = "vulkan")]
|
||||
if graphics.vulkan {
|
||||
if let Ok(session) = vulkan::init_oxr_graphics(instance, window) {
|
||||
return Ok(session);
|
||||
}
|
||||
}
|
||||
|
||||
Err(XrError {})
|
||||
}
|
||||
@@ -1,80 +1,24 @@
|
||||
use std::ffi::{c_void, CString};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Context;
|
||||
use ash::vk::{self, Handle};
|
||||
use bevy::log::info;
|
||||
use bevy::math::uvec2;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderQueue};
|
||||
use bevy::window::RawHandleWrapper;
|
||||
use openxr as xr;
|
||||
use wgpu::Instance;
|
||||
use openxr::Instance;
|
||||
|
||||
use crate::input::XrInput;
|
||||
use crate::resources::{
|
||||
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter,
|
||||
XrInstance, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews,
|
||||
};
|
||||
use crate::VIEW_TYPE;
|
||||
use crate::backend::graphics::{OXrGraphics, VIEW_TYPE};
|
||||
use crate::backend::OXrSession;
|
||||
use crate::error::XrError;
|
||||
|
||||
pub fn initialize_xr_graphics(
|
||||
pub fn init_oxr_graphics(
|
||||
xr_instance: Instance,
|
||||
window: Option<RawHandleWrapper>,
|
||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
||||
// any extension at some point
|
||||
) -> anyhow::Result<(
|
||||
RenderDevice,
|
||||
RenderQueue,
|
||||
RenderAdapterInfo,
|
||||
RenderAdapter,
|
||||
Instance,
|
||||
XrInstance,
|
||||
XrSession,
|
||||
XrEnvironmentBlendMode,
|
||||
XrResolution,
|
||||
XrFormat,
|
||||
XrSessionRunning,
|
||||
XrFrameWaiter,
|
||||
XrSwapchain,
|
||||
XrInput,
|
||||
XrViews,
|
||||
XrFrameState,
|
||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
||||
// any extension at some point
|
||||
)> {
|
||||
) -> Result<OXrSession, XrError> {
|
||||
use wgpu_hal::{api::Vulkan as V, Api};
|
||||
|
||||
let xr_entry = super::xr_entry()?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
xr_entry.initialize_android_loader()?;
|
||||
|
||||
let available_extensions = xr_entry.enumerate_extensions()?;
|
||||
assert!(available_extensions.khr_vulkan_enable2);
|
||||
info!("available xr exts: {:#?}", available_extensions);
|
||||
|
||||
let mut enabled_extensions = xr::ExtensionSet::default();
|
||||
enabled_extensions.khr_vulkan_enable2 = true;
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
enabled_extensions.khr_android_create_instance = true;
|
||||
}
|
||||
enabled_extensions.ext_hand_tracking = available_extensions.ext_hand_tracking;
|
||||
// enabled_extensions.ext_hand_joints_motion_range = available_extensions.ext_hand_joints_motion_range;
|
||||
|
||||
|
||||
let available_layers = xr_entry.enumerate_layers()?;
|
||||
info!("available xr layers: {:#?}", available_layers);
|
||||
|
||||
let xr_instance = xr_entry.create_instance(
|
||||
&xr::ApplicationInfo {
|
||||
application_name: "Ambient",
|
||||
..Default::default()
|
||||
},
|
||||
&enabled_extensions,
|
||||
&[],
|
||||
)?;
|
||||
info!("created instance");
|
||||
let instance_props = xr_instance.properties()?;
|
||||
let xr_system_id = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
|
||||
info!("created system");
|
||||
@@ -113,10 +57,10 @@ pub fn initialize_xr_graphics(
|
||||
);
|
||||
}
|
||||
|
||||
let vk_entry = unsafe { ash::Entry::load() }?;
|
||||
let vk_entry = unsafe { ash::Entry::load() }.map_err(|_| XrError {})?;
|
||||
let flags = wgpu_hal::InstanceFlags::empty();
|
||||
let extensions =
|
||||
<V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)?;
|
||||
let extensions = <V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)
|
||||
.map_err(|_| XrError {})?;
|
||||
let device_extensions = vec![
|
||||
ash::extensions::khr::Swapchain::name(),
|
||||
ash::extensions::khr::DrawIndirectCount::name(),
|
||||
@@ -131,7 +75,7 @@ pub fn initialize_xr_graphics(
|
||||
let vk_instance = unsafe {
|
||||
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
|
||||
|
||||
let app_name = CString::new("Ambient")?;
|
||||
let app_name = CString::new("Bevy").map_err(|_| XrError {})?;
|
||||
let vk_app_info = vk::ApplicationInfo::builder()
|
||||
.application_name(&app_name)
|
||||
.application_version(1)
|
||||
@@ -148,11 +92,8 @@ pub fn initialize_xr_graphics(
|
||||
.enabled_extension_names(&extensions_cchar) as *const _
|
||||
as *const _,
|
||||
)
|
||||
.context("XR error creating Vulkan instance")
|
||||
.unwrap()
|
||||
.map_err(vk::Result::from_raw)
|
||||
.context("Vulkan error creating Vulkan instance")
|
||||
.unwrap();
|
||||
.map_err(|_| XrError {})?
|
||||
.map_err(|_| XrError {})?;
|
||||
|
||||
ash::Instance::load(
|
||||
vk_entry.static_fn(),
|
||||
@@ -186,7 +127,8 @@ pub fn initialize_xr_graphics(
|
||||
flags,
|
||||
false,
|
||||
Some(Box::new(())),
|
||||
)?
|
||||
)
|
||||
.map_err(|_| XrError {})?
|
||||
};
|
||||
|
||||
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
|
||||
@@ -196,7 +138,7 @@ pub fn initialize_xr_graphics(
|
||||
|
||||
let wgpu_exposed_adapter = wgpu_vk_instance
|
||||
.expose_adapter(vk_physical_device)
|
||||
.context("failed to expose adapter")?;
|
||||
.ok_or(XrError {})?;
|
||||
|
||||
let enabled_extensions = wgpu_exposed_adapter
|
||||
.adapter
|
||||
@@ -232,9 +174,8 @@ pub fn initialize_xr_graphics(
|
||||
vk_physical_device.as_raw() as _,
|
||||
&info as *const _ as *const _,
|
||||
)
|
||||
.context("XR error creating Vulkan device")?
|
||||
.map_err(vk::Result::from_raw)
|
||||
.context("Vulkan error creating Vulkan device")?;
|
||||
.map_err(|_| XrError {})?
|
||||
.map_err(|_| XrError {})?;
|
||||
|
||||
ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _))
|
||||
};
|
||||
@@ -249,7 +190,8 @@ pub fn initialize_xr_graphics(
|
||||
family_info.queue_family_index,
|
||||
0,
|
||||
)
|
||||
}?;
|
||||
}
|
||||
.map_err(|_| XrError {})?;
|
||||
|
||||
(
|
||||
wgpu_open_device,
|
||||
@@ -278,9 +220,10 @@ pub fn initialize_xr_graphics(
|
||||
},
|
||||
None,
|
||||
)
|
||||
}?;
|
||||
}
|
||||
.map_err(|_| XrError {})?;
|
||||
|
||||
let (session, frame_wait, frame_stream) = unsafe {
|
||||
let (session, frame_waiter, frame_stream) = unsafe {
|
||||
xr_instance.create_session::<xr::Vulkan>(
|
||||
xr_system_id,
|
||||
&xr::vulkan::SessionCreateInfo {
|
||||
@@ -312,12 +255,12 @@ pub fn initialize_xr_graphics(
|
||||
views[0].recommended_image_rect_height,
|
||||
);
|
||||
|
||||
let handle = session
|
||||
let swapchain = session
|
||||
.create_swapchain(&xr::SwapchainCreateInfo {
|
||||
create_flags: xr::SwapchainCreateFlags::EMPTY,
|
||||
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT
|
||||
| xr::SwapchainUsageFlags::SAMPLED,
|
||||
format: wgpu_to_vulkan(swapchain_format).as_raw() as _,
|
||||
format: wgpu_to_vulkan(swapchain_format).ok_or(XrError {})?.as_raw() as _,
|
||||
// The Vulkan graphics pipeline we create is not set up for multisampling,
|
||||
// so we hardcode this to 1. If we used a proper multisampling setup, we
|
||||
// could set this to `views[0].recommended_swapchain_sample_count`.
|
||||
@@ -329,9 +272,9 @@ pub fn initialize_xr_graphics(
|
||||
mip_count: 1,
|
||||
})
|
||||
.unwrap();
|
||||
let images = handle.enumerate_images().unwrap();
|
||||
let images = swapchain.enumerate_images().unwrap();
|
||||
|
||||
let buffers = images
|
||||
let buffers: Vec<_> = images
|
||||
.into_iter()
|
||||
.map(|color_image| {
|
||||
let color_image = vk::Image::from_raw(color_image);
|
||||
@@ -381,101 +324,155 @@ pub fn initialize_xr_graphics(
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok((
|
||||
wgpu_device.into(),
|
||||
RenderQueue(Arc::new(wgpu_queue)),
|
||||
RenderAdapterInfo(wgpu_adapter.get_info()),
|
||||
RenderAdapter(Arc::new(wgpu_adapter)),
|
||||
wgpu_instance,
|
||||
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(),
|
||||
Swapchain::Vulkan(SwapchainInner {
|
||||
stream: Mutex::new(frame_stream),
|
||||
handle: Mutex::new(handle),
|
||||
buffers,
|
||||
image_index: Mutex::new(0),
|
||||
})
|
||||
.into(),
|
||||
XrInput::new(xr_instance, session.into_any_graphics())?,
|
||||
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(),
|
||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
||||
// any extension at some point
|
||||
))
|
||||
OXrSession {
|
||||
graphics: OXrGraphics::Vulkan {
|
||||
swapchain,
|
||||
frame_stream,
|
||||
frame_waiter,
|
||||
},
|
||||
device: wgpu_device.into(),
|
||||
queue: RenderQueue(Arc::new(wgpu_queue)),
|
||||
adapter_info: RenderAdapterInfo(wgpu_adapter.get_info()),
|
||||
adapter: RenderAdapter(Arc::new(wgpu_adapter)),
|
||||
session: session.into_any_graphics(),
|
||||
blend_mode,
|
||||
resolution,
|
||||
format: swapchain_format,
|
||||
buffers,
|
||||
image_index: Mutex::new(0),
|
||||
instance: xr_instance,
|
||||
};
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> vk::Format {
|
||||
use vk::Format;
|
||||
match format {
|
||||
wgpu::TextureFormat::R8Unorm => Format::R8_UNORM,
|
||||
wgpu::TextureFormat::R8Snorm => Format::R8_SNORM,
|
||||
wgpu::TextureFormat::R8Uint => Format::R8_UINT,
|
||||
wgpu::TextureFormat::R8Sint => Format::R8_SINT,
|
||||
wgpu::TextureFormat::R16Uint => Format::R16_UINT,
|
||||
wgpu::TextureFormat::R16Sint => Format::R16_SINT,
|
||||
wgpu::TextureFormat::R16Unorm => Format::R16_UNORM,
|
||||
wgpu::TextureFormat::R16Snorm => Format::R16_SNORM,
|
||||
wgpu::TextureFormat::R16Float => Format::R16_SFLOAT,
|
||||
wgpu::TextureFormat::Rg8Unorm => Format::R8G8_UNORM,
|
||||
wgpu::TextureFormat::Rg8Snorm => Format::R8G8_SNORM,
|
||||
wgpu::TextureFormat::Rg8Uint => Format::R8G8_UINT,
|
||||
wgpu::TextureFormat::Rg8Sint => Format::R8G8_SINT,
|
||||
wgpu::TextureFormat::R32Uint => Format::R32_UINT,
|
||||
wgpu::TextureFormat::R32Sint => Format::R32_SINT,
|
||||
wgpu::TextureFormat::R32Float => Format::R32_SFLOAT,
|
||||
wgpu::TextureFormat::Rg16Uint => Format::R16G16_UINT,
|
||||
wgpu::TextureFormat::Rg16Sint => Format::R16G16_SINT,
|
||||
wgpu::TextureFormat::Rg16Unorm => Format::R16G16_UNORM,
|
||||
wgpu::TextureFormat::Rg16Snorm => Format::R16G16_SNORM,
|
||||
wgpu::TextureFormat::Rg16Float => Format::R16G16_SFLOAT,
|
||||
wgpu::TextureFormat::Rgba8Unorm => Format::R8G8B8A8_UNORM,
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb => Format::R8G8B8A8_SRGB,
|
||||
wgpu::TextureFormat::Rgba8Snorm => Format::R8G8B8A8_SNORM,
|
||||
wgpu::TextureFormat::Rgba8Uint => Format::R8G8B8A8_UINT,
|
||||
wgpu::TextureFormat::Rgba8Sint => Format::R8G8B8A8_SINT,
|
||||
wgpu::TextureFormat::Bgra8Unorm => Format::B8G8R8A8_UNORM,
|
||||
wgpu::TextureFormat::Bgra8UnormSrgb => Format::B8G8R8A8_SRGB,
|
||||
wgpu::TextureFormat::Rgb9e5Ufloat => Format::E5B9G9R9_UFLOAT_PACK32, // this might be the wrong type??? i can't tell
|
||||
wgpu::TextureFormat::Rgb10a2Unorm => Format::A2R10G10B10_UNORM_PACK32,
|
||||
wgpu::TextureFormat::Rg11b10Float => panic!("this texture type invokes nothing but fear within my soul and i don't think vulkan has a proper type for this"),
|
||||
wgpu::TextureFormat::Rg32Uint => Format::R32G32_UINT,
|
||||
wgpu::TextureFormat::Rg32Sint => Format::R32G32_SINT,
|
||||
wgpu::TextureFormat::Rg32Float => Format::R32G32_SFLOAT,
|
||||
wgpu::TextureFormat::Rgba16Uint => Format::R16G16B16A16_UINT,
|
||||
wgpu::TextureFormat::Rgba16Sint => Format::R16G16B16A16_SINT,
|
||||
wgpu::TextureFormat::Rgba16Unorm => Format::R16G16B16A16_UNORM,
|
||||
wgpu::TextureFormat::Rgba16Snorm => Format::R16G16B16A16_SNORM,
|
||||
wgpu::TextureFormat::Rgba16Float => Format::R16G16B16A16_SFLOAT,
|
||||
wgpu::TextureFormat::Rgba32Uint => Format::R32G32B32A32_UINT,
|
||||
wgpu::TextureFormat::Rgba32Sint => Format::R32G32B32A32_SINT,
|
||||
wgpu::TextureFormat::Rgba32Float => Format::R32G32B32A32_SFLOAT,
|
||||
wgpu::TextureFormat::Stencil8 => Format::S8_UINT,
|
||||
wgpu::TextureFormat::Depth16Unorm => Format::D16_UNORM,
|
||||
wgpu::TextureFormat::Depth24Plus => Format::X8_D24_UNORM_PACK32,
|
||||
wgpu::TextureFormat::Depth24PlusStencil8 => Format::D24_UNORM_S8_UINT,
|
||||
wgpu::TextureFormat::Depth32Float => Format::D32_SFLOAT,
|
||||
wgpu::TextureFormat::Depth32FloatStencil8 => Format::D32_SFLOAT_S8_UINT,
|
||||
wgpu::TextureFormat::Etc2Rgb8Unorm => Format::ETC2_R8G8B8_UNORM_BLOCK,
|
||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb => Format::ETC2_R8G8B8_SRGB_BLOCK,
|
||||
wgpu::TextureFormat::Etc2Rgb8A1Unorm => Format::ETC2_R8G8B8A1_UNORM_BLOCK,
|
||||
wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb => Format::ETC2_R8G8B8A1_SRGB_BLOCK,
|
||||
wgpu::TextureFormat::Etc2Rgba8Unorm => Format::ETC2_R8G8B8A8_UNORM_BLOCK,
|
||||
wgpu::TextureFormat::Etc2Rgba8UnormSrgb => Format::ETC2_R8G8B8A8_SRGB_BLOCK,
|
||||
wgpu::TextureFormat::EacR11Unorm => Format::EAC_R11_UNORM_BLOCK,
|
||||
wgpu::TextureFormat::EacR11Snorm => Format::EAC_R11_SNORM_BLOCK,
|
||||
wgpu::TextureFormat::EacRg11Unorm => Format::EAC_R11G11_UNORM_BLOCK,
|
||||
wgpu::TextureFormat::EacRg11Snorm => Format::EAC_R11G11_SNORM_BLOCK,
|
||||
wgpu::TextureFormat::Astc { .. } => panic!("please god kill me now"),
|
||||
_ => panic!("fuck no")
|
||||
}
|
||||
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<vk::Format> {
|
||||
// Copied with minor modification from:
|
||||
// https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151
|
||||
// license: MIT OR Apache-2.0
|
||||
use ash::vk::Format as F;
|
||||
use wgpu::TextureFormat as Tf;
|
||||
use wgpu::{AstcBlock, AstcChannel};
|
||||
Some(match format {
|
||||
Tf::R8Unorm => F::R8_UNORM,
|
||||
Tf::R8Snorm => F::R8_SNORM,
|
||||
Tf::R8Uint => F::R8_UINT,
|
||||
Tf::R8Sint => F::R8_SINT,
|
||||
Tf::R16Uint => F::R16_UINT,
|
||||
Tf::R16Sint => F::R16_SINT,
|
||||
Tf::R16Unorm => F::R16_UNORM,
|
||||
Tf::R16Snorm => F::R16_SNORM,
|
||||
Tf::R16Float => F::R16_SFLOAT,
|
||||
Tf::Rg8Unorm => F::R8G8_UNORM,
|
||||
Tf::Rg8Snorm => F::R8G8_SNORM,
|
||||
Tf::Rg8Uint => F::R8G8_UINT,
|
||||
Tf::Rg8Sint => F::R8G8_SINT,
|
||||
Tf::Rg16Unorm => F::R16G16_UNORM,
|
||||
Tf::Rg16Snorm => F::R16G16_SNORM,
|
||||
Tf::R32Uint => F::R32_UINT,
|
||||
Tf::R32Sint => F::R32_SINT,
|
||||
Tf::R32Float => F::R32_SFLOAT,
|
||||
Tf::Rg16Uint => F::R16G16_UINT,
|
||||
Tf::Rg16Sint => F::R16G16_SINT,
|
||||
Tf::Rg16Float => F::R16G16_SFLOAT,
|
||||
Tf::Rgba8Unorm => F::R8G8B8A8_UNORM,
|
||||
Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB,
|
||||
Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB,
|
||||
Tf::Rgba8Snorm => F::R8G8B8A8_SNORM,
|
||||
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
|
||||
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
|
||||
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
|
||||
Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32,
|
||||
Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32,
|
||||
Tf::Rg32Uint => F::R32G32_UINT,
|
||||
Tf::Rg32Sint => F::R32G32_SINT,
|
||||
Tf::Rg32Float => F::R32G32_SFLOAT,
|
||||
Tf::Rgba16Uint => F::R16G16B16A16_UINT,
|
||||
Tf::Rgba16Sint => F::R16G16B16A16_SINT,
|
||||
Tf::Rgba16Unorm => F::R16G16B16A16_UNORM,
|
||||
Tf::Rgba16Snorm => F::R16G16B16A16_SNORM,
|
||||
Tf::Rgba16Float => F::R16G16B16A16_SFLOAT,
|
||||
Tf::Rgba32Uint => F::R32G32B32A32_UINT,
|
||||
Tf::Rgba32Sint => F::R32G32B32A32_SINT,
|
||||
Tf::Rgba32Float => F::R32G32B32A32_SFLOAT,
|
||||
Tf::Depth32Float => F::D32_SFLOAT,
|
||||
Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT,
|
||||
Tf::Depth24Plus | Tf::Depth24PlusStencil8 | Tf::Stencil8 => return None, // Dependent on device properties
|
||||
Tf::Depth16Unorm => F::D16_UNORM,
|
||||
Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32,
|
||||
Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK,
|
||||
Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK,
|
||||
Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK,
|
||||
Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK,
|
||||
Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK,
|
||||
Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK,
|
||||
Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK,
|
||||
Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK,
|
||||
Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK,
|
||||
Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK,
|
||||
Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK,
|
||||
Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK,
|
||||
Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK,
|
||||
Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK,
|
||||
Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK,
|
||||
Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK,
|
||||
Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK,
|
||||
Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK,
|
||||
Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK,
|
||||
Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK,
|
||||
Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK,
|
||||
Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK,
|
||||
Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK,
|
||||
Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK,
|
||||
Tf::Astc { block, channel } => match channel {
|
||||
AstcChannel::Unorm => match block {
|
||||
AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK,
|
||||
AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK,
|
||||
AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK,
|
||||
AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK,
|
||||
AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK,
|
||||
AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK,
|
||||
AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK,
|
||||
AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK,
|
||||
AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK,
|
||||
AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK,
|
||||
AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK,
|
||||
AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK,
|
||||
AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK,
|
||||
AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK,
|
||||
},
|
||||
AstcChannel::UnormSrgb => match block {
|
||||
AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK,
|
||||
AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK,
|
||||
AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK,
|
||||
AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK,
|
||||
AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK,
|
||||
AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK,
|
||||
AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK,
|
||||
AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK,
|
||||
AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK,
|
||||
AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK,
|
||||
AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK,
|
||||
AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK,
|
||||
AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK,
|
||||
AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK,
|
||||
},
|
||||
AstcChannel::Hdr => match block {
|
||||
AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT,
|
||||
AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
53
src/backend/mod.rs
Normal file
53
src/backend/mod.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) mod graphics;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod oxr;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) mod oxr_utils;
|
||||
pub mod traits;
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(crate) mod web_utils;
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod webxr;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use oxr::*;
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub use webxr::*;
|
||||
|
||||
macro_rules! xr_inner {
|
||||
($res:ty, $oxr:ty, $webxr:ty) => {
|
||||
paste::paste! {
|
||||
pub enum [<$res Inner>] {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
OpenXR($oxr),
|
||||
#[cfg(target_family = "wasm")]
|
||||
WebXR($webxr),
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl From<$oxr> for $res {
|
||||
fn from(value: $oxr) -> $res {
|
||||
$res(std::rc::Rc::new([<$res Inner>]::OpenXR(value)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
impl From<$webxr> for $res {
|
||||
fn from(value: $webxr) -> $res {
|
||||
$res(std::rc::Rc::new([<$res Inner>]::WebXR(value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use crate::resources::*;
|
||||
|
||||
xr_inner!(XrEntry, OXrEntry, WebXrEntry);
|
||||
xr_inner!(XrInstance, OXrInstance, WebXrInstance);
|
||||
xr_inner!(XrSession, OXrSession, WebXrSession);
|
||||
xr_inner!(XrInput, OXrAction, WebXrAction);
|
||||
xr_inner!(XrController, OXrController, WebXrActionSet);
|
||||
xr_inner!(XrActionSpace, OXrActionSpace, WebXrActionSpace);
|
||||
xr_inner!(XrReferenceSpace, OXrReferenceSpace, WebXrReferenceSpace);
|
||||
159
src/backend/oxr.rs
Normal file
159
src/backend/oxr.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use bevy::math::UVec2;
|
||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||
|
||||
use bevy::utils::HashMap;
|
||||
use openxr::{
|
||||
ActionSet, AnyGraphics, ApplicationInfo, Entry, EnvironmentBlendMode, ExtensionSet, Instance,
|
||||
Session,
|
||||
};
|
||||
|
||||
use super::graphics::OXrGraphics;
|
||||
use super::traits::*;
|
||||
use super::{oxr_utils::*, XrControllerInner};
|
||||
|
||||
use crate::backend::graphics::init_oxr_graphics;
|
||||
use crate::error::XrError;
|
||||
use crate::resources::*;
|
||||
use crate::types::*;
|
||||
|
||||
pub struct OXrEntry(Entry);
|
||||
|
||||
impl XrEntryTrait for OXrEntry {
|
||||
fn get_xr_entry(&self) -> Result<XrEntry, XrError> {
|
||||
Ok(OXrEntry(xr_entry()?).into())
|
||||
}
|
||||
|
||||
fn get_available_features(&self) -> Result<FeatureList, XrError> {
|
||||
let available_extensions = self.0.enumerate_extensions()?;
|
||||
let mut feature_list = FeatureList::default();
|
||||
feature_list.graphics.vulkan = available_extensions.khr_vulkan_enable2;
|
||||
Ok(feature_list)
|
||||
}
|
||||
|
||||
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError> {
|
||||
let mut enabled_extensions = ExtensionSet::default();
|
||||
enabled_extensions.khr_vulkan_enable2 = features.graphics.vulkan;
|
||||
let instance = self.0.create_instance(
|
||||
&ApplicationInfo {
|
||||
application_name: "Ambient",
|
||||
..Default::default()
|
||||
},
|
||||
&enabled_extensions,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
Ok(OXrInstance {
|
||||
enabled_features: features,
|
||||
inner: instance,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OXrInstance {
|
||||
inner: Instance,
|
||||
enabled_features: FeatureList,
|
||||
}
|
||||
|
||||
impl XrInstanceTrait for OXrInstance {
|
||||
fn requested_features(&self) -> FeatureList {
|
||||
self.enabled_features
|
||||
}
|
||||
|
||||
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError> {
|
||||
Ok(init_oxr_graphics(
|
||||
self.inner.clone(),
|
||||
self.enabled_features.graphics,
|
||||
info.window,
|
||||
)?
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OXrSession {
|
||||
pub(crate) instance: Instance,
|
||||
pub(crate) graphics: OXrGraphics,
|
||||
pub(crate) device: RenderDevice,
|
||||
pub(crate) queue: RenderQueue,
|
||||
pub(crate) adapter: RenderAdapter,
|
||||
pub(crate) adapter_info: RenderAdapterInfo,
|
||||
pub(crate) session: Session<AnyGraphics>,
|
||||
pub(crate) blend_mode: EnvironmentBlendMode,
|
||||
pub(crate) resolution: UVec2,
|
||||
pub(crate) format: wgpu::TextureFormat,
|
||||
pub(crate) buffers: Vec<wgpu::Texture>,
|
||||
pub(crate) image_index: Mutex<usize>,
|
||||
}
|
||||
|
||||
impl XrSessionTrait for OXrSession {
|
||||
fn sync_controllers(&self, (left, _): (XrController, XrController)) -> Result<(), XrError> {
|
||||
let XrControllerInner::OpenXR(controller) = &**left;
|
||||
self.session
|
||||
.sync_actions(&[(&controller.action_set).into()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn create_controllers(
|
||||
&self,
|
||||
info: ActionSetCreateInfo,
|
||||
) -> Result<(XrController, XrController), XrError> {
|
||||
let instance = &self.instance;
|
||||
let action_set = instance.create_action_set("controllers", "XR Controllers", 0)?;
|
||||
let left_path = instance.string_to_path("/user/hand/left").unwrap();
|
||||
let right_path = instance.string_to_path("/user/hand/right").unwrap();
|
||||
let hand_subpaths = &[left_path, right_path];
|
||||
use ActionPath::*;
|
||||
let actions = &[
|
||||
HandPose,
|
||||
PointerPose,
|
||||
GripPull,
|
||||
TriggerPull,
|
||||
TriggerTouch,
|
||||
HapticFeedback,
|
||||
PrimaryButton,
|
||||
PrimaryButtonTouch,
|
||||
SecondaryButton,
|
||||
SecondaryButtonTouch,
|
||||
MenuButton,
|
||||
ThumbstickX,
|
||||
ThumbstickY,
|
||||
ThumbstickTouch,
|
||||
ThumbstickClick,
|
||||
ThumbrestTouch,
|
||||
];
|
||||
let action_map = Rc::new(create_actions(&action_set, actions, hand_subpaths)?);
|
||||
Ok((
|
||||
OXrController {
|
||||
action_set: action_set.clone(),
|
||||
actions: action_map.clone(),
|
||||
side: Side::Left,
|
||||
}
|
||||
.into(),
|
||||
OXrController {
|
||||
action_set,
|
||||
actions: action_map,
|
||||
side: Side::Right,
|
||||
}
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OXrAction {}
|
||||
|
||||
pub struct OXrController {
|
||||
action_set: ActionSet,
|
||||
actions: Rc<HashMap<ActionPath, TypedAction>>,
|
||||
side: Side,
|
||||
}
|
||||
|
||||
pub struct OXrActionSpace {}
|
||||
|
||||
pub struct OXrReferenceSpace {}
|
||||
132
src/backend/oxr_utils.rs
Normal file
132
src/backend/oxr_utils.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::{
|
||||
error::XrError,
|
||||
types::{ActionPath, ActionSidedness, ActionType, ControllerType, Side},
|
||||
};
|
||||
use bevy::utils::HashMap;
|
||||
use openxr::{Action, ActionSet, Binding, Entry, Haptic, Path, Posef};
|
||||
|
||||
#[cfg(feature = "linked")]
|
||||
pub fn xr_entry() -> Result<openxr::Entry, XrError> {
|
||||
Ok(Entry::linked())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "linked"))]
|
||||
pub fn xr_entry() -> Result<openxr::Entry, XrError> {
|
||||
unsafe { Entry::load().map_err(|_| XrError {}) }
|
||||
}
|
||||
|
||||
pub fn create_actions(
|
||||
action_set: &ActionSet,
|
||||
actions: &[ActionPath],
|
||||
hand_subpaths: &[Path],
|
||||
) -> Result<HashMap<ActionPath, TypedAction>, XrError> {
|
||||
let mut action_map = HashMap::new();
|
||||
for action in actions {
|
||||
let subaction_paths = match action.sidedness() {
|
||||
ActionSidedness::Single => &[],
|
||||
ActionSidedness::Double => hand_subpaths,
|
||||
};
|
||||
let name = action.action_name();
|
||||
let localized_name = action.pretty_action_name();
|
||||
let typed_action = match action.action_type() {
|
||||
ActionType::Bool => TypedAction::Bool(action_set.create_action(
|
||||
name,
|
||||
localized_name,
|
||||
subaction_paths,
|
||||
)?),
|
||||
ActionType::Float => {
|
||||
TypedAction::F32(action_set.create_action(name, localized_name, subaction_paths)?)
|
||||
}
|
||||
ActionType::Haptics => TypedAction::Haptic(action_set.create_action(
|
||||
name,
|
||||
localized_name,
|
||||
subaction_paths,
|
||||
)?),
|
||||
ActionType::Pose => TypedAction::PoseF(action_set.create_action(
|
||||
name,
|
||||
localized_name,
|
||||
subaction_paths,
|
||||
)?),
|
||||
};
|
||||
|
||||
action_map.insert(*action, typed_action);
|
||||
}
|
||||
Ok(action_map)
|
||||
}
|
||||
|
||||
pub enum TypedAction {
|
||||
F32(Action<f32>),
|
||||
Bool(Action<bool>),
|
||||
PoseF(Action<Posef>),
|
||||
Haptic(Action<Haptic>),
|
||||
}
|
||||
|
||||
impl TypedAction {
|
||||
fn make_binding(&self, name: Path) -> Binding {
|
||||
match self {
|
||||
TypedAction::F32(a) => Binding::new(a, name),
|
||||
TypedAction::Bool(a) => Binding::new(a, name),
|
||||
TypedAction::PoseF(a) => Binding::new(a, name),
|
||||
TypedAction::Haptic(a) => Binding::new(a, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControllerType {
|
||||
pub(crate) fn set_controller_bindings(
|
||||
&self,
|
||||
instance: &openxr::Instance,
|
||||
bindings: &HashMap<ActionPath, TypedAction>,
|
||||
) -> Result<(), XrError> {
|
||||
match self {
|
||||
ControllerType::OculusTouch => {
|
||||
instance.suggest_interaction_profile_bindings(
|
||||
instance.string_to_path("/interaction_profiles/oculus/touch_controller")?,
|
||||
&[],
|
||||
)?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_binding_paths(&self, path: ActionPath) -> &[&'static str] {
|
||||
match self {
|
||||
ControllerType::OculusTouch => match path {
|
||||
ActionPath::HandPose => &[
|
||||
"/user/hand/left/input/grip/pose",
|
||||
"/user/hand/right/input/grip/pose",
|
||||
],
|
||||
ActionPath::PointerPose => &[
|
||||
"/user/hand/left/input/aim/pose",
|
||||
"/user/hand/right/input/aim/pose",
|
||||
],
|
||||
ActionPath::GripPull => &[
|
||||
"/user/hand/left/input/squeeze/value",
|
||||
"/user/hand/right/input/squeeze/value",
|
||||
],
|
||||
ActionPath::TriggerPull => &[
|
||||
"/user/hand/left/input/trigger/value",
|
||||
"/user/hand/right/input/trigger/value",
|
||||
],
|
||||
ActionPath::TriggerTouch => &[
|
||||
"/user/hand/left/input/trigger/touch",
|
||||
"/user/hand/right/input/trigger/touch",
|
||||
],
|
||||
ActionPath::HapticFeedback => &[
|
||||
"/user/hand/left/output/haptic",
|
||||
"/user/hand/right/output/haptic",
|
||||
],
|
||||
ActionPath::PrimaryButton => &[],
|
||||
ActionPath::PrimaryButtonTouch => todo!(),
|
||||
ActionPath::SecondaryButton => todo!(),
|
||||
ActionPath::SecondaryButtonTouch => todo!(),
|
||||
ActionPath::MenuButton => todo!(),
|
||||
ActionPath::ThumbstickX => todo!(),
|
||||
ActionPath::ThumbstickY => todo!(),
|
||||
ActionPath::ThumbstickTouch => todo!(),
|
||||
ActionPath::ThumbstickClick => todo!(),
|
||||
ActionPath::ThumbrestTouch => todo!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/backend/traits.rs
Normal file
63
src/backend/traits.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use crate::error::XrError;
|
||||
use crate::resources::*;
|
||||
use crate::types::*;
|
||||
|
||||
use bevy::render::primitives::Aabb;
|
||||
|
||||
pub trait XrEntryTrait {
|
||||
fn get_xr_entry(&self) -> Result<XrEntry, XrError>;
|
||||
fn get_available_features(&self) -> Result<FeatureList, XrError>;
|
||||
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError>;
|
||||
}
|
||||
|
||||
pub trait XrInstanceTrait {
|
||||
fn requested_features(&self) -> FeatureList;
|
||||
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError>;
|
||||
}
|
||||
|
||||
pub trait XrSessionTrait {
|
||||
fn sync_controllers(&self, controllers: (XrController, XrController)) -> Result<(), XrError>;
|
||||
fn get_reference_space(&self, info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError>;
|
||||
fn create_controllers(
|
||||
&self,
|
||||
info: ActionSetCreateInfo,
|
||||
) -> Result<(XrController, XrController), XrError>;
|
||||
}
|
||||
|
||||
pub trait XrControllerTrait {
|
||||
fn get_action_space(&self, info: ActionSpaceInfo) -> Result<XrActionSpace, XrError>;
|
||||
fn get_action(&self, id: ActionId) -> Option<XrInput>;
|
||||
}
|
||||
|
||||
pub trait XrInputTrait {
|
||||
fn get_action_state(&self) -> ActionState;
|
||||
fn get_action_bool(&self) -> Option<bool> {
|
||||
if let ActionState::Bool(b) = self.get_action_state() {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn get_action_float(&self) -> Option<f32> {
|
||||
if let ActionState::Float(f) = self.get_action_state() {
|
||||
Some(f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn get_action_haptic(&self) -> Option<Haptics> {
|
||||
if let ActionState::Haptics(h) = self.get_action_state() {
|
||||
Some(h)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait XrActionSpaceTrait {
|
||||
fn locate(&self, base: XrReferenceSpace) -> Pose;
|
||||
}
|
||||
|
||||
pub trait XrReferenceSpaceTrait {
|
||||
fn bounds(&self) -> Aabb;
|
||||
}
|
||||
56
src/backend/web_utils.rs
Normal file
56
src/backend/web_utils.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{
|
||||
js_sys::{Object, Promise, Reflect},
|
||||
HtmlCanvasElement, WebGl2RenderingContext,
|
||||
};
|
||||
|
||||
pub fn get_canvas(canvas_id: &str) -> Result<HtmlCanvasElement, JsValue> {
|
||||
let query = format!("#{}", canvas_id);
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let canvas = document
|
||||
.query_selector(&query)
|
||||
.unwrap()
|
||||
.expect("bevy_webxr - could not find canvas");
|
||||
let canvas = canvas.dyn_into::<HtmlCanvasElement>()?;
|
||||
Ok(canvas)
|
||||
}
|
||||
|
||||
pub fn create_webgl_context(
|
||||
xr_mode: bool,
|
||||
canvas: &str,
|
||||
) -> Result<WebGl2RenderingContext, JsValue> {
|
||||
let canvas = get_canvas(canvas)?;
|
||||
|
||||
let gl: WebGl2RenderingContext = if xr_mode {
|
||||
let gl_attribs = Object::new();
|
||||
Reflect::set(
|
||||
&gl_attribs,
|
||||
&JsValue::from_str("xrCompatible"),
|
||||
&JsValue::TRUE,
|
||||
)?;
|
||||
canvas
|
||||
.get_context_with_context_options("webgl2", &gl_attribs)?
|
||||
.unwrap()
|
||||
.dyn_into()?
|
||||
} else {
|
||||
canvas.get_context("webgl2")?.unwrap().dyn_into()?
|
||||
};
|
||||
|
||||
Ok(gl)
|
||||
}
|
||||
|
||||
pub trait PromiseRes {
|
||||
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue>;
|
||||
}
|
||||
|
||||
impl PromiseRes for Promise {
|
||||
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue> {
|
||||
resolve_promise(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_promise<T: From<JsValue>>(promise: Promise) -> Result<T, JsValue> {
|
||||
bevy::tasks::block_on(async move { JsFuture::from(promise).await.map(Into::into) })
|
||||
}
|
||||
90
src/backend/webxr.rs
Normal file
90
src/backend/webxr.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use web_sys::XrRenderStateInit;
|
||||
use web_sys::XrWebGlLayer;
|
||||
|
||||
use super::traits::*;
|
||||
use super::web_utils::*;
|
||||
|
||||
use crate::error::XrError;
|
||||
use crate::resources::*;
|
||||
use crate::types::*;
|
||||
|
||||
pub const BEVY_CANVAS_ID: &str = "bevy_canvas";
|
||||
pub const BEVY_CANVAS_QUERY: &str = "canvas[data-bevy-webxr=\"bevy_canvas\"]";
|
||||
|
||||
pub struct WebXrEntry(web_sys::XrSystem);
|
||||
|
||||
impl XrEntryTrait for WebXrEntry {
|
||||
fn get_xr_entry(&self) -> Result<XrEntry, XrError> {
|
||||
if let Some(window) = web_sys::window() {
|
||||
Ok(WebXrEntry(window.navigator().xr()).into())
|
||||
} else {
|
||||
Err(XrError {})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_available_features(&self) -> Result<FeatureList, XrError> {
|
||||
Ok(FeatureList::default())
|
||||
}
|
||||
|
||||
fn create_instance(&self, features: FeatureList) -> Result<XrInstance, XrError> {
|
||||
Ok(WebXrInstance {
|
||||
xr: self.0.clone(),
|
||||
features,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebXrInstance {
|
||||
xr: web_sys::XrSystem,
|
||||
features: FeatureList,
|
||||
}
|
||||
|
||||
impl XrInstanceTrait for WebXrInstance {
|
||||
fn requested_features(&self) -> FeatureList {
|
||||
self.features
|
||||
}
|
||||
|
||||
fn create_session(&self, info: SessionCreateInfo) -> Result<XrSession, XrError> {
|
||||
let session = self
|
||||
.xr
|
||||
.request_session(web_sys::XrSessionMode::ImmersiveVr)
|
||||
.resolve()?;
|
||||
|
||||
let gl = create_webgl_context(true, &info.canvas.expect("Expected canvas string"))?;
|
||||
let xr_gl_layer = XrWebGlLayer::new_with_web_gl2_rendering_context(&session, &gl)?;
|
||||
let mut render_state_init = XrRenderStateInit::new();
|
||||
render_state_init.base_layer(Some(&xr_gl_layer));
|
||||
session.update_render_state_with_state(&render_state_init);
|
||||
|
||||
Ok(WebXrSession(session).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebXrSession(web_sys::XrSession);
|
||||
|
||||
impl XrSessionTrait for WebXrSession {
|
||||
fn sync_actions(&self, action_set: XrActionSet) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_reference_space(&self, _info: ReferenceSpaceInfo) -> Result<XrReferenceSpace, XrError> {
|
||||
let space = self
|
||||
.0
|
||||
.request_reference_space(web_sys::XrReferenceSpaceType::BoundedFloor)
|
||||
.resolve()?;
|
||||
Ok(WebXrReferenceSpace(space).into())
|
||||
}
|
||||
|
||||
fn create_action_set(&self, info: ActionSetCreateInfo) -> Result<XrActionSet, XrError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WebXrActionSet(Vec<XrAction>);
|
||||
|
||||
pub struct WebXrAction(web_sys::XrInputSource);
|
||||
|
||||
pub struct WebXrActionSpace(web_sys::XrJointSpace);
|
||||
|
||||
pub struct WebXrReferenceSpace(web_sys::XrBoundedReferenceSpace);
|
||||
15
src/error.rs
Normal file
15
src/error.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub struct XrError {}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
impl From<wasm_bindgen::JsValue> for XrError {
|
||||
fn from(_: wasm_bindgen::JsValue) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl From<openxr::sys::Result> for XrError {
|
||||
fn from(_: openxr::sys::Result) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
mod vulkan;
|
||||
|
||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||
use bevy::window::RawHandleWrapper;
|
||||
use wgpu::Instance;
|
||||
|
||||
use crate::input::XrInput;
|
||||
use crate::resources::{
|
||||
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
|
||||
XrSession, XrSessionRunning, XrSwapchain, XrViews,
|
||||
};
|
||||
|
||||
use openxr as xr;
|
||||
|
||||
pub fn initialize_xr_graphics(
|
||||
window: Option<RawHandleWrapper>,
|
||||
) -> anyhow::Result<(
|
||||
RenderDevice,
|
||||
RenderQueue,
|
||||
RenderAdapterInfo,
|
||||
RenderAdapter,
|
||||
Instance,
|
||||
XrInstance,
|
||||
XrSession,
|
||||
XrEnvironmentBlendMode,
|
||||
XrResolution,
|
||||
XrFormat,
|
||||
XrSessionRunning,
|
||||
XrFrameWaiter,
|
||||
XrSwapchain,
|
||||
XrInput,
|
||||
XrViews,
|
||||
XrFrameState,
|
||||
)> {
|
||||
vulkan::initialize_xr_graphics(window)
|
||||
}
|
||||
|
||||
pub fn xr_entry() -> anyhow::Result<xr::Entry> {
|
||||
#[cfg(feature = "linked")]
|
||||
let entry = Ok(xr::Entry::linked());
|
||||
#[cfg(not(feature = "linked"))]
|
||||
let entry = unsafe { xr::Entry::load().map_err(|e| anyhow::anyhow!(e)) };
|
||||
entry
|
||||
}
|
||||
68
src/input.rs
68
src/input.rs
@@ -1,68 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use openxr as xr;
|
||||
|
||||
#[derive(Clone, Resource)]
|
||||
pub struct XrInput {
|
||||
//pub action_set: xr::ActionSet,
|
||||
//pub hand_pose: xr::Action<xr::Posef>,
|
||||
//pub right_space: Arc<xr::Space>,
|
||||
//pub left_space: Arc<xr::Space>,
|
||||
pub stage: Arc<xr::Space>,
|
||||
pub head: Arc<xr::Space>,
|
||||
}
|
||||
|
||||
impl XrInput {
|
||||
pub fn new(_instance: xr::Instance, session: xr::Session<xr::AnyGraphics>) -> xr::Result<Self> {
|
||||
// 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::<xr::Posef>(
|
||||
// "hand_pose",
|
||||
// "Hand Pose",
|
||||
// &[left_hand_subaction_path, right_hand_subaction_path],
|
||||
// )?;
|
||||
// /* let left_action =
|
||||
// action_set.create_action::<xr::Posef>("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)?;
|
||||
let head = session
|
||||
.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)
|
||||
.unwrap();
|
||||
//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),
|
||||
stage: Arc::new(stage),
|
||||
head: Arc::new(head),
|
||||
})
|
||||
}
|
||||
}
|
||||
397
src/lib.rs
397
src/lib.rs
@@ -1,394 +1,5 @@
|
||||
mod graphics;
|
||||
pub mod input;
|
||||
pub mod resource_macros;
|
||||
pub mod backend;
|
||||
pub mod error;
|
||||
mod resource_macros;
|
||||
pub mod resources;
|
||||
pub mod xr_init;
|
||||
pub mod xr_input;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::xr_init::RenderRestartPlugin;
|
||||
use crate::xr_input::hands::hand_tracking::DisableHandTracking;
|
||||
use crate::xr_input::oculus_touch::ActionSets;
|
||||
use bevy::app::PluginGroupBuilder;
|
||||
use bevy::ecs::system::{RunSystemOnce, SystemState};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::camera::{
|
||||
CameraPlugin, ManualTextureView, ManualTextureViewHandle, ManualTextureViews,
|
||||
};
|
||||
use bevy::render::globals::GlobalsPlugin;
|
||||
use bevy::render::mesh::morph::MorphPlugin;
|
||||
use bevy::render::mesh::MeshPlugin;
|
||||
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||
use bevy::render::render_asset::RenderAssetDependency;
|
||||
use bevy::render::render_resource::ShaderLoader;
|
||||
use bevy::render::renderer::{
|
||||
render_system, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
|
||||
};
|
||||
use bevy::render::settings::RenderCreation;
|
||||
use bevy::render::view::{self, ViewPlugin, WindowRenderPlugin};
|
||||
use bevy::render::{color, primitives, Render, RenderApp, RenderPlugin, RenderSet};
|
||||
use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper};
|
||||
use input::XrInput;
|
||||
use openxr as xr;
|
||||
use resources::*;
|
||||
use xr::FormFactor;
|
||||
use xr_init::{
|
||||
init_non_xr_graphics, update_xr_stuff, xr_only, RenderCreationData, XrEnableRequest,
|
||||
XrEnableStatus, XrRenderData, XrRenderUpdate,
|
||||
};
|
||||
use xr_input::controllers::XrControllerType;
|
||||
use xr_input::hands::emulated::HandEmulationPlugin;
|
||||
use xr_input::hands::hand_tracking::{HandTrackingData, HandTrackingPlugin};
|
||||
use xr_input::OpenXrInput;
|
||||
|
||||
const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
|
||||
|
||||
pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(1208214591);
|
||||
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
|
||||
|
||||
/// Adds OpenXR support to an App
|
||||
pub struct OpenXrPlugin;
|
||||
|
||||
impl Default for OpenXrPlugin {
|
||||
fn default() -> Self {
|
||||
OpenXrPlugin
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FutureXrResources(
|
||||
pub Arc<
|
||||
Mutex<
|
||||
Option<(
|
||||
XrInstance,
|
||||
XrSession,
|
||||
XrEnvironmentBlendMode,
|
||||
XrResolution,
|
||||
XrFormat,
|
||||
XrSessionRunning,
|
||||
XrFrameWaiter,
|
||||
XrSwapchain,
|
||||
XrInput,
|
||||
XrViews,
|
||||
XrFrameState,
|
||||
)>,
|
||||
>,
|
||||
>,
|
||||
);
|
||||
|
||||
impl Plugin for OpenXrPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||
SystemState::new(&mut app.world);
|
||||
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
||||
if let Ok((
|
||||
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.clone())
|
||||
{
|
||||
// std::thread::sleep(Duration::from_secs(5));
|
||||
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
|
||||
debug!("Configured wgpu adapter Features: {:#?}", device.features());
|
||||
app.insert_resource(xr_instance.clone());
|
||||
app.insert_resource(session.clone());
|
||||
app.insert_resource(blend_mode.clone());
|
||||
app.insert_resource(resolution.clone());
|
||||
app.insert_resource(format.clone());
|
||||
app.insert_resource(session_running.clone());
|
||||
app.insert_resource(frame_waiter.clone());
|
||||
app.insert_resource(swapchain.clone());
|
||||
app.insert_resource(input.clone());
|
||||
app.insert_resource(views.clone());
|
||||
app.insert_resource(frame_state.clone());
|
||||
let xr_data = XrRenderData {
|
||||
xr_instance,
|
||||
xr_session: session,
|
||||
xr_blend_mode: blend_mode,
|
||||
xr_resolution: resolution,
|
||||
xr_format: format,
|
||||
xr_session_running: session_running,
|
||||
xr_frame_waiter: frame_waiter,
|
||||
xr_swapchain: swapchain,
|
||||
xr_input: input,
|
||||
xr_views: views,
|
||||
xr_frame_state: frame_state,
|
||||
};
|
||||
app.insert_resource(xr_data);
|
||||
app.insert_resource(ActionSets(vec![]));
|
||||
app.add_plugins(RenderPlugin {
|
||||
render_creation: RenderCreation::Manual(
|
||||
device,
|
||||
queue,
|
||||
adapter_info,
|
||||
render_adapter,
|
||||
RenderInstance(Arc::new(instance)),
|
||||
),
|
||||
});
|
||||
app.insert_resource(XrEnableStatus::Enabled);
|
||||
} else {
|
||||
app.add_plugins(RenderPlugin::default());
|
||||
app.insert_resource(XrEnableStatus::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
fn ready(&self, app: &App) -> bool {
|
||||
app.world
|
||||
.get_resource::<XrEnableStatus>()
|
||||
.map(|frr| *frr != XrEnableStatus::Waiting)
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
// TODO: Split this up into the indevidual resources
|
||||
if let Some(data) = app.world.get_resource::<XrRenderData>().cloned() {
|
||||
let hands = data.xr_instance.exts().ext_hand_tracking.is_some()
|
||||
&& data
|
||||
.xr_instance
|
||||
.supports_hand_tracking(
|
||||
data.xr_instance
|
||||
.system(FormFactor::HEAD_MOUNTED_DISPLAY)
|
||||
.unwrap(),
|
||||
)
|
||||
.is_ok_and(|v| v);
|
||||
if hands {
|
||||
app.insert_resource(HandTrackingData::new(&data.xr_session).unwrap());
|
||||
} else {
|
||||
app.insert_resource(DisableHandTracking::Both);
|
||||
}
|
||||
|
||||
let (left, right) = data.xr_swapchain.get_render_views();
|
||||
let left = ManualTextureView {
|
||||
texture_view: left.into(),
|
||||
size: *data.xr_resolution,
|
||||
format: *data.xr_format,
|
||||
};
|
||||
let right = ManualTextureView {
|
||||
texture_view: right.into(),
|
||||
size: *data.xr_resolution,
|
||||
format: *data.xr_format,
|
||||
};
|
||||
app.add_systems(PreUpdate, xr_begin_frame.run_if(xr_only()));
|
||||
let mut manual_texture_views = app.world.resource_mut::<ManualTextureViews>();
|
||||
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
|
||||
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
|
||||
drop(manual_texture_views);
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app.insert_resource(data.xr_instance.clone());
|
||||
render_app.insert_resource(data.xr_session.clone());
|
||||
render_app.insert_resource(data.xr_blend_mode.clone());
|
||||
render_app.insert_resource(data.xr_resolution.clone());
|
||||
render_app.insert_resource(data.xr_format.clone());
|
||||
render_app.insert_resource(data.xr_session_running.clone());
|
||||
render_app.insert_resource(data.xr_frame_waiter.clone());
|
||||
render_app.insert_resource(data.xr_swapchain.clone());
|
||||
render_app.insert_resource(data.xr_input.clone());
|
||||
render_app.insert_resource(data.xr_views.clone());
|
||||
render_app.insert_resource(data.xr_frame_state.clone());
|
||||
render_app.insert_resource(XrEnableStatus::Enabled);
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
(
|
||||
post_frame
|
||||
.run_if(xr_only())
|
||||
.before(render_system)
|
||||
.after(RenderSet::ExtractCommands),
|
||||
end_frame.run_if(xr_only()).after(render_system),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultXrPlugins;
|
||||
|
||||
impl PluginGroup for DefaultXrPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
DefaultPlugins
|
||||
.build()
|
||||
.disable::<RenderPlugin>()
|
||||
.disable::<PipelinedRenderingPlugin>()
|
||||
.add_before::<RenderPlugin, _>(OpenXrPlugin)
|
||||
.add_after::<OpenXrPlugin, _>(OpenXrInput::new(XrControllerType::OculusTouch))
|
||||
.add_before::<OpenXrPlugin, _>(RenderRestartPlugin)
|
||||
.add(HandEmulationPlugin)
|
||||
.add(HandTrackingPlugin)
|
||||
.set(WindowPlugin {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
#[cfg(target_os = "android")]
|
||||
primary_window: None,
|
||||
#[cfg(target_os = "android")]
|
||||
exit_condition: bevy::window::ExitCondition::DontExit,
|
||||
#[cfg(target_os = "android")]
|
||||
close_when_requested: true,
|
||||
..default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xr_begin_frame(
|
||||
instance: Res<XrInstance>,
|
||||
session: Res<XrSession>,
|
||||
session_running: Res<XrSessionRunning>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
frame_waiter: Res<XrFrameWaiter>,
|
||||
swapchain: Res<XrSwapchain>,
|
||||
views: Res<XrViews>,
|
||||
input: Res<XrInput>,
|
||||
) {
|
||||
{
|
||||
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,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
InstanceLossPending(_) => return,
|
||||
EventsLost(e) => {
|
||||
warn!("lost {} XR events", e.lost_event_count());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
let _span = info_span!("xr_wait_frame").entered();
|
||||
*frame_state.lock().unwrap() = match frame_waiter.lock().unwrap().wait() {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
warn!("error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
{
|
||||
let _span = info_span!("xr_begin_frame").entered();
|
||||
swapchain.begin().unwrap()
|
||||
}
|
||||
{
|
||||
let _span = info_span!("xr_locate_views").entered();
|
||||
*views.lock().unwrap() = session
|
||||
.locate_views(
|
||||
VIEW_TYPE,
|
||||
frame_state.lock().unwrap().predicted_display_time,
|
||||
&input.stage,
|
||||
)
|
||||
.unwrap()
|
||||
.1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post_frame(
|
||||
resolution: Res<XrResolution>,
|
||||
format: Res<XrFormat>,
|
||||
swapchain: Res<XrSwapchain>,
|
||||
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||
) {
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(
|
||||
xr_frame_state: Res<XrFrameState>,
|
||||
views: Res<XrViews>,
|
||||
input: Res<XrInput>,
|
||||
swapchain: Res<XrSwapchain>,
|
||||
resolution: Res<XrResolution>,
|
||||
environment_blend_mode: Res<XrEnvironmentBlendMode>,
|
||||
) {
|
||||
{
|
||||
let _span = info_span!("xr_release_image").entered();
|
||||
swapchain.release_image().unwrap();
|
||||
}
|
||||
{
|
||||
let _span = info_span!("xr_end_frame").entered();
|
||||
let result = swapchain.end(
|
||||
xr_frame_state.lock().unwrap().predicted_display_time,
|
||||
&*views.lock().unwrap(),
|
||||
&input.stage,
|
||||
**resolution,
|
||||
**environment_blend_mode,
|
||||
);
|
||||
match result {
|
||||
Ok(_) => {}
|
||||
Err(e) => warn!("error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn locate_views(
|
||||
views: Res<XrViews>,
|
||||
input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
xr_frame_state: Res<XrFrameState>,
|
||||
) {
|
||||
let _span = info_span!("xr_locate_views").entered();
|
||||
*views.lock().unwrap() = match session.locate_views(
|
||||
VIEW_TYPE,
|
||||
xr_frame_state.lock().unwrap().predicted_display_time,
|
||||
&input.stage,
|
||||
) {
|
||||
Ok(this) => this,
|
||||
Err(err) => {
|
||||
warn!("error: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
.1;
|
||||
}
|
||||
pub mod types;
|
||||
|
||||
@@ -1,58 +1 @@
|
||||
#[macro_export]
|
||||
macro_rules! xr_resource_wrapper {
|
||||
($wrapper_type:ident, $xr_type:ty) => {
|
||||
#[derive(Clone, bevy::prelude::Resource)]
|
||||
pub struct $wrapper_type($xr_type);
|
||||
|
||||
impl $wrapper_type {
|
||||
pub fn new(value: $xr_type) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for $wrapper_type {
|
||||
type Target = $xr_type;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$xr_type> for $wrapper_type {
|
||||
fn from(value: $xr_type) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! xr_arc_resource_wrapper {
|
||||
($wrapper_type:ident, $xr_type:ty) => {
|
||||
#[derive(Clone, bevy::prelude::Resource)]
|
||||
pub struct $wrapper_type(std::sync::Arc<$xr_type>);
|
||||
|
||||
impl $wrapper_type {
|
||||
pub fn new(value: $xr_type) -> Self {
|
||||
Self(std::sync::Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for $wrapper_type {
|
||||
type Target = $xr_type;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$xr_type> for $wrapper_type {
|
||||
fn from(value: $xr_type) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub use xr_arc_resource_wrapper;
|
||||
pub use xr_resource_wrapper;
|
||||
|
||||
186
src/resources.rs
186
src/resources.rs
@@ -1,166 +1,32 @@
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Mutex;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::resource_macros::*;
|
||||
use bevy::prelude::*;
|
||||
use openxr as xr;
|
||||
use bevy::prelude::{Deref, DerefMut};
|
||||
|
||||
xr_resource_wrapper!(XrInstance, xr::Instance);
|
||||
xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>);
|
||||
xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
|
||||
xr_resource_wrapper!(XrResolution, UVec2);
|
||||
xr_resource_wrapper!(XrFormat, wgpu::TextureFormat);
|
||||
xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool);
|
||||
xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<xr::FrameWaiter>);
|
||||
xr_arc_resource_wrapper!(XrSwapchain, Swapchain);
|
||||
xr_arc_resource_wrapper!(XrFrameState, Mutex<xr::FrameState>);
|
||||
xr_arc_resource_wrapper!(XrViews, Mutex<Vec<xr::View>>);
|
||||
use crate::backend;
|
||||
|
||||
pub enum Swapchain {
|
||||
Vulkan(SwapchainInner<xr::Vulkan>),
|
||||
macro_rules! xr_resources {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident;
|
||||
)*
|
||||
) => {
|
||||
paste::paste! {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
#[derive(Clone, Deref, DerefMut)]
|
||||
pub struct $name(pub(crate) Rc<backend::[<$name Inner>]>);
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Swapchain {
|
||||
pub(crate) fn begin(&self) -> xr::Result<()> {
|
||||
match self {
|
||||
Swapchain::Vulkan(swapchain) => swapchain.begin(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
|
||||
match self {
|
||||
Swapchain::Vulkan(swapchain) => swapchain.get_render_views(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn acquire_image(&self) -> xr::Result<()> {
|
||||
match self {
|
||||
Swapchain::Vulkan(swapchain) => swapchain.acquire_image(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wait_image(&self) -> xr::Result<()> {
|
||||
match self {
|
||||
Swapchain::Vulkan(swapchain) => swapchain.wait_image(),
|
||||
}
|
||||
}
|
||||
|
||||
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(swapchain) => swapchain.end(
|
||||
predicted_display_time,
|
||||
views,
|
||||
stage,
|
||||
resolution,
|
||||
environment_blend_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SwapchainInner<G: xr::Graphics> {
|
||||
pub(crate) stream: Mutex<xr::FrameStream<G>>,
|
||||
pub(crate) handle: Mutex<xr::Swapchain<G>>,
|
||||
pub(crate) buffers: Vec<wgpu::Texture>,
|
||||
pub(crate) image_index: Mutex<usize>,
|
||||
}
|
||||
|
||||
impl<G: xr::Graphics> SwapchainInner<G> {
|
||||
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.lock().unwrap()];
|
||||
|
||||
(
|
||||
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
array_layer_count: Some(1),
|
||||
..Default::default()
|
||||
}),
|
||||
texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
array_layer_count: Some(1),
|
||||
base_array_layer: 1,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
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 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<()> {
|
||||
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();
|
||||
if views.len() == 0 {
|
||||
warn!("views are len of 0");
|
||||
return Ok(());
|
||||
}
|
||||
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),
|
||||
),
|
||||
])],
|
||||
)
|
||||
}
|
||||
xr_resources! {
|
||||
XrEntry;
|
||||
XrInstance;
|
||||
XrSession;
|
||||
XrInput;
|
||||
XrController;
|
||||
XrActionSpace;
|
||||
XrReferenceSpace;
|
||||
}
|
||||
|
||||
191
src/types.rs
Normal file
191
src/types.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use bevy::{
|
||||
render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue},
|
||||
window::RawHandleWrapper,
|
||||
};
|
||||
|
||||
/// This struct stores all of the render resources required to initialize the bevy render plugin
|
||||
///
|
||||
/// Returned from [XrSessionTrait::get_render_resources](crate::backend::traits::XrSessionTrait::get_render_resources)
|
||||
pub struct RenderResources {
|
||||
pub adapter: RenderAdapter,
|
||||
pub adapter_info: RenderAdapterInfo,
|
||||
pub device: RenderDevice,
|
||||
pub queue: RenderQueue,
|
||||
}
|
||||
|
||||
/// Information used to create the [XrSession](crate::resources::XrSession)
|
||||
///
|
||||
/// Passed into [XrInstanceTrait::create_session](crate::backend::traits::XrInstanceTrait::create_session)
|
||||
pub struct SessionCreateInfo {
|
||||
/// This field is required to be [Some] when using WebXR
|
||||
pub canvas: Option<String>,
|
||||
pub window: Option<RawHandleWrapper>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ControllerType {
|
||||
OculusTouch,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ActionSetCreateInfo {
|
||||
pub controller: ControllerType,
|
||||
// TODO!() allow custom fields
|
||||
}
|
||||
|
||||
pub struct ReferenceSpaceInfo {}
|
||||
|
||||
pub struct ActionSpaceInfo {}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct FeatureList {
|
||||
pub graphics: GraphicsFeatures,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct GraphicsFeatures {
|
||||
pub vulkan: bool,
|
||||
}
|
||||
|
||||
pub struct Pose;
|
||||
|
||||
pub struct Haptics;
|
||||
|
||||
pub enum ActionState {
|
||||
Bool(bool),
|
||||
Float(f32),
|
||||
Haptics(Haptics),
|
||||
Pose(Pose),
|
||||
}
|
||||
|
||||
pub enum ActionType {
|
||||
Bool,
|
||||
Float,
|
||||
Haptics,
|
||||
Pose,
|
||||
}
|
||||
|
||||
pub struct ActionId {
|
||||
pub action_path: ActionPath,
|
||||
pub side: Option<Side>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum ActionPath {
|
||||
HandPose,
|
||||
PointerPose,
|
||||
GripPull,
|
||||
TriggerPull,
|
||||
TriggerTouch,
|
||||
HapticFeedback,
|
||||
PrimaryButton,
|
||||
PrimaryButtonTouch,
|
||||
SecondaryButton,
|
||||
SecondaryButtonTouch,
|
||||
MenuButton,
|
||||
ThumbstickX,
|
||||
ThumbstickY,
|
||||
ThumbstickTouch,
|
||||
ThumbstickClick,
|
||||
ThumbrestTouch,
|
||||
}
|
||||
|
||||
impl ActionPath {
|
||||
pub fn action_name(&self) -> &'static str {
|
||||
use ActionPath::*;
|
||||
match self {
|
||||
HandPose => "hand_pose",
|
||||
PointerPose => "pointer_pose",
|
||||
GripPull => "grip_pull",
|
||||
TriggerPull => "trigger_pull",
|
||||
TriggerTouch => "trigger_touch",
|
||||
HapticFeedback => "haptic_feedback",
|
||||
PrimaryButton => "primary_button",
|
||||
PrimaryButtonTouch => "primary_button_touch",
|
||||
SecondaryButton => "secondary_button",
|
||||
SecondaryButtonTouch => "secondary_button_touch",
|
||||
MenuButton => "menu_button",
|
||||
ThumbstickX => "thumbstick_x",
|
||||
ThumbstickY => "thumbstick_y",
|
||||
ThumbstickTouch => "thumbstick_touch",
|
||||
ThumbstickClick => "thumbstick_click",
|
||||
ThumbrestTouch => "thumbrest_touch",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pretty_action_name(&self) -> &'static str {
|
||||
use ActionPath::*;
|
||||
match self {
|
||||
HandPose => "Hand Pose",
|
||||
PointerPose => "Pointer Pose",
|
||||
GripPull => "Grip Pull",
|
||||
TriggerPull => "Trigger Pull",
|
||||
TriggerTouch => "Trigger Touch",
|
||||
HapticFeedback => "Haptic Feedback",
|
||||
PrimaryButton => "Primary Button",
|
||||
PrimaryButtonTouch => "Primary Button Touch",
|
||||
SecondaryButton => "Secondary Button",
|
||||
SecondaryButtonTouch => "Secondary Button Touch",
|
||||
MenuButton => "Menu Button",
|
||||
ThumbstickX => "Thumbstick X",
|
||||
ThumbstickY => "Thumbstick Y",
|
||||
ThumbstickTouch => "Thumbstick Touch",
|
||||
ThumbstickClick => "Thumbstick Click",
|
||||
ThumbrestTouch => "Thumbrest Touch",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action_type(&self) -> ActionType {
|
||||
use ActionPath::*;
|
||||
match self {
|
||||
HandPose => ActionType::Pose,
|
||||
PointerPose => ActionType::Pose,
|
||||
GripPull => ActionType::Float,
|
||||
TriggerPull => ActionType::Float,
|
||||
TriggerTouch => ActionType::Bool,
|
||||
HapticFeedback => ActionType::Haptics,
|
||||
PrimaryButton => ActionType::Bool,
|
||||
PrimaryButtonTouch => ActionType::Bool,
|
||||
SecondaryButton => ActionType::Bool,
|
||||
SecondaryButtonTouch => ActionType::Bool,
|
||||
MenuButton => ActionType::Bool,
|
||||
ThumbstickX => ActionType::Float,
|
||||
ThumbstickY => ActionType::Float,
|
||||
ThumbstickTouch => ActionType::Bool,
|
||||
ThumbstickClick => ActionType::Bool,
|
||||
ThumbrestTouch => ActionType::Bool,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sidedness(&self) -> ActionSidedness {
|
||||
use ActionPath::*;
|
||||
match self {
|
||||
HandPose => ActionSidedness::Double,
|
||||
PointerPose => ActionSidedness::Double,
|
||||
GripPull => ActionSidedness::Double,
|
||||
TriggerPull => ActionSidedness::Double,
|
||||
TriggerTouch => ActionSidedness::Double,
|
||||
HapticFeedback => ActionSidedness::Double,
|
||||
PrimaryButton => ActionSidedness::Double,
|
||||
PrimaryButtonTouch => ActionSidedness::Double,
|
||||
SecondaryButton => ActionSidedness::Double,
|
||||
SecondaryButtonTouch => ActionSidedness::Double,
|
||||
MenuButton => ActionSidedness::Double,
|
||||
ThumbstickX => ActionSidedness::Double,
|
||||
ThumbstickY => ActionSidedness::Double,
|
||||
ThumbstickTouch => ActionSidedness::Double,
|
||||
ThumbstickClick => ActionSidedness::Double,
|
||||
ThumbrestTouch => ActionSidedness::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ActionSidedness {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
pub enum Side {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
364
src/xr_init.rs
364
src/xr_init.rs
@@ -1,364 +0,0 @@
|
||||
// Just a lot of code that is meant for something way more complex but hey.
|
||||
// maybe will work on that soon
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use bevy::{
|
||||
ecs::schedule::{ExecutorKind, ScheduleLabel},
|
||||
prelude::*,
|
||||
render::{
|
||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||
renderer::{
|
||||
self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
|
||||
},
|
||||
settings::WgpuSettings,
|
||||
},
|
||||
window::{PrimaryWindow, RawHandleWrapper},
|
||||
};
|
||||
use wgpu::Instance;
|
||||
|
||||
use crate::{
|
||||
graphics,
|
||||
input::XrInput,
|
||||
resources::{
|
||||
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
|
||||
XrSession, XrSessionRunning, XrSwapchain, XrViews,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct RenderCreationData {
|
||||
pub device: RenderDevice,
|
||||
pub queue: RenderQueue,
|
||||
pub adapter_info: RenderAdapterInfo,
|
||||
pub render_adapter: RenderAdapter,
|
||||
pub instance: Arc<Instance>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone, ExtractResource)]
|
||||
pub struct XrRenderData {
|
||||
pub xr_instance: XrInstance,
|
||||
pub xr_session: XrSession,
|
||||
pub xr_blend_mode: XrEnvironmentBlendMode,
|
||||
pub xr_resolution: XrResolution,
|
||||
pub xr_format: XrFormat,
|
||||
pub xr_session_running: XrSessionRunning,
|
||||
pub xr_frame_waiter: XrFrameWaiter,
|
||||
pub xr_swapchain: XrSwapchain,
|
||||
pub xr_input: XrInput,
|
||||
pub xr_views: XrViews,
|
||||
pub xr_frame_state: XrFrameState,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Copy, Debug)]
|
||||
pub enum XrEnableRequest {
|
||||
TryEnable,
|
||||
TryDisable,
|
||||
}
|
||||
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum XrEnableStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
Waiting,
|
||||
}
|
||||
|
||||
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum XrNextEnabledState {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub struct RenderRestartPlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ForceMain;
|
||||
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPreSetup;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrSetup;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPrePostSetup;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPostSetup;
|
||||
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPreCleanup;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrCleanup;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPostCleanup;
|
||||
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPreRenderUpdate;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrRenderUpdate;
|
||||
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct XrPostRenderUpdate;
|
||||
|
||||
pub fn xr_only() -> impl FnMut(Option<Res<'_, XrEnableStatus>>) -> bool {
|
||||
resource_exists_and_equals(XrEnableStatus::Enabled)
|
||||
}
|
||||
|
||||
impl Plugin for RenderRestartPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
add_schedules(app);
|
||||
app.add_plugins(ExtractResourcePlugin::<XrRenderData>::default())
|
||||
.insert_resource(ForceMain)
|
||||
.add_event::<XrEnableRequest>()
|
||||
.add_event::<XrEnableStatus>()
|
||||
.add_systems(PreStartup, xr_presetup.run_if(xr_only()))
|
||||
.add_systems(Startup, xr_setup.run_if(xr_only()))
|
||||
.add_systems(PostStartup, xr_postsetup.run_if(xr_only()))
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
update_xr_stuff.run_if(on_event::<XrEnableRequest>()),
|
||||
)
|
||||
.add_systems(XrPreRenderUpdate, decide_next_xr_state)
|
||||
.add_systems(XrPostRenderUpdate, clear_events)
|
||||
.add_systems(
|
||||
XrRenderUpdate,
|
||||
(
|
||||
cleanup_xr.run_if(resource_exists_and_equals(XrNextEnabledState::Disabled)),
|
||||
// handle_xr_enable_requests,
|
||||
apply_deferred,
|
||||
setup_xr, /* .run_if(resource_exists_and_equals(XrEnableStatus::Enabled)) */
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.add_systems(XrCleanup, cleanup_oxr_session);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_events(mut events: ResMut<Events<XrEnableRequest>>) {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
fn add_schedules(app: &mut App) {
|
||||
let schedules = [
|
||||
Schedule::new(XrPreSetup),
|
||||
Schedule::new(XrSetup),
|
||||
Schedule::new(XrPrePostSetup),
|
||||
Schedule::new(XrPostSetup),
|
||||
Schedule::new(XrPreRenderUpdate),
|
||||
Schedule::new(XrRenderUpdate),
|
||||
Schedule::new(XrPostRenderUpdate),
|
||||
Schedule::new(XrPreCleanup),
|
||||
Schedule::new(XrCleanup),
|
||||
Schedule::new(XrPostCleanup),
|
||||
];
|
||||
for mut schedule in schedules {
|
||||
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
|
||||
schedule.set_apply_final_deferred(true);
|
||||
app.add_schedule(schedule);
|
||||
}
|
||||
}
|
||||
|
||||
fn xr_presetup(world: &mut World) {
|
||||
world.run_schedule(XrPreSetup);
|
||||
}
|
||||
fn xr_setup(world: &mut World) {
|
||||
world.run_schedule(XrSetup);
|
||||
}
|
||||
fn xr_postsetup(world: &mut World) {
|
||||
world.run_schedule(XrPrePostSetup);
|
||||
world.run_schedule(XrPostSetup);
|
||||
}
|
||||
|
||||
fn setup_xr(world: &mut World) {
|
||||
world.run_schedule(XrPreSetup);
|
||||
world.run_schedule(XrSetup);
|
||||
world.run_schedule(XrPrePostSetup);
|
||||
world.run_schedule(XrPostSetup);
|
||||
}
|
||||
fn cleanup_xr(world: &mut World) {
|
||||
world.run_schedule(XrPreCleanup);
|
||||
world.run_schedule(XrCleanup);
|
||||
world.run_schedule(XrPostCleanup);
|
||||
}
|
||||
|
||||
fn cleanup_oxr_session(xr_status: Option<Res<XrEnableStatus>>, session: Option<ResMut<XrSession>>) {
|
||||
if let (Some(XrEnableStatus::Disabled), Some(s)) = (xr_status.map(|v| v.into_inner()), session)
|
||||
{
|
||||
s.into_inner().request_exit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_xr_stuff(world: &mut World) {
|
||||
world.run_schedule(XrPreRenderUpdate);
|
||||
world.run_schedule(XrRenderUpdate);
|
||||
world.run_schedule(XrPostRenderUpdate);
|
||||
}
|
||||
|
||||
// fn handle_xr_enable_requests(
|
||||
// primary_window: Query<&RawHandleWrapper, With<PrimaryWindow>>,
|
||||
// mut commands: Commands,
|
||||
// next_state: Res<XrNextEnabledState>,
|
||||
// on_main: Option<NonSend<ForceMain>>,
|
||||
// ) {
|
||||
// // Just to force this system onto the main thread because of unsafe code
|
||||
// let _ = on_main;
|
||||
//
|
||||
// commands.insert_resource(XrEnableStatus::Waiting);
|
||||
// let (creation_data, xr_data) = match next_state.into_inner() {
|
||||
// XrNextEnabledState::Enabled => {
|
||||
// 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.get_single().ok().cloned())
|
||||
// .unwrap();
|
||||
//
|
||||
// commands.insert_resource(XrEnableStatus::Enabled);
|
||||
// (
|
||||
// RenderCreationData {
|
||||
// device,
|
||||
// queue,
|
||||
// adapter_info,
|
||||
// render_adapter,
|
||||
// instance: Arc::new(instance),
|
||||
// },
|
||||
// Some(XrRenderData {
|
||||
// xr_instance,
|
||||
// xr_session: session,
|
||||
// xr_blend_mode: blend_mode,
|
||||
// xr_resolution: resolution,
|
||||
// xr_format: format,
|
||||
// xr_session_running: session_running,
|
||||
// xr_frame_waiter: frame_waiter,
|
||||
// xr_swapchain: swapchain,
|
||||
// xr_input: input,
|
||||
// xr_views: views,
|
||||
// xr_frame_state: frame_state,
|
||||
// }),
|
||||
// )
|
||||
// }
|
||||
// XrNextEnabledState::Disabled => (
|
||||
// init_non_xr_graphics(primary_window.get_single().ok().cloned()),
|
||||
// None,
|
||||
// ),
|
||||
// };
|
||||
//
|
||||
// commands.insert_resource(creation_data.device);
|
||||
// commands.insert_resource(creation_data.queue);
|
||||
// commands.insert_resource(RenderInstance(creation_data.instance));
|
||||
// commands.insert_resource(creation_data.adapter_info);
|
||||
// commands.insert_resource(creation_data.render_adapter);
|
||||
//
|
||||
// if let Some(xr_data) = xr_data {
|
||||
// // TODO: Remove this lib.rs:144
|
||||
// commands.insert_resource(xr_data.clone());
|
||||
//
|
||||
// commands.insert_resource(xr_data.xr_instance);
|
||||
// commands.insert_resource(xr_data.xr_session);
|
||||
// commands.insert_resource(xr_data.xr_blend_mode);
|
||||
// commands.insert_resource(xr_data.xr_resolution);
|
||||
// commands.insert_resource(xr_data.xr_format);
|
||||
// commands.insert_resource(xr_data.xr_session_running);
|
||||
// commands.insert_resource(xr_data.xr_frame_waiter);
|
||||
// commands.insert_resource(xr_data.xr_input);
|
||||
// commands.insert_resource(xr_data.xr_views);
|
||||
// commands.insert_resource(xr_data.xr_frame_state);
|
||||
// commands.insert_resource(xr_data.xr_swapchain);
|
||||
// } else {
|
||||
// commands.remove_resource::<XrRenderData>();
|
||||
//
|
||||
// commands.remove_resource::<XrInstance>();
|
||||
// commands.remove_resource::<XrSession>();
|
||||
// commands.remove_resource::<XrEnvironmentBlendMode>();
|
||||
// commands.remove_resource::<XrResolution>();
|
||||
// commands.remove_resource::<XrFormat>();
|
||||
// commands.remove_resource::<XrSessionRunning>();
|
||||
// commands.remove_resource::<XrFrameWaiter>();
|
||||
// commands.remove_resource::<XrSwapchain>();
|
||||
// commands.remove_resource::<XrInput>();
|
||||
// commands.remove_resource::<XrViews>();
|
||||
// commands.remove_resource::<XrFrameState>();
|
||||
// }
|
||||
// }
|
||||
|
||||
fn decide_next_xr_state(
|
||||
mut commands: Commands,
|
||||
mut events: EventReader<XrEnableRequest>,
|
||||
xr_status: Option<Res<XrEnableStatus>>,
|
||||
) {
|
||||
info!("hm");
|
||||
let request = match events.read().next() {
|
||||
Some(v) => v,
|
||||
None => return,
|
||||
};
|
||||
info!("ok");
|
||||
match (request, xr_status.as_deref()) {
|
||||
(XrEnableRequest::TryEnable, Some(XrEnableStatus::Enabled)) => {
|
||||
info!("Xr Already Enabled! ignoring request");
|
||||
return;
|
||||
}
|
||||
(XrEnableRequest::TryDisable, Some(XrEnableStatus::Disabled)) => {
|
||||
info!("Xr Already Disabled! ignoring request");
|
||||
return;
|
||||
}
|
||||
(_, Some(XrEnableStatus::Waiting)) => {
|
||||
info!("Already Handling Request! ignoring request");
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let r = match request {
|
||||
XrEnableRequest::TryEnable => XrNextEnabledState::Enabled,
|
||||
XrEnableRequest::TryDisable => XrNextEnabledState::Disabled,
|
||||
};
|
||||
info!("{:#?}", r);
|
||||
commands.insert_resource(r);
|
||||
}
|
||||
|
||||
pub fn init_non_xr_graphics(primary_window: Option<RawHandleWrapper>) -> RenderCreationData {
|
||||
let settings = WgpuSettings::default();
|
||||
|
||||
let async_renderer = async move {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
// Probably a bad idea unwraping here but on the other hand no backends
|
||||
backends: settings.backends.unwrap(),
|
||||
dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
|
||||
});
|
||||
let surface = primary_window.map(|wrapper| unsafe {
|
||||
// SAFETY: Plugins should be set up on the main thread.
|
||||
let handle = wrapper.get_handle();
|
||||
instance
|
||||
.create_surface(&handle)
|
||||
.expect("Failed to create wgpu surface")
|
||||
});
|
||||
|
||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: settings.power_preference,
|
||||
compatible_surface: surface.as_ref(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (device, queue, adapter_info, render_adapter) =
|
||||
renderer::initialize_renderer(&instance, &settings, &request_adapter_options).await;
|
||||
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
|
||||
debug!("Configured wgpu adapter Features: {:#?}", device.features());
|
||||
RenderCreationData {
|
||||
device,
|
||||
queue,
|
||||
adapter_info,
|
||||
render_adapter,
|
||||
instance: Arc::new(instance),
|
||||
}
|
||||
};
|
||||
// No need for wasm in bevy_oxr web xr would be a different crate
|
||||
futures_lite::future::block_on(async_renderer)
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
use bevy::{prelude::*, utils::HashMap};
|
||||
use openxr as xr;
|
||||
use xr::{Action, Binding, Haptic, Posef};
|
||||
|
||||
use crate::{resources::{XrInstance, XrSession}, xr_init::XrPrePostSetup};
|
||||
|
||||
use super::oculus_touch::ActionSets;
|
||||
|
||||
pub struct OpenXrActionsPlugin;
|
||||
impl Plugin for OpenXrActionsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(SetupActionSets {
|
||||
sets: HashMap::new(),
|
||||
});
|
||||
app.add_systems(XrPrePostSetup, setup_oxr_actions);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_oxr_actions(world: &mut World) {
|
||||
let actions = world.remove_resource::<SetupActionSets>().unwrap();
|
||||
let instance = world.get_resource::<XrInstance>().unwrap();
|
||||
let session = world.get_resource::<XrSession>().unwrap();
|
||||
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 mut oxr_action_sets = Vec::new();
|
||||
let mut action_sets = XrActionSets { sets: default() };
|
||||
// let mut action_bindings: HashMap<&'static str, Vec<xr::Path>> = HashMap::new();
|
||||
let mut a_iter = actions.sets.into_iter();
|
||||
let mut action_bindings: HashMap<
|
||||
(&'static str, &'static str),
|
||||
HashMap<&'static str, Vec<xr::Path>>,
|
||||
> = HashMap::new();
|
||||
while let Some((set_name, set)) = a_iter.next() {
|
||||
let mut actions: HashMap<&'static str, TypedAction> = default();
|
||||
let oxr_action_set = instance
|
||||
.create_action_set(set_name, set.pretty_name, set.priority)
|
||||
.expect("Unable to create action set");
|
||||
for (action_name, action) in set.actions.into_iter() {
|
||||
let typed_action = match action.action_type {
|
||||
ActionType::F32 => TypedAction::F32(match action.handednes {
|
||||
ActionHandednes::Single => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &[])
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
ActionHandednes::Double => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &hands)
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
}),
|
||||
ActionType::Bool => TypedAction::Bool(match action.handednes {
|
||||
ActionHandednes::Single => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &[])
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
ActionHandednes::Double => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &hands)
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
}),
|
||||
ActionType::PoseF => TypedAction::PoseF(match action.handednes {
|
||||
ActionHandednes::Single => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &[])
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
ActionHandednes::Double => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &hands)
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
}),
|
||||
ActionType::Haptic => TypedAction::Haptic(match action.handednes {
|
||||
ActionHandednes::Single => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &[])
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
ActionHandednes::Double => oxr_action_set
|
||||
.create_action(action_name, action.pretty_name, &hands)
|
||||
.expect(&format!("Unable to create action: {}", action_name)),
|
||||
}),
|
||||
};
|
||||
actions.insert(action_name, typed_action);
|
||||
for (device_path, bindings) in action.bindings.into_iter() {
|
||||
for b in bindings {
|
||||
info!("binding {} to {}", action_name, b);
|
||||
action_bindings
|
||||
.entry((set_name, action_name))
|
||||
.or_default()
|
||||
.entry(device_path)
|
||||
.or_default()
|
||||
.push(instance.string_to_path(b).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
oxr_action_sets.push(oxr_action_set);
|
||||
action_sets.sets.insert(
|
||||
set_name,
|
||||
ActionSet {
|
||||
// oxr_action_set,
|
||||
actions,
|
||||
enabled: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut b_indings: HashMap<&'static str, Vec<Binding>> = HashMap::new();
|
||||
for (dev, mut bindings) in action_sets
|
||||
.sets
|
||||
.iter()
|
||||
.flat_map(|(set_name, set)| {
|
||||
set.actions
|
||||
.iter()
|
||||
.map(move |(action_name, a)| (set_name, action_name, a))
|
||||
})
|
||||
.zip([&action_bindings].into_iter().cycle())
|
||||
.flat_map(move |((set_name, action_name, action), bindings)| {
|
||||
bindings
|
||||
.get(&(set_name.clone(), action_name.clone()))
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(move |(dev, bindings)| (action.clone(), dev.clone(), bindings))
|
||||
})
|
||||
.map(|(action, dev, bindings)| {
|
||||
info!("Hi");
|
||||
(
|
||||
dev,
|
||||
bindings
|
||||
.into_iter()
|
||||
.map(move |binding| match &action {
|
||||
TypedAction::F32(a) => Binding::new(a, *binding),
|
||||
TypedAction::Bool(a) => Binding::new(a, *binding),
|
||||
TypedAction::PoseF(a) => Binding::new(a, *binding),
|
||||
TypedAction::Haptic(a) => Binding::new(a, *binding),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
{
|
||||
b_indings.entry(dev).or_default().append(&mut bindings);
|
||||
}
|
||||
for (dev, bindings) in b_indings.into_iter() {
|
||||
info!(dev);
|
||||
instance
|
||||
.suggest_interaction_profile_bindings(instance.string_to_path(dev).unwrap(), &bindings)
|
||||
.expect("Unable to suggest interaction bindings!");
|
||||
}
|
||||
session
|
||||
.attach_action_sets(&oxr_action_sets.iter().collect::<Vec<_>>())
|
||||
.expect("Unable to attach action sets!");
|
||||
|
||||
world.insert_resource(ActionSets(oxr_action_sets));
|
||||
world.insert_resource(action_sets);
|
||||
}
|
||||
|
||||
pub enum ActionHandednes {
|
||||
Single,
|
||||
Double,
|
||||
}
|
||||
|
||||
pub enum ActionType {
|
||||
F32,
|
||||
Bool,
|
||||
PoseF,
|
||||
Haptic,
|
||||
}
|
||||
|
||||
pub enum TypedAction {
|
||||
F32(Action<f32>),
|
||||
Bool(Action<bool>),
|
||||
PoseF(Action<Posef>),
|
||||
Haptic(Action<Haptic>),
|
||||
}
|
||||
|
||||
pub struct SetupAction {
|
||||
pretty_name: &'static str,
|
||||
action_type: ActionType,
|
||||
handednes: ActionHandednes,
|
||||
bindings: HashMap<&'static str, Vec<&'static str>>,
|
||||
}
|
||||
|
||||
pub struct SetupActionSet {
|
||||
pretty_name: &'static str,
|
||||
priority: u32,
|
||||
actions: HashMap<&'static str, SetupAction>,
|
||||
}
|
||||
|
||||
impl SetupActionSet {
|
||||
pub fn new_action(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
pretty_name: &'static str,
|
||||
action_type: ActionType,
|
||||
handednes: ActionHandednes,
|
||||
) {
|
||||
self.actions.insert(
|
||||
name,
|
||||
SetupAction {
|
||||
pretty_name,
|
||||
action_type,
|
||||
handednes,
|
||||
bindings: default(),
|
||||
},
|
||||
);
|
||||
}
|
||||
pub fn suggest_binding(&mut self, device_path: &'static str, bindings: &[XrBinding]) {
|
||||
for binding in bindings {
|
||||
self.actions
|
||||
.get_mut(binding.action)
|
||||
.ok_or(anyhow::anyhow!("Missing Action: {}", binding.action))
|
||||
.unwrap()
|
||||
.bindings
|
||||
.entry(device_path)
|
||||
.or_default()
|
||||
.push(binding.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct XrBinding {
|
||||
action: &'static str,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
impl XrBinding {
|
||||
pub fn new(action_name: &'static str, binding_path: &'static str) -> XrBinding {
|
||||
XrBinding {
|
||||
action: action_name,
|
||||
path: binding_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct SetupActionSets {
|
||||
sets: HashMap<&'static str, SetupActionSet>,
|
||||
}
|
||||
|
||||
impl SetupActionSets {
|
||||
pub fn add_action_set(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
pretty_name: &'static str,
|
||||
priority: u32,
|
||||
) -> &mut SetupActionSet {
|
||||
self.sets.insert(
|
||||
name,
|
||||
SetupActionSet {
|
||||
pretty_name,
|
||||
priority,
|
||||
actions: HashMap::new(),
|
||||
},
|
||||
);
|
||||
self.sets.get_mut(name).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActionSet {
|
||||
// oxr_action_set: xr::ActionSet,
|
||||
enabled: bool,
|
||||
actions: HashMap<&'static str, TypedAction>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct XrActionSets {
|
||||
sets: HashMap<&'static str, ActionSet>,
|
||||
}
|
||||
|
||||
impl XrActionSets {
|
||||
pub fn get_action_f32(
|
||||
&self,
|
||||
action_set: &'static str,
|
||||
action_name: &'static str,
|
||||
) -> anyhow::Result<&Action<f32>> {
|
||||
let action = self
|
||||
.sets
|
||||
.get(action_set)
|
||||
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
|
||||
.actions
|
||||
.get(action_name)
|
||||
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
|
||||
match action {
|
||||
TypedAction::F32(a) => Ok(a),
|
||||
_ => anyhow::bail!("wrong action type"),
|
||||
}
|
||||
}
|
||||
pub fn get_action_bool(
|
||||
&self,
|
||||
action_set: &'static str,
|
||||
action_name: &'static str,
|
||||
) -> anyhow::Result<&Action<bool>> {
|
||||
let action = self
|
||||
.sets
|
||||
.get(action_set)
|
||||
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
|
||||
.actions
|
||||
.get(action_name)
|
||||
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
|
||||
match action {
|
||||
TypedAction::Bool(a) => Ok(a),
|
||||
_ => anyhow::bail!("wrong action type"),
|
||||
}
|
||||
}
|
||||
pub fn get_action_posef(
|
||||
&self,
|
||||
action_set: &'static str,
|
||||
action_name: &'static str,
|
||||
) -> anyhow::Result<&Action<Posef>> {
|
||||
let action = self
|
||||
.sets
|
||||
.get(action_set)
|
||||
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
|
||||
.actions
|
||||
.get(action_name)
|
||||
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
|
||||
match action {
|
||||
TypedAction::PoseF(a) => Ok(a),
|
||||
_ => anyhow::bail!("wrong action type"),
|
||||
}
|
||||
}
|
||||
pub fn get_action_haptic(
|
||||
&self,
|
||||
action_set: &'static str,
|
||||
action_name: &'static str,
|
||||
) -> anyhow::Result<&Action<Haptic>> {
|
||||
let action = self
|
||||
.sets
|
||||
.get(action_set)
|
||||
.ok_or(anyhow::anyhow!("Action Set Not Found!"))?
|
||||
.actions
|
||||
.get(action_name)
|
||||
.ok_or(anyhow::anyhow!("Action Not Found!"))?;
|
||||
match action {
|
||||
TypedAction::Haptic(a) => Ok(a),
|
||||
_ => anyhow::bail!("wrong action type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
use openxr::{Action, ActionTy};
|
||||
|
||||
pub struct Touchable<T: ActionTy> {
|
||||
pub inner: Action<T>,
|
||||
pub touch: Action<bool>,
|
||||
}
|
||||
pub struct Handed<T> {
|
||||
pub left: T,
|
||||
pub right: T,
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum XrControllerType {
|
||||
OculusTouch,
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
use bevy::ecs::schedule::IntoSystemConfigs;
|
||||
use bevy::log::{debug, info};
|
||||
use bevy::prelude::{
|
||||
Color, Gizmos, GlobalTransform, Plugin, Quat, Query, Res, Transform, Update, Vec2, Vec3, With,
|
||||
Without,
|
||||
};
|
||||
|
||||
use crate::xr_init::xr_only;
|
||||
use crate::{
|
||||
input::XrInput,
|
||||
resources::{XrFrameState, XrSession},
|
||||
};
|
||||
|
||||
use crate::xr_input::{
|
||||
oculus_touch::{OculusController, OculusControllerRef},
|
||||
Hand,
|
||||
};
|
||||
|
||||
use super::{
|
||||
actions::XrActionSets,
|
||||
trackers::{OpenXRLeftController, OpenXRRightController, OpenXRTrackingRoot},
|
||||
};
|
||||
|
||||
/// add debug renderer for controllers
|
||||
#[derive(Default)]
|
||||
pub struct OpenXrDebugRenderer;
|
||||
|
||||
impl Plugin for OpenXrDebugRenderer {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_systems(Update, draw_gizmos.run_if(xr_only()));
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments, clippy::complexity)]
|
||||
pub fn draw_gizmos(
|
||||
mut gizmos: Gizmos,
|
||||
oculus_controller: Res<OculusController>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
tracking_root_query: Query<
|
||||
&mut Transform,
|
||||
(
|
||||
With<OpenXRTrackingRoot>,
|
||||
Without<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
),
|
||||
>,
|
||||
left_controller_query: Query<
|
||||
&GlobalTransform,
|
||||
(
|
||||
With<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
Without<OpenXRTrackingRoot>,
|
||||
),
|
||||
>,
|
||||
right_controller_query: Query<
|
||||
&GlobalTransform,
|
||||
(
|
||||
With<OpenXRRightController>,
|
||||
Without<OpenXRLeftController>,
|
||||
Without<OpenXRTrackingRoot>,
|
||||
),
|
||||
>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
// if let Some(hand_tracking) = hand_tracking {
|
||||
// let handtracking_ref = hand_tracking.get_ref(&xr_input, &frame_state);
|
||||
// if let Some(joints) = handtracking_ref.get_poses(Hand::Left) {
|
||||
// for joint in joints.inner() {
|
||||
// let trans = Transform::from_rotation(joint.orientation);
|
||||
// gizmos.circle(
|
||||
// joint.position,
|
||||
// trans.forward(),
|
||||
// joint.radius,
|
||||
// Color::ORANGE_RED,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// if let Some(joints) = handtracking_ref.get_poses(Hand::Right) {
|
||||
// for joint in joints.inner() {
|
||||
// let trans = Transform::from_rotation(joint.orientation);
|
||||
// gizmos.circle(
|
||||
// joint.position,
|
||||
// trans.forward(),
|
||||
// joint.radius,
|
||||
// Color::LIME_GREEN,
|
||||
// );
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//lock frame
|
||||
let frame_state = *frame_state.lock().unwrap();
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
let root = tracking_root_query.get_single();
|
||||
match root {
|
||||
Ok(position) => {
|
||||
gizmos.circle(
|
||||
position.translation
|
||||
+ Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.01,
|
||||
z: 0.0,
|
||||
},
|
||||
Vec3::Y,
|
||||
0.2,
|
||||
Color::RED,
|
||||
);
|
||||
}
|
||||
Err(_) => info!("too many tracking roots"),
|
||||
}
|
||||
//draw the hands
|
||||
//left
|
||||
let left_transform = left_controller_query.get_single();
|
||||
match left_transform {
|
||||
Ok(left_entity) => {
|
||||
draw_hand_gizmo(&mut gizmos, &controller, Hand::Left, left_entity);
|
||||
}
|
||||
Err(_) => debug!("no left controller entity for debug gizmos"),
|
||||
}
|
||||
//right
|
||||
let right_transform = right_controller_query.get_single();
|
||||
match right_transform {
|
||||
Ok(right_entity) => {
|
||||
draw_hand_gizmo(&mut gizmos, &controller, Hand::Right, right_entity);
|
||||
}
|
||||
Err(_) => debug!("no right controller entity for debug gizmos"),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_hand_gizmo(
|
||||
gizmos: &mut Gizmos,
|
||||
controller: &OculusControllerRef<'_>,
|
||||
hand: Hand,
|
||||
hand_transform: &GlobalTransform,
|
||||
) {
|
||||
match hand {
|
||||
Hand::Left => {
|
||||
let left_color = Color::YELLOW_GREEN;
|
||||
let off_color = Color::BLUE;
|
||||
let touch_color = Color::GREEN;
|
||||
let pressed_color = Color::RED;
|
||||
|
||||
let grip_quat_offset = Quat::from_rotation_x(-1.4);
|
||||
let face_quat_offset = Quat::from_rotation_x(1.05);
|
||||
let trans = hand_transform.compute_transform();
|
||||
let controller_vec3 = trans.translation;
|
||||
let controller_quat = trans.rotation;
|
||||
let face_quat = controller_quat.mul_quat(face_quat_offset);
|
||||
let face_quat_normal = face_quat.mul_vec3(Vec3::Z);
|
||||
|
||||
//draw grip
|
||||
gizmos.rect(
|
||||
controller_vec3,
|
||||
controller_quat * grip_quat_offset,
|
||||
Vec2::new(0.05, 0.1),
|
||||
left_color,
|
||||
);
|
||||
|
||||
let face_translation_offset = Quat::from_rotation_x(-1.7); //direction to move the face from the controller tracking point
|
||||
let face_translation_vec3 = controller_vec3
|
||||
+ controller_quat
|
||||
.mul_quat(face_translation_offset)
|
||||
.mul_vec3(Vec3::Y * 0.075); //distance to move face by
|
||||
|
||||
//draw face
|
||||
gizmos.circle(
|
||||
face_translation_vec3,
|
||||
face_quat_normal,
|
||||
0.04,
|
||||
Color::YELLOW_GREEN,
|
||||
);
|
||||
|
||||
//button b
|
||||
let mut b_color = off_color;
|
||||
if controller.y_button_touched() {
|
||||
b_color = touch_color;
|
||||
}
|
||||
if controller.y_button() {
|
||||
b_color = pressed_color;
|
||||
}
|
||||
|
||||
let b_offset_quat = face_quat;
|
||||
let b_translation_vec3 =
|
||||
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(0.025, -0.01, 0.0));
|
||||
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color);
|
||||
|
||||
//button a
|
||||
let mut a_color = off_color;
|
||||
if controller.x_button_touched() {
|
||||
a_color = touch_color;
|
||||
}
|
||||
if controller.x_button() {
|
||||
a_color = pressed_color;
|
||||
}
|
||||
|
||||
let a_offset_quat = face_quat;
|
||||
let a_translation_vec3 =
|
||||
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(0.025, 0.01, 0.0));
|
||||
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color);
|
||||
|
||||
//joystick
|
||||
let joystick_offset_quat = face_quat;
|
||||
let joystick_base_vec =
|
||||
face_translation_vec3 + joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, 0.0));
|
||||
let mut joystick_color = off_color;
|
||||
if controller.thumbstick_touch(Hand::Left) {
|
||||
joystick_color = touch_color;
|
||||
}
|
||||
|
||||
//base
|
||||
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color);
|
||||
|
||||
let stick = controller.thumbstick(Hand::Left);
|
||||
let input = Vec3::new(stick.x, -stick.y, 0.0);
|
||||
let joystick_top_vec = face_translation_vec3
|
||||
+ joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, -0.01))
|
||||
+ joystick_offset_quat.mul_vec3(input * 0.01);
|
||||
//top
|
||||
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color);
|
||||
|
||||
//trigger
|
||||
let trigger_state = controller.trigger(Hand::Left);
|
||||
let trigger_rotation = Quat::from_rotation_x(-0.75 * trigger_state);
|
||||
let mut trigger_color = off_color;
|
||||
if controller.trigger_touched(Hand::Left) {
|
||||
trigger_color = touch_color;
|
||||
}
|
||||
let trigger_transform = Transform {
|
||||
translation: face_translation_vec3
|
||||
+ face_quat
|
||||
.mul_quat(trigger_rotation)
|
||||
.mul_vec3(Vec3::new(0.0, 0.0, 0.02)),
|
||||
rotation: face_quat.mul_quat(trigger_rotation),
|
||||
scale: Vec3 {
|
||||
x: 0.01,
|
||||
y: 0.02,
|
||||
z: 0.03,
|
||||
},
|
||||
};
|
||||
gizmos.cuboid(trigger_transform, trigger_color);
|
||||
}
|
||||
Hand::Right => {
|
||||
//get right controller
|
||||
let right_color = Color::YELLOW_GREEN;
|
||||
let off_color = Color::BLUE;
|
||||
let touch_color = Color::GREEN;
|
||||
let pressed_color = Color::RED;
|
||||
|
||||
let grip_quat_offset = Quat::from_rotation_x(-1.4);
|
||||
let face_quat_offset = Quat::from_rotation_x(1.05);
|
||||
|
||||
let trans = hand_transform.compute_transform();
|
||||
let controller_vec3 = trans.translation;
|
||||
let controller_quat = trans.rotation;
|
||||
let face_quat = controller_quat.mul_quat(face_quat_offset);
|
||||
let face_quat_normal = face_quat.mul_vec3(Vec3::Z);
|
||||
|
||||
let _squeeze = controller.squeeze(Hand::Right);
|
||||
//info!("{:?}", squeeze);
|
||||
//grip
|
||||
gizmos.rect(
|
||||
controller_vec3,
|
||||
controller_quat * grip_quat_offset,
|
||||
Vec2::new(0.05, 0.1),
|
||||
right_color,
|
||||
);
|
||||
|
||||
let face_translation_offset = Quat::from_rotation_x(-1.7); //direction to move the face from the controller tracking point
|
||||
let face_translation_vec3 = controller_vec3
|
||||
+ controller_quat
|
||||
.mul_quat(face_translation_offset)
|
||||
.mul_vec3(Vec3::Y * 0.075); //distance to move face by
|
||||
|
||||
//draw face
|
||||
gizmos.circle(
|
||||
face_translation_vec3,
|
||||
face_quat_normal,
|
||||
0.04,
|
||||
Color::YELLOW_GREEN,
|
||||
);
|
||||
|
||||
//button b
|
||||
let mut b_color = off_color;
|
||||
if controller.b_button_touched() {
|
||||
b_color = touch_color;
|
||||
}
|
||||
if controller.b_button() {
|
||||
b_color = pressed_color;
|
||||
}
|
||||
|
||||
let b_offset_quat = face_quat;
|
||||
let b_translation_vec3 =
|
||||
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(-0.025, -0.01, 0.0));
|
||||
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color);
|
||||
|
||||
//button a
|
||||
let mut a_color = off_color;
|
||||
if controller.a_button_touched() {
|
||||
a_color = touch_color;
|
||||
}
|
||||
if controller.a_button() {
|
||||
a_color = pressed_color;
|
||||
}
|
||||
|
||||
let a_offset_quat = face_quat;
|
||||
let a_translation_vec3 =
|
||||
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(-0.025, 0.01, 0.0));
|
||||
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color);
|
||||
|
||||
//joystick time
|
||||
let joystick_offset_quat = face_quat;
|
||||
let joystick_base_vec =
|
||||
face_translation_vec3 + joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, 0.0));
|
||||
let mut joystick_color = off_color;
|
||||
if controller.thumbstick_touch(Hand::Right) {
|
||||
joystick_color = touch_color;
|
||||
}
|
||||
|
||||
//base
|
||||
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color);
|
||||
|
||||
let stick = controller.thumbstick(Hand::Right);
|
||||
let input = Vec3::new(stick.x, -stick.y, 0.0);
|
||||
let joystick_top_vec = face_translation_vec3
|
||||
+ joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, -0.01))
|
||||
+ joystick_offset_quat.mul_vec3(input * 0.01);
|
||||
//top
|
||||
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color);
|
||||
|
||||
//trigger
|
||||
let trigger_state = controller.trigger(Hand::Right);
|
||||
let trigger_rotation = Quat::from_rotation_x(-0.75 * trigger_state);
|
||||
let mut trigger_color = off_color;
|
||||
if controller.trigger_touched(Hand::Right) {
|
||||
trigger_color = touch_color;
|
||||
}
|
||||
let trigger_transform = Transform {
|
||||
translation: face_translation_vec3
|
||||
+ face_quat
|
||||
.mul_quat(trigger_rotation)
|
||||
.mul_vec3(Vec3::new(0.0, 0.0, 0.02)),
|
||||
rotation: face_quat.mul_quat(trigger_rotation),
|
||||
scale: Vec3 {
|
||||
x: 0.01,
|
||||
y: 0.02,
|
||||
z: 0.03,
|
||||
},
|
||||
};
|
||||
gizmos.cuboid(trigger_transform, trigger_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,520 +0,0 @@
|
||||
use bevy::prelude::{Quat, Transform, Vec3};
|
||||
use openxr::{Posef, Quaternionf, Vector3f};
|
||||
|
||||
use super::Hand;
|
||||
|
||||
pub fn get_simulated_open_hand_transforms(hand: Hand) -> [Transform; 26] {
|
||||
let test_hand_bones: [Vec3; 26] = [
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
}, //palm
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: -0.04,
|
||||
}, //wrist
|
||||
Vec3 {
|
||||
x: -0.02,
|
||||
y: 0.00,
|
||||
z: 0.015,
|
||||
}, //thumb
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.03,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.024,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.024,
|
||||
},
|
||||
Vec3 {
|
||||
x: -0.01,
|
||||
y: -0.015,
|
||||
z: 0.0155,
|
||||
}, //index
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.064,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.037,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.02,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.01,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: -0.02,
|
||||
z: 0.016,
|
||||
}, //middle
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.064,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.037,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.02,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.01,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.01,
|
||||
y: -0.015,
|
||||
z: 0.015,
|
||||
}, //ring
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.064,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.037,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.02,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.01,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.02,
|
||||
y: -0.01,
|
||||
z: 0.015,
|
||||
}, //little
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.064,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.037,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.02,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.01,
|
||||
},
|
||||
];
|
||||
let result = bones_to_transforms(test_hand_bones, hand);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bones_to_transforms(hand_bones: [Vec3; 26], hand: Hand) -> [Transform; 26] {
|
||||
match hand {
|
||||
Hand::Left => {
|
||||
let mut result_array: [Transform; 26] = [Transform::default(); 26];
|
||||
for (place, data) in result_array.iter_mut().zip(hand_bones.iter()) {
|
||||
*place = Transform {
|
||||
translation: Vec3 {
|
||||
x: -data.x,
|
||||
y: -data.y,
|
||||
z: -data.z,
|
||||
},
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::splat(1.0),
|
||||
}
|
||||
}
|
||||
return result_array;
|
||||
}
|
||||
Hand::Right => {
|
||||
let mut result_array: [Transform; 26] = [Transform::default(); 26];
|
||||
for (place, data) in result_array.iter_mut().zip(hand_bones.iter()) {
|
||||
*place = Transform {
|
||||
translation: Vec3 {
|
||||
x: data.x,
|
||||
y: -data.y,
|
||||
z: -data.z,
|
||||
},
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::splat(1.0),
|
||||
}
|
||||
}
|
||||
return result_array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_test_hand_pose_array() -> [Posef; 26] {
|
||||
let test_hand_pose: [Posef; 26] = [
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.267,
|
||||
y: 0.849,
|
||||
z: 0.204,
|
||||
w: 0.407,
|
||||
},
|
||||
}, //palm
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.02,
|
||||
y: -0.040,
|
||||
z: -0.015,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.267,
|
||||
y: 0.849,
|
||||
z: 0.204,
|
||||
w: 0.407,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.019,
|
||||
y: -0.037,
|
||||
z: 0.011,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.744,
|
||||
y: -0.530,
|
||||
z: 0.156,
|
||||
w: -0.376,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.015,
|
||||
y: -0.014,
|
||||
z: 0.047,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.786,
|
||||
y: -0.550,
|
||||
z: 0.126,
|
||||
w: -0.254,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.004,
|
||||
y: 0.003,
|
||||
z: 0.068,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.729,
|
||||
y: -0.564,
|
||||
z: 0.027,
|
||||
w: -0.387,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.009,
|
||||
y: 0.011,
|
||||
z: 0.072,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.585,
|
||||
y: -0.548,
|
||||
z: -0.140,
|
||||
w: -0.582,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.027,
|
||||
y: -0.021,
|
||||
z: 0.001,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.277,
|
||||
y: -0.826,
|
||||
z: 0.317,
|
||||
w: -0.376,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.002,
|
||||
y: 0.026,
|
||||
z: 0.034,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.277,
|
||||
y: -0.826,
|
||||
z: 0.317,
|
||||
w: -0.376,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.023,
|
||||
y: 0.049,
|
||||
z: 0.055,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.244,
|
||||
y: -0.843,
|
||||
z: 0.256,
|
||||
w: -0.404,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.037,
|
||||
y: 0.059,
|
||||
z: 0.067,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.200,
|
||||
y: -0.866,
|
||||
z: 0.165,
|
||||
w: -0.428,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.045,
|
||||
y: 0.063,
|
||||
z: 0.073,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.172,
|
||||
y: -0.874,
|
||||
z: 0.110,
|
||||
w: -0.440,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.021,
|
||||
y: -0.017,
|
||||
z: -0.007,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.185,
|
||||
y: -0.817,
|
||||
z: 0.370,
|
||||
w: -0.401,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.011,
|
||||
y: 0.029,
|
||||
z: 0.018,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.185,
|
||||
y: -0.817,
|
||||
z: 0.370,
|
||||
w: -0.401,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.034,
|
||||
y: 0.06,
|
||||
z: 0.033,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.175,
|
||||
y: -0.809,
|
||||
z: 0.371,
|
||||
w: -0.420,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.051,
|
||||
y: 0.072,
|
||||
z: 0.045,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.109,
|
||||
y: -0.856,
|
||||
z: 0.245,
|
||||
w: -0.443,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.06,
|
||||
y: 0.077,
|
||||
z: 0.051,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.075,
|
||||
y: -0.871,
|
||||
z: 0.180,
|
||||
w: -0.450,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.013,
|
||||
y: -0.017,
|
||||
z: -0.015,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.132,
|
||||
y: -0.786,
|
||||
z: 0.408,
|
||||
w: -0.445,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.02,
|
||||
y: 0.025,
|
||||
z: 0.0,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.132,
|
||||
y: -0.786,
|
||||
z: 0.408,
|
||||
w: -0.445,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.042,
|
||||
y: 0.055,
|
||||
z: 0.007,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.131,
|
||||
y: -0.762,
|
||||
z: 0.432,
|
||||
w: -0.464,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.06,
|
||||
y: 0.069,
|
||||
z: 0.015,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.071,
|
||||
y: -0.810,
|
||||
z: 0.332,
|
||||
w: -0.477,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.069,
|
||||
y: 0.075,
|
||||
z: 0.02,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.029,
|
||||
y: -0.836,
|
||||
z: 0.260,
|
||||
w: -0.482,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: 0.004,
|
||||
y: -0.022,
|
||||
z: -0.022,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.060,
|
||||
y: -0.749,
|
||||
z: 0.481,
|
||||
w: -0.452,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.028,
|
||||
y: 0.018,
|
||||
z: -0.015,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.060,
|
||||
y: -0.749,
|
||||
z: 0.481,
|
||||
w: -0.452,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.046,
|
||||
y: 0.042,
|
||||
z: -0.017,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: -0.061,
|
||||
y: -0.684,
|
||||
z: 0.534,
|
||||
w: -0.493,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.059,
|
||||
y: 0.053,
|
||||
z: -0.015,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: 0.002,
|
||||
y: -0.745,
|
||||
z: 0.444,
|
||||
w: -0.498,
|
||||
},
|
||||
},
|
||||
Posef {
|
||||
position: Vector3f {
|
||||
x: -0.068,
|
||||
y: 0.059,
|
||||
z: -0.013,
|
||||
},
|
||||
orientation: Quaternionf {
|
||||
x: 0.045,
|
||||
y: -0.780,
|
||||
z: 0.378,
|
||||
w: -0.496,
|
||||
},
|
||||
},
|
||||
];
|
||||
return test_hand_pose;
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
use bevy::prelude::{
|
||||
default, Color, Commands, Component, Deref, DerefMut, Entity, Gizmos, Plugin, PostUpdate,
|
||||
Query, Resource, SpatialBundle, Startup, Transform,
|
||||
};
|
||||
|
||||
use crate::xr_input::{Hand, trackers::OpenXRTracker};
|
||||
|
||||
use super::{HandBone, BoneTrackingStatus};
|
||||
|
||||
/// add debug renderer for controllers
|
||||
#[derive(Default)]
|
||||
pub struct OpenXrHandInput;
|
||||
|
||||
impl Plugin for OpenXrHandInput {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_systems(Startup, spawn_hand_entities);
|
||||
}
|
||||
}
|
||||
|
||||
/// add debug renderer for controllers
|
||||
#[derive(Default)]
|
||||
pub struct HandInputDebugRenderer;
|
||||
|
||||
impl Plugin for HandInputDebugRenderer {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_systems(PostUpdate, draw_hand_entities);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Clone, Copy)]
|
||||
pub struct HandsResource {
|
||||
pub left: HandResource,
|
||||
pub right: HandResource,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct HandResource {
|
||||
pub palm: Entity,
|
||||
pub wrist: Entity,
|
||||
pub thumb: ThumbResource,
|
||||
pub index: IndexResource,
|
||||
pub middle: MiddleResource,
|
||||
pub ring: RingResource,
|
||||
pub little: LittleResource,
|
||||
}
|
||||
|
||||
impl Default for HandResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
palm: Entity::PLACEHOLDER,
|
||||
wrist: Entity::PLACEHOLDER,
|
||||
thumb: Default::default(),
|
||||
index: Default::default(),
|
||||
middle: Default::default(),
|
||||
ring: Default::default(),
|
||||
little: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ThumbResource {
|
||||
pub metacarpal: Entity,
|
||||
pub proximal: Entity,
|
||||
pub distal: Entity,
|
||||
pub tip: Entity,
|
||||
}
|
||||
|
||||
impl Default for ThumbResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
metacarpal: Entity::PLACEHOLDER,
|
||||
proximal: Entity::PLACEHOLDER,
|
||||
distal: Entity::PLACEHOLDER,
|
||||
tip: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IndexResource {
|
||||
pub metacarpal: Entity,
|
||||
pub proximal: Entity,
|
||||
pub intermediate: Entity,
|
||||
pub distal: Entity,
|
||||
pub tip: Entity,
|
||||
}
|
||||
|
||||
impl Default for IndexResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
metacarpal: Entity::PLACEHOLDER,
|
||||
proximal: Entity::PLACEHOLDER,
|
||||
intermediate: Entity::PLACEHOLDER,
|
||||
distal: Entity::PLACEHOLDER,
|
||||
tip: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MiddleResource {
|
||||
pub metacarpal: Entity,
|
||||
pub proximal: Entity,
|
||||
pub intermediate: Entity,
|
||||
pub distal: Entity,
|
||||
pub tip: Entity,
|
||||
}
|
||||
impl Default for MiddleResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
metacarpal: Entity::PLACEHOLDER,
|
||||
proximal: Entity::PLACEHOLDER,
|
||||
intermediate: Entity::PLACEHOLDER,
|
||||
distal: Entity::PLACEHOLDER,
|
||||
tip: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RingResource {
|
||||
pub metacarpal: Entity,
|
||||
pub proximal: Entity,
|
||||
pub intermediate: Entity,
|
||||
pub distal: Entity,
|
||||
pub tip: Entity,
|
||||
}
|
||||
impl Default for RingResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
metacarpal: Entity::PLACEHOLDER,
|
||||
proximal: Entity::PLACEHOLDER,
|
||||
intermediate: Entity::PLACEHOLDER,
|
||||
distal: Entity::PLACEHOLDER,
|
||||
tip: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LittleResource {
|
||||
pub metacarpal: Entity,
|
||||
pub proximal: Entity,
|
||||
pub intermediate: Entity,
|
||||
pub distal: Entity,
|
||||
pub tip: Entity,
|
||||
}
|
||||
impl Default for LittleResource {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
metacarpal: Entity::PLACEHOLDER,
|
||||
proximal: Entity::PLACEHOLDER,
|
||||
intermediate: Entity::PLACEHOLDER,
|
||||
distal: Entity::PLACEHOLDER,
|
||||
tip: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_hand_entities(mut commands: Commands) {
|
||||
let hands = [Hand::Left, Hand::Right];
|
||||
let bones = HandBone::get_all_bones();
|
||||
//hand resource
|
||||
let mut hand_resource = HandsResource { ..default() };
|
||||
for hand in hands.iter() {
|
||||
for bone in bones.iter() {
|
||||
let boneid = commands
|
||||
.spawn((
|
||||
SpatialBundle::default(),
|
||||
bone.clone(),
|
||||
OpenXRTracker,
|
||||
hand.clone(),
|
||||
BoneTrackingStatus::Emulated,
|
||||
HandBoneRadius(0.1),
|
||||
))
|
||||
.id();
|
||||
match hand {
|
||||
Hand::Left => match bone {
|
||||
HandBone::Palm => hand_resource.left.palm = boneid,
|
||||
HandBone::Wrist => hand_resource.left.wrist = boneid,
|
||||
HandBone::ThumbMetacarpal => hand_resource.left.thumb.metacarpal = boneid,
|
||||
HandBone::ThumbProximal => hand_resource.left.thumb.proximal = boneid,
|
||||
HandBone::ThumbDistal => hand_resource.left.thumb.distal = boneid,
|
||||
HandBone::ThumbTip => hand_resource.left.thumb.tip = boneid,
|
||||
HandBone::IndexMetacarpal => hand_resource.left.index.metacarpal = boneid,
|
||||
HandBone::IndexProximal => hand_resource.left.index.proximal = boneid,
|
||||
HandBone::IndexIntermediate => hand_resource.left.index.intermediate = boneid,
|
||||
HandBone::IndexDistal => hand_resource.left.index.distal = boneid,
|
||||
HandBone::IndexTip => hand_resource.left.index.tip = boneid,
|
||||
HandBone::MiddleMetacarpal => hand_resource.left.middle.metacarpal = boneid,
|
||||
HandBone::MiddleProximal => hand_resource.left.middle.proximal = boneid,
|
||||
HandBone::MiddleIntermediate => hand_resource.left.middle.intermediate = boneid,
|
||||
HandBone::MiddleDistal => hand_resource.left.middle.distal = boneid,
|
||||
HandBone::MiddleTip => hand_resource.left.middle.tip = boneid,
|
||||
HandBone::RingMetacarpal => hand_resource.left.ring.metacarpal = boneid,
|
||||
HandBone::RingProximal => hand_resource.left.ring.proximal = boneid,
|
||||
HandBone::RingIntermediate => hand_resource.left.ring.intermediate = boneid,
|
||||
HandBone::RingDistal => hand_resource.left.ring.distal = boneid,
|
||||
HandBone::RingTip => hand_resource.left.ring.tip = boneid,
|
||||
HandBone::LittleMetacarpal => hand_resource.left.little.metacarpal = boneid,
|
||||
HandBone::LittleProximal => hand_resource.left.little.proximal = boneid,
|
||||
HandBone::LittleIntermediate => hand_resource.left.little.intermediate = boneid,
|
||||
HandBone::LittleDistal => hand_resource.left.little.distal = boneid,
|
||||
HandBone::LittleTip => hand_resource.left.little.tip = boneid,
|
||||
},
|
||||
Hand::Right => match bone {
|
||||
HandBone::Palm => hand_resource.right.palm = boneid,
|
||||
HandBone::Wrist => hand_resource.right.wrist = boneid,
|
||||
HandBone::ThumbMetacarpal => hand_resource.right.thumb.metacarpal = boneid,
|
||||
HandBone::ThumbProximal => hand_resource.right.thumb.proximal = boneid,
|
||||
HandBone::ThumbDistal => hand_resource.right.thumb.distal = boneid,
|
||||
HandBone::ThumbTip => hand_resource.right.thumb.tip = boneid,
|
||||
HandBone::IndexMetacarpal => hand_resource.right.index.metacarpal = boneid,
|
||||
HandBone::IndexProximal => hand_resource.right.index.proximal = boneid,
|
||||
HandBone::IndexIntermediate => hand_resource.right.index.intermediate = boneid,
|
||||
HandBone::IndexDistal => hand_resource.right.index.distal = boneid,
|
||||
HandBone::IndexTip => hand_resource.right.index.tip = boneid,
|
||||
HandBone::MiddleMetacarpal => hand_resource.right.middle.metacarpal = boneid,
|
||||
HandBone::MiddleProximal => hand_resource.right.middle.proximal = boneid,
|
||||
HandBone::MiddleIntermediate => {
|
||||
hand_resource.right.middle.intermediate = boneid
|
||||
}
|
||||
HandBone::MiddleDistal => hand_resource.right.middle.distal = boneid,
|
||||
HandBone::MiddleTip => hand_resource.right.middle.tip = boneid,
|
||||
HandBone::RingMetacarpal => hand_resource.right.ring.metacarpal = boneid,
|
||||
HandBone::RingProximal => hand_resource.right.ring.proximal = boneid,
|
||||
HandBone::RingIntermediate => hand_resource.right.ring.intermediate = boneid,
|
||||
HandBone::RingDistal => hand_resource.right.ring.distal = boneid,
|
||||
HandBone::RingTip => hand_resource.right.ring.tip = boneid,
|
||||
HandBone::LittleMetacarpal => hand_resource.right.little.metacarpal = boneid,
|
||||
HandBone::LittleProximal => hand_resource.right.little.proximal = boneid,
|
||||
HandBone::LittleIntermediate => {
|
||||
hand_resource.right.little.intermediate = boneid
|
||||
}
|
||||
HandBone::LittleDistal => hand_resource.right.little.distal = boneid,
|
||||
HandBone::LittleTip => hand_resource.right.little.tip = boneid,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
commands.insert_resource(hand_resource);
|
||||
}
|
||||
|
||||
#[derive(Debug, Component, DerefMut, Deref)]
|
||||
pub struct HandBoneRadius(pub f32);
|
||||
|
||||
pub fn draw_hand_entities(
|
||||
mut gizmos: Gizmos,
|
||||
query: Query<(&Transform, &HandBone, &HandBoneRadius)>,
|
||||
) {
|
||||
for (transform, hand_bone, hand_bone_radius) in query.iter() {
|
||||
let (_, color) = get_bone_gizmo_style(hand_bone);
|
||||
gizmos.sphere(
|
||||
transform.translation,
|
||||
transform.rotation,
|
||||
hand_bone_radius.0,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_bone_gizmo_style(hand_bone: &HandBone) -> (f32, Color) {
|
||||
match hand_bone {
|
||||
HandBone::Palm => (0.01, Color::WHITE),
|
||||
HandBone::Wrist => (0.01, Color::GRAY),
|
||||
HandBone::ThumbMetacarpal => (0.01, Color::RED),
|
||||
HandBone::ThumbProximal => (0.008, Color::RED),
|
||||
HandBone::ThumbDistal => (0.006, Color::RED),
|
||||
HandBone::ThumbTip => (0.004, Color::RED),
|
||||
HandBone::IndexMetacarpal => (0.01, Color::ORANGE),
|
||||
HandBone::IndexProximal => (0.008, Color::ORANGE),
|
||||
HandBone::IndexIntermediate => (0.006, Color::ORANGE),
|
||||
HandBone::IndexDistal => (0.004, Color::ORANGE),
|
||||
HandBone::IndexTip => (0.002, Color::ORANGE),
|
||||
HandBone::MiddleMetacarpal => (0.01, Color::YELLOW),
|
||||
HandBone::MiddleProximal => (0.008, Color::YELLOW),
|
||||
HandBone::MiddleIntermediate => (0.006, Color::YELLOW),
|
||||
HandBone::MiddleDistal => (0.004, Color::YELLOW),
|
||||
HandBone::MiddleTip => (0.002, Color::YELLOW),
|
||||
HandBone::RingMetacarpal => (0.01, Color::GREEN),
|
||||
HandBone::RingProximal => (0.008, Color::GREEN),
|
||||
HandBone::RingIntermediate => (0.006, Color::GREEN),
|
||||
HandBone::RingDistal => (0.004, Color::GREEN),
|
||||
HandBone::RingTip => (0.002, Color::GREEN),
|
||||
HandBone::LittleMetacarpal => (0.01, Color::BLUE),
|
||||
HandBone::LittleProximal => (0.008, Color::BLUE),
|
||||
HandBone::LittleIntermediate => (0.006, Color::BLUE),
|
||||
HandBone::LittleDistal => (0.004, Color::BLUE),
|
||||
HandBone::LittleTip => (0.002, Color::BLUE),
|
||||
}
|
||||
}
|
||||
@@ -1,550 +0,0 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use openxr::{ActionTy, HandJoint};
|
||||
|
||||
use super::common::{get_bone_gizmo_style, HandBoneRadius};
|
||||
use crate::{
|
||||
xr_init::{xr_only, XrSetup},
|
||||
resources::{XrInstance, XrSession},
|
||||
xr_input::{
|
||||
actions::{
|
||||
ActionHandednes, ActionType, SetupActionSet, SetupActionSets, XrActionSets, XrBinding,
|
||||
},
|
||||
hand_poses::get_simulated_open_hand_transforms,
|
||||
trackers::{OpenXRLeftController, OpenXRRightController, OpenXRTrackingRoot},
|
||||
Hand,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{BoneTrackingStatus, HandBone};
|
||||
|
||||
pub enum TouchValue<T: ActionTy> {
|
||||
None,
|
||||
Touched(T),
|
||||
}
|
||||
|
||||
pub struct HandEmulationPlugin;
|
||||
|
||||
impl Plugin for HandEmulationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
update_hand_skeleton_from_emulated.run_if(xr_only()),
|
||||
);
|
||||
app.add_systems(XrSetup, setup_hand_emulation_action_set);
|
||||
}
|
||||
}
|
||||
|
||||
const HAND_ACTION_SET: &str = "hand_pose_approx";
|
||||
|
||||
fn setup_hand_emulation_action_set(mut action_sets: ResMut<SetupActionSets>) {
|
||||
let mut action_set = action_sets.add_action_set(HAND_ACTION_SET, "Hand Pose Approximaiton", 0);
|
||||
action_set.new_action(
|
||||
"thumb_touch",
|
||||
"Thumb Touched",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumb_x",
|
||||
"Thumb X",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumb_y",
|
||||
"Thumb Y",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
|
||||
action_set.new_action(
|
||||
"index_touch",
|
||||
"Index Finger Touched",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"index_value",
|
||||
"Index Finger Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
|
||||
action_set.new_action(
|
||||
"middle_value",
|
||||
"Middle Finger Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"ring_value",
|
||||
"Ring Finger Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"little_value",
|
||||
"Little Finger Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
|
||||
suggest_oculus_touch_profile(action_set);
|
||||
}
|
||||
|
||||
pub struct EmulatedHandPoseData {}
|
||||
|
||||
fn suggest_oculus_touch_profile(action_set: &mut SetupActionSet) {
|
||||
action_set.suggest_binding(
|
||||
"/interaction_profiles/oculus/touch_controller",
|
||||
&[
|
||||
XrBinding::new("thumb_x", "/user/hand/left/input/thumbstick/x"),
|
||||
XrBinding::new("thumb_x", "/user/hand/right/input/thumbstick/x"),
|
||||
XrBinding::new("thumb_y", "/user/hand/left/input/thumbstick/y"),
|
||||
XrBinding::new("thumb_y", "/user/hand/right/input/thumbstick/y"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/left/input/thumbstick/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/right/input/thumbstick/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/left/input/x/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/left/input/y/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/right/input/a/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/right/input/b/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/left/input/thumbrest/touch"),
|
||||
XrBinding::new("thumb_touch", "/user/hand/right/input/thumbrest/touch"),
|
||||
XrBinding::new("index_touch", "/user/hand/left/input/trigger/touch"),
|
||||
XrBinding::new("index_value", "/user/hand/left/input/trigger/value"),
|
||||
XrBinding::new("index_touch", "/user/hand/right/input/trigger/touch"),
|
||||
XrBinding::new("index_value", "/user/hand/right/input/trigger/value"),
|
||||
XrBinding::new("middle_value", "/user/hand/left/input/squeeze/value"),
|
||||
XrBinding::new("middle_value", "/user/hand/right/input/squeeze/value"),
|
||||
XrBinding::new("ring_value", "/user/hand/left/input/squeeze/value"),
|
||||
XrBinding::new("ring_value", "/user/hand/right/input/squeeze/value"),
|
||||
XrBinding::new("little_value", "/user/hand/left/input/squeeze/value"),
|
||||
XrBinding::new("little_value", "/user/hand/right/input/squeeze/value"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn update_hand_skeleton_from_emulated(
|
||||
session: Res<XrSession>,
|
||||
instance: Res<XrInstance>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
left_controller_transform: Query<&Transform, With<OpenXRLeftController>>,
|
||||
right_controller_transform: Query<&Transform, With<OpenXRRightController>>,
|
||||
tracking_root_transform: Query<&Transform, With<OpenXRTrackingRoot>>,
|
||||
mut bones: Query<
|
||||
(
|
||||
&mut Transform,
|
||||
&HandBone,
|
||||
&Hand,
|
||||
&BoneTrackingStatus,
|
||||
&mut HandBoneRadius,
|
||||
),
|
||||
(
|
||||
Without<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
Without<OpenXRTrackingRoot>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
//get the transforms outside the loop
|
||||
let left = left_controller_transform.get_single();
|
||||
let right = right_controller_transform.get_single();
|
||||
let mut data: [[Transform; 26]; 2] = [[Transform::default(); 26]; 2];
|
||||
for (subaction_path, hand) in [
|
||||
(
|
||||
instance.string_to_path("/user/hand/left").unwrap(),
|
||||
Hand::Left,
|
||||
),
|
||||
(
|
||||
instance.string_to_path("/user/hand/right").unwrap(),
|
||||
Hand::Right,
|
||||
),
|
||||
] {
|
||||
let thumb_curl = match action_sets
|
||||
.get_action_bool(HAND_ACTION_SET, "thumb_touch")
|
||||
.unwrap()
|
||||
.state(&session, subaction_path)
|
||||
.unwrap()
|
||||
.current_state
|
||||
{
|
||||
true => 1.0,
|
||||
false => 0.0,
|
||||
};
|
||||
let index_curl = action_sets
|
||||
.get_action_f32(HAND_ACTION_SET, "index_value")
|
||||
.unwrap()
|
||||
.state(&session, subaction_path)
|
||||
.unwrap()
|
||||
.current_state;
|
||||
let middle_curl = action_sets
|
||||
.get_action_f32(HAND_ACTION_SET, "middle_value")
|
||||
.unwrap()
|
||||
.state(&session, subaction_path)
|
||||
.unwrap()
|
||||
.current_state;
|
||||
let ring_curl = action_sets
|
||||
.get_action_f32(HAND_ACTION_SET, "ring_value")
|
||||
.unwrap()
|
||||
.state(&session, subaction_path)
|
||||
.unwrap()
|
||||
.current_state;
|
||||
let little_curl = action_sets
|
||||
.get_action_f32(HAND_ACTION_SET, "little_value")
|
||||
.unwrap()
|
||||
.state(&session, subaction_path)
|
||||
.unwrap()
|
||||
.current_state;
|
||||
match hand {
|
||||
Hand::Left => match left {
|
||||
Ok(hand_transform) => {
|
||||
data[0] = update_hand_bones_emulated(
|
||||
hand_transform,
|
||||
hand,
|
||||
thumb_curl,
|
||||
index_curl,
|
||||
middle_curl,
|
||||
ring_curl,
|
||||
little_curl,
|
||||
);
|
||||
}
|
||||
Err(_) => debug!("no left controller transform for hand bone emulation"),
|
||||
},
|
||||
Hand::Right => match right {
|
||||
Ok(hand_transform) => {
|
||||
data[1] = update_hand_bones_emulated(
|
||||
hand_transform,
|
||||
hand,
|
||||
thumb_curl,
|
||||
index_curl,
|
||||
middle_curl,
|
||||
ring_curl,
|
||||
little_curl,
|
||||
);
|
||||
}
|
||||
Err(_) => debug!("no right controller transform for hand bone emulation"),
|
||||
},
|
||||
}
|
||||
}
|
||||
let trt = tracking_root_transform.single();
|
||||
for (mut t, bone, hand, status, mut radius) in bones.iter_mut() {
|
||||
match status {
|
||||
BoneTrackingStatus::Emulated => {}
|
||||
BoneTrackingStatus::Tracked => continue,
|
||||
}
|
||||
radius.0 = get_bone_gizmo_style(bone).0;
|
||||
|
||||
*t = data[match hand {
|
||||
Hand::Left => 0,
|
||||
Hand::Right => 1,
|
||||
}][bone.get_index_from_bone()];
|
||||
*t = t.with_scale(trt.scale);
|
||||
*t = t.with_rotation(trt.rotation * t.rotation);
|
||||
*t = t.with_translation(trt.transform_point(t.translation));
|
||||
}
|
||||
}
|
||||
pub fn update_hand_bones_emulated(
|
||||
controller_transform: &Transform,
|
||||
hand: Hand,
|
||||
thumb_curl: f32,
|
||||
index_curl: f32,
|
||||
middle_curl: f32,
|
||||
ring_curl: f32,
|
||||
little_curl: f32,
|
||||
) -> [Transform; 26] {
|
||||
let left_hand_rot = Quat::from_rotation_y(PI);
|
||||
let hand_translation: Vec3 = controller_transform.translation;
|
||||
|
||||
let controller_quat: Quat = match hand {
|
||||
Hand::Left => controller_transform.rotation.mul_quat(left_hand_rot),
|
||||
Hand::Right => controller_transform.rotation,
|
||||
};
|
||||
|
||||
let splay_direction = match hand {
|
||||
Hand::Left => -1.0,
|
||||
Hand::Right => 1.0,
|
||||
};
|
||||
//lets make a structure to hold our calculated transforms for now
|
||||
let mut calc_transforms = [Transform::default(); 26];
|
||||
|
||||
//get palm quat
|
||||
let y = Quat::from_rotation_y(-90.0 * PI / 180.0);
|
||||
let x = Quat::from_rotation_x(-90.0 * PI / 180.0);
|
||||
let palm_quat = controller_quat.mul_quat(y).mul_quat(x);
|
||||
//get simulated bones
|
||||
let hand_transform_array: [Transform; 26] = get_simulated_open_hand_transforms(hand);
|
||||
//palm
|
||||
let palm = hand_transform_array[HandJoint::PALM];
|
||||
calc_transforms[HandJoint::PALM] = Transform {
|
||||
translation: hand_translation + palm.translation,
|
||||
..default()
|
||||
};
|
||||
//wrist
|
||||
let wrist = hand_transform_array[HandJoint::WRIST];
|
||||
calc_transforms[HandJoint::WRIST] = Transform {
|
||||
translation: hand_translation + palm.translation + palm_quat.mul_vec3(wrist.translation),
|
||||
..default()
|
||||
};
|
||||
|
||||
//thumb
|
||||
let thumb_joints = [
|
||||
HandJoint::THUMB_METACARPAL,
|
||||
HandJoint::THUMB_PROXIMAL,
|
||||
HandJoint::THUMB_DISTAL,
|
||||
HandJoint::THUMB_TIP,
|
||||
];
|
||||
let mut prior_start: Option<Vec3> = None;
|
||||
let mut prior_quat: Option<Quat> = None;
|
||||
let mut prior_vector: Option<Vec3> = None;
|
||||
let splay = Quat::from_rotation_y(splay_direction * 30.0 * PI / 180.0);
|
||||
let huh = Quat::from_rotation_x(-35.0 * PI / 180.0);
|
||||
let splay_quat = palm_quat.mul_quat(huh).mul_quat(splay);
|
||||
for bone in thumb_joints.iter() {
|
||||
match prior_start {
|
||||
Some(start) => {
|
||||
let curl_angle: f32 = get_bone_curl_angle(*bone, thumb_curl);
|
||||
let tp_lrot = Quat::from_rotation_y(splay_direction * curl_angle * PI / 180.0);
|
||||
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
|
||||
let thumb_prox = hand_transform_array[*bone];
|
||||
let tp_start = start + prior_vector.unwrap();
|
||||
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
|
||||
prior_start = Some(tp_start);
|
||||
prior_quat = Some(tp_quat);
|
||||
prior_vector = Some(tp_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tp_start + tp_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let thumb_meta = hand_transform_array[*bone];
|
||||
let tm_start = hand_translation
|
||||
+ palm_quat.mul_vec3(palm.translation)
|
||||
+ palm_quat.mul_vec3(wrist.translation);
|
||||
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
|
||||
prior_start = Some(tm_start);
|
||||
prior_quat = Some(splay_quat);
|
||||
prior_vector = Some(tm_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tm_start + tm_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//index
|
||||
let thumb_joints = [
|
||||
HandJoint::INDEX_METACARPAL,
|
||||
HandJoint::INDEX_PROXIMAL,
|
||||
HandJoint::INDEX_INTERMEDIATE,
|
||||
HandJoint::INDEX_DISTAL,
|
||||
HandJoint::INDEX_TIP,
|
||||
];
|
||||
let mut prior_start: Option<Vec3> = None;
|
||||
let mut prior_quat: Option<Quat> = None;
|
||||
let mut prior_vector: Option<Vec3> = None;
|
||||
let splay = Quat::from_rotation_y(splay_direction * 10.0 * PI / 180.0);
|
||||
let splay_quat = palm_quat.mul_quat(splay);
|
||||
for bone in thumb_joints.iter() {
|
||||
match prior_start {
|
||||
Some(start) => {
|
||||
let curl_angle: f32 = get_bone_curl_angle(*bone, index_curl);
|
||||
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
|
||||
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
|
||||
let thumb_prox = hand_transform_array[*bone];
|
||||
let tp_start = start + prior_vector.unwrap();
|
||||
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
|
||||
prior_start = Some(tp_start);
|
||||
prior_quat = Some(tp_quat);
|
||||
prior_vector = Some(tp_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tp_start + tp_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let thumb_meta = hand_transform_array[*bone];
|
||||
let tm_start = hand_translation
|
||||
+ palm_quat.mul_vec3(palm.translation)
|
||||
+ palm_quat.mul_vec3(wrist.translation);
|
||||
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
|
||||
prior_start = Some(tm_start);
|
||||
prior_quat = Some(splay_quat);
|
||||
prior_vector = Some(tm_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tm_start + tm_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//middle
|
||||
let thumb_joints = [
|
||||
HandJoint::MIDDLE_METACARPAL,
|
||||
HandJoint::MIDDLE_PROXIMAL,
|
||||
HandJoint::MIDDLE_INTERMEDIATE,
|
||||
HandJoint::MIDDLE_DISTAL,
|
||||
HandJoint::MIDDLE_TIP,
|
||||
];
|
||||
let mut prior_start: Option<Vec3> = None;
|
||||
let mut prior_quat: Option<Quat> = None;
|
||||
let mut prior_vector: Option<Vec3> = None;
|
||||
let splay = Quat::from_rotation_y(splay_direction * 0.0 * PI / 180.0);
|
||||
let splay_quat = palm_quat.mul_quat(splay);
|
||||
for bone in thumb_joints.iter() {
|
||||
match prior_start {
|
||||
Some(start) => {
|
||||
let curl_angle: f32 = get_bone_curl_angle(*bone, middle_curl);
|
||||
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
|
||||
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
|
||||
let thumb_prox = hand_transform_array[*bone];
|
||||
let tp_start = start + prior_vector.unwrap();
|
||||
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
|
||||
prior_start = Some(tp_start);
|
||||
prior_quat = Some(tp_quat);
|
||||
prior_vector = Some(tp_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tp_start + tp_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let thumb_meta = hand_transform_array[*bone];
|
||||
let tm_start = hand_translation
|
||||
+ palm_quat.mul_vec3(palm.translation)
|
||||
+ palm_quat.mul_vec3(wrist.translation);
|
||||
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
|
||||
prior_start = Some(tm_start);
|
||||
prior_quat = Some(splay_quat);
|
||||
prior_vector = Some(tm_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tm_start + tm_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//ring
|
||||
let thumb_joints = [
|
||||
HandJoint::RING_METACARPAL,
|
||||
HandJoint::RING_PROXIMAL,
|
||||
HandJoint::RING_INTERMEDIATE,
|
||||
HandJoint::RING_DISTAL,
|
||||
HandJoint::RING_TIP,
|
||||
];
|
||||
let mut prior_start: Option<Vec3> = None;
|
||||
let mut prior_quat: Option<Quat> = None;
|
||||
let mut prior_vector: Option<Vec3> = None;
|
||||
let splay = Quat::from_rotation_y(splay_direction * -10.0 * PI / 180.0);
|
||||
let splay_quat = palm_quat.mul_quat(splay);
|
||||
for bone in thumb_joints.iter() {
|
||||
match prior_start {
|
||||
Some(start) => {
|
||||
let curl_angle: f32 = get_bone_curl_angle(*bone, ring_curl);
|
||||
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
|
||||
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
|
||||
let thumb_prox = hand_transform_array[*bone];
|
||||
let tp_start = start + prior_vector.unwrap();
|
||||
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
|
||||
prior_start = Some(tp_start);
|
||||
prior_quat = Some(tp_quat);
|
||||
prior_vector = Some(tp_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tp_start + tp_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let thumb_meta = hand_transform_array[*bone];
|
||||
let tm_start = hand_translation
|
||||
+ palm_quat.mul_vec3(palm.translation)
|
||||
+ palm_quat.mul_vec3(wrist.translation);
|
||||
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
|
||||
prior_start = Some(tm_start);
|
||||
prior_quat = Some(splay_quat);
|
||||
prior_vector = Some(tm_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tm_start + tm_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//little
|
||||
let thumb_joints = [
|
||||
HandJoint::LITTLE_METACARPAL,
|
||||
HandJoint::LITTLE_PROXIMAL,
|
||||
HandJoint::LITTLE_INTERMEDIATE,
|
||||
HandJoint::LITTLE_DISTAL,
|
||||
HandJoint::LITTLE_TIP,
|
||||
];
|
||||
let mut prior_start: Option<Vec3> = None;
|
||||
let mut prior_quat: Option<Quat> = None;
|
||||
let mut prior_vector: Option<Vec3> = None;
|
||||
let splay = Quat::from_rotation_y(splay_direction * -20.0 * PI / 180.0);
|
||||
let splay_quat = palm_quat.mul_quat(splay);
|
||||
for bone in thumb_joints.iter() {
|
||||
match prior_start {
|
||||
Some(start) => {
|
||||
let curl_angle: f32 = get_bone_curl_angle(*bone, little_curl);
|
||||
let tp_lrot = Quat::from_rotation_x(curl_angle * PI / 180.0);
|
||||
let tp_quat = prior_quat.unwrap().mul_quat(tp_lrot);
|
||||
let thumb_prox = hand_transform_array[*bone];
|
||||
let tp_start = start + prior_vector.unwrap();
|
||||
let tp_vector = tp_quat.mul_vec3(thumb_prox.translation);
|
||||
prior_start = Some(tp_start);
|
||||
prior_quat = Some(tp_quat);
|
||||
prior_vector = Some(tp_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tp_start + tp_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
None => {
|
||||
let thumb_meta = hand_transform_array[*bone];
|
||||
let tm_start = hand_translation
|
||||
+ palm_quat.mul_vec3(palm.translation)
|
||||
+ palm_quat.mul_vec3(wrist.translation);
|
||||
let tm_vector = palm_quat.mul_vec3(thumb_meta.translation);
|
||||
prior_start = Some(tm_start);
|
||||
prior_quat = Some(splay_quat);
|
||||
prior_vector = Some(tm_vector);
|
||||
//store it
|
||||
calc_transforms[*bone] = Transform {
|
||||
translation: tm_start + tm_vector,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
calc_transforms
|
||||
}
|
||||
|
||||
fn get_bone_curl_angle(bone: HandJoint, curl: f32) -> f32 {
|
||||
let mul: f32 = match bone {
|
||||
HandJoint::INDEX_PROXIMAL => 0.0,
|
||||
HandJoint::MIDDLE_PROXIMAL => 0.0,
|
||||
HandJoint::RING_PROXIMAL => 0.0,
|
||||
HandJoint::LITTLE_PROXIMAL => 0.0,
|
||||
HandJoint::THUMB_PROXIMAL => 0.0,
|
||||
HandJoint::THUMB_TIP => 0.1,
|
||||
HandJoint::THUMB_DISTAL => 0.1,
|
||||
HandJoint::THUMB_METACARPAL => 0.1,
|
||||
_ => 1.0,
|
||||
};
|
||||
let curl_angle = -((mul * curl * 80.0) + 5.0);
|
||||
return curl_angle;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use openxr::{HandTracker, Result, SpaceLocationFlags};
|
||||
|
||||
use crate::{
|
||||
input::XrInput,
|
||||
|
||||
resources::{XrFrameState, XrSession},
|
||||
xr_input::{
|
||||
hands::HandBone, trackers::OpenXRTrackingRoot, Hand, QuatConv,
|
||||
Vec3Conv,
|
||||
}, xr_init::xr_only,
|
||||
};
|
||||
use super::common::HandBoneRadius;
|
||||
|
||||
use super::BoneTrackingStatus;
|
||||
|
||||
#[derive(Resource, PartialEq)]
|
||||
pub enum DisableHandTracking {
|
||||
OnlyLeft,
|
||||
OnlyRight,
|
||||
Both,
|
||||
}
|
||||
pub struct HandTrackingPlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct HandTrackingData {
|
||||
left_hand: HandTracker,
|
||||
right_hand: HandTracker,
|
||||
}
|
||||
|
||||
impl HandTrackingData {
|
||||
pub fn new(session: &XrSession) -> Result<HandTrackingData> {
|
||||
let left = session.create_hand_tracker(openxr::HandEXT::LEFT)?;
|
||||
let right = session.create_hand_tracker(openxr::HandEXT::RIGHT)?;
|
||||
Ok(HandTrackingData {
|
||||
left_hand: left,
|
||||
right_hand: right,
|
||||
})
|
||||
}
|
||||
pub fn get_ref<'a>(
|
||||
&'a self,
|
||||
input: &'a XrInput,
|
||||
frame_state: &'a XrFrameState,
|
||||
) -> HandTrackingRef<'a> {
|
||||
HandTrackingRef {
|
||||
tracking: self,
|
||||
input,
|
||||
frame_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HandTrackingRef<'a> {
|
||||
tracking: &'a HandTrackingData,
|
||||
input: &'a XrInput,
|
||||
frame_state: &'a XrFrameState,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct HandJoint {
|
||||
pub position: Vec3,
|
||||
pub position_valid: bool,
|
||||
pub position_tracked: bool,
|
||||
pub orientation: Quat,
|
||||
pub orientation_valid: bool,
|
||||
pub orientation_tracked: bool,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
pub struct HandJoints {
|
||||
inner: [HandJoint; 26],
|
||||
}
|
||||
impl HandJoints {
|
||||
pub fn inner(&self) -> &[HandJoint; 26] {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl HandJoints {
|
||||
pub fn get_joint(&self, bone: HandBone) -> &HandJoint {
|
||||
&self.inner[bone.get_index_from_bone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> HandTrackingRef<'a> {
|
||||
pub fn get_poses(&self, side: Hand) -> Option<HandJoints> {
|
||||
self.input
|
||||
.stage
|
||||
.locate_hand_joints(
|
||||
match side {
|
||||
Hand::Left => &self.tracking.left_hand,
|
||||
Hand::Right => &self.tracking.right_hand,
|
||||
},
|
||||
self.frame_state.lock().unwrap().predicted_display_time,
|
||||
)
|
||||
.unwrap()
|
||||
.map(|joints| {
|
||||
joints
|
||||
.into_iter()
|
||||
.map(|joint| HandJoint {
|
||||
position: joint.pose.position.to_vec3(),
|
||||
orientation: joint.pose.orientation.to_quat(),
|
||||
position_valid: joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::POSITION_VALID),
|
||||
position_tracked: joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::POSITION_TRACKED),
|
||||
orientation_valid: joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::ORIENTATION_VALID),
|
||||
orientation_tracked: joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::ORIENTATION_TRACKED),
|
||||
radius: joint.radius,
|
||||
})
|
||||
.collect::<Vec<HandJoint>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
})
|
||||
.map(|joints| HandJoints { inner: joints })
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for HandTrackingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
(
|
||||
update_hand_bones.run_if(|dh: Option<Res<DisableHandTracking>>| {
|
||||
!dh.is_some_and(|v| *v == DisableHandTracking::Both)
|
||||
}).run_if(xr_only()),
|
||||
update_tracking_state_on_disable,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tracking_state_on_disable(
|
||||
mut is_off: Local<bool>,
|
||||
disabled_tracking: Option<Res<DisableHandTracking>>,
|
||||
mut tracking_states: Query<&mut BoneTrackingStatus>,
|
||||
) {
|
||||
if !*is_off
|
||||
&& disabled_tracking
|
||||
.as_ref()
|
||||
.is_some_and(|t| **t == DisableHandTracking::Both)
|
||||
{
|
||||
tracking_states
|
||||
.par_iter_mut()
|
||||
.for_each(|mut state| *state = BoneTrackingStatus::Emulated);
|
||||
}
|
||||
*is_off = disabled_tracking
|
||||
.as_ref()
|
||||
.is_some_and(|t| **t == DisableHandTracking::Both);
|
||||
}
|
||||
|
||||
pub fn update_hand_bones(
|
||||
disabled_tracking: Option<Res<DisableHandTracking>>,
|
||||
hand_tracking: Option<Res<HandTrackingData>>,
|
||||
xr_input: Res<XrInput>,
|
||||
xr_frame_state: Res<XrFrameState>,
|
||||
root_query: Query<(&Transform, With<OpenXRTrackingRoot>, Without<HandBone>)>,
|
||||
mut bones: Query<(
|
||||
&mut Transform,
|
||||
&Hand,
|
||||
&HandBone,
|
||||
&mut HandBoneRadius,
|
||||
&mut BoneTrackingStatus,
|
||||
)>,
|
||||
) {
|
||||
let hand_ref = match hand_tracking.as_ref() {
|
||||
Some(h) => h.get_ref(&xr_input, &xr_frame_state),
|
||||
None => {
|
||||
warn!("No Handtracking data!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (root_transform, _, _) = root_query.get_single().unwrap();
|
||||
let left_hand_data = hand_ref.get_poses(Hand::Left);
|
||||
let right_hand_data = hand_ref.get_poses(Hand::Right);
|
||||
bones
|
||||
.par_iter_mut()
|
||||
.for_each(|(mut transform, hand, bone, mut radius, mut status)| {
|
||||
match (&hand, disabled_tracking.as_ref().map(|d| d.as_ref())) {
|
||||
(Hand::Left, Some(DisableHandTracking::OnlyLeft)) => {
|
||||
*status = BoneTrackingStatus::Emulated;
|
||||
return;
|
||||
}
|
||||
(Hand::Right, Some(DisableHandTracking::OnlyRight)) => {
|
||||
*status = BoneTrackingStatus::Emulated;
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let bone_data = match (hand, &left_hand_data, &right_hand_data) {
|
||||
(Hand::Left, Some(data), _) => data.get_joint(*bone),
|
||||
(Hand::Right, _, Some(data)) => data.get_joint(*bone),
|
||||
_ => {
|
||||
*status = BoneTrackingStatus::Emulated;
|
||||
return;
|
||||
}
|
||||
};
|
||||
if *status == BoneTrackingStatus::Emulated {
|
||||
*status = BoneTrackingStatus::Tracked;
|
||||
}
|
||||
radius.0 = bone_data.radius;
|
||||
*transform = transform
|
||||
.with_translation(root_transform.transform_point(bone_data.position))
|
||||
.with_rotation(root_transform.rotation * bone_data.orientation)
|
||||
});
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
use bevy::{app::PluginGroupBuilder, prelude::*};
|
||||
|
||||
use self::{emulated::HandEmulationPlugin, hand_tracking::HandTrackingPlugin};
|
||||
|
||||
pub mod emulated;
|
||||
pub mod hand_tracking;
|
||||
pub mod common;
|
||||
|
||||
pub struct XrHandPlugins;
|
||||
|
||||
impl PluginGroup for XrHandPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(HandTrackingPlugin)
|
||||
.add(HandEmulationPlugin)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BoneTrackingStatus {
|
||||
Emulated,
|
||||
Tracked,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub enum HandBone {
|
||||
Palm,
|
||||
Wrist,
|
||||
ThumbMetacarpal,
|
||||
ThumbProximal,
|
||||
ThumbDistal,
|
||||
ThumbTip,
|
||||
IndexMetacarpal,
|
||||
IndexProximal,
|
||||
IndexIntermediate,
|
||||
IndexDistal,
|
||||
IndexTip,
|
||||
MiddleMetacarpal,
|
||||
MiddleProximal,
|
||||
MiddleIntermediate,
|
||||
MiddleDistal,
|
||||
MiddleTip,
|
||||
RingMetacarpal,
|
||||
RingProximal,
|
||||
RingIntermediate,
|
||||
RingDistal,
|
||||
RingTip,
|
||||
LittleMetacarpal,
|
||||
LittleProximal,
|
||||
LittleIntermediate,
|
||||
LittleDistal,
|
||||
LittleTip,
|
||||
}
|
||||
impl HandBone {
|
||||
pub fn is_finger(&self) -> bool {
|
||||
match &self {
|
||||
HandBone::Wrist => false,
|
||||
HandBone::Palm => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
pub fn is_metacarpal(&self) -> bool {
|
||||
match &self {
|
||||
HandBone::ThumbMetacarpal => true,
|
||||
HandBone::IndexMetacarpal => true,
|
||||
HandBone::MiddleMetacarpal => true,
|
||||
HandBone::RingMetacarpal => true,
|
||||
HandBone::LittleTip => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub const fn get_all_bones() -> [HandBone; 26] {
|
||||
[
|
||||
HandBone::Palm,
|
||||
HandBone::Wrist,
|
||||
HandBone::ThumbMetacarpal,
|
||||
HandBone::ThumbProximal,
|
||||
HandBone::ThumbDistal,
|
||||
HandBone::ThumbTip,
|
||||
HandBone::IndexMetacarpal,
|
||||
HandBone::IndexProximal,
|
||||
HandBone::IndexIntermediate,
|
||||
HandBone::IndexDistal,
|
||||
HandBone::IndexTip,
|
||||
HandBone::MiddleMetacarpal,
|
||||
HandBone::MiddleProximal,
|
||||
HandBone::MiddleIntermediate,
|
||||
HandBone::MiddleDistal,
|
||||
HandBone::MiddleTip,
|
||||
HandBone::RingMetacarpal,
|
||||
HandBone::RingProximal,
|
||||
HandBone::RingIntermediate,
|
||||
HandBone::RingDistal,
|
||||
HandBone::RingTip,
|
||||
HandBone::LittleMetacarpal,
|
||||
HandBone::LittleProximal,
|
||||
HandBone::LittleIntermediate,
|
||||
HandBone::LittleDistal,
|
||||
HandBone::LittleTip,
|
||||
]
|
||||
}
|
||||
pub fn get_index_from_bone(&self) -> usize {
|
||||
match &self {
|
||||
HandBone::Palm => 0,
|
||||
HandBone::Wrist => 1,
|
||||
HandBone::ThumbMetacarpal => 2,
|
||||
HandBone::ThumbProximal => 3,
|
||||
HandBone::ThumbDistal => 4,
|
||||
HandBone::ThumbTip => 5,
|
||||
HandBone::IndexMetacarpal => 6,
|
||||
HandBone::IndexProximal => 7,
|
||||
HandBone::IndexIntermediate => 8,
|
||||
HandBone::IndexDistal => 9,
|
||||
HandBone::IndexTip => 10,
|
||||
HandBone::MiddleMetacarpal => 11,
|
||||
HandBone::MiddleProximal => 12,
|
||||
HandBone::MiddleIntermediate => 13,
|
||||
HandBone::MiddleDistal => 14,
|
||||
HandBone::MiddleTip => 15,
|
||||
HandBone::RingMetacarpal => 16,
|
||||
HandBone::RingProximal => 17,
|
||||
HandBone::RingIntermediate => 18,
|
||||
HandBone::RingDistal => 19,
|
||||
HandBone::RingTip => 20,
|
||||
HandBone::LittleMetacarpal => 21,
|
||||
HandBone::LittleProximal => 22,
|
||||
HandBone::LittleIntermediate => 23,
|
||||
HandBone::LittleDistal => 24,
|
||||
HandBone::LittleTip => 25,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::log::info;
|
||||
use bevy::prelude::{
|
||||
Color, Component, Entity, Event, EventReader, EventWriter, Gizmos, GlobalTransform, Quat,
|
||||
Query, Transform, Vec3, With, Without,
|
||||
};
|
||||
|
||||
use super::trackers::{AimPose, OpenXRTrackingRoot};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRDirectInteractor;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRRayInteractor;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRSocketInteractor;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Touched(pub bool);
|
||||
|
||||
#[derive(Component, Clone, Copy, PartialEq, PartialOrd, Debug)]
|
||||
pub enum XRInteractableState {
|
||||
Idle,
|
||||
Hover,
|
||||
Select,
|
||||
}
|
||||
|
||||
impl Default for XRInteractableState {
|
||||
fn default() -> Self {
|
||||
XRInteractableState::Idle
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum XRInteractorState {
|
||||
Idle,
|
||||
Selecting,
|
||||
}
|
||||
impl Default for XRInteractorState {
|
||||
fn default() -> Self {
|
||||
XRInteractorState::Idle
|
||||
}
|
||||
}
|
||||
#[derive(Component)]
|
||||
pub enum XRSelection {
|
||||
Empty,
|
||||
Full(Entity),
|
||||
}
|
||||
impl Default for XRSelection {
|
||||
fn default() -> Self {
|
||||
XRSelection::Empty
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRInteractable;
|
||||
|
||||
pub fn draw_socket_gizmos(
|
||||
mut gizmos: Gizmos,
|
||||
interactor_query: Query<(
|
||||
&GlobalTransform,
|
||||
&XRInteractorState,
|
||||
Entity,
|
||||
&XRSocketInteractor,
|
||||
)>,
|
||||
) {
|
||||
for (global, state, _entity, _socket) in interactor_query.iter() {
|
||||
let mut transform = global.compute_transform().clone();
|
||||
transform.scale = Vec3::splat(0.1);
|
||||
let color = match state {
|
||||
XRInteractorState::Idle => Color::BLUE,
|
||||
XRInteractorState::Selecting => Color::PURPLE,
|
||||
};
|
||||
gizmos.cuboid(transform, color)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_interaction_gizmos(
|
||||
mut gizmos: Gizmos,
|
||||
interactable_query: Query<
|
||||
(&GlobalTransform, &XRInteractableState),
|
||||
(With<XRInteractable>, Without<XRDirectInteractor>),
|
||||
>,
|
||||
interactor_query: Query<
|
||||
(
|
||||
&GlobalTransform,
|
||||
&XRInteractorState,
|
||||
Option<&XRDirectInteractor>,
|
||||
Option<&XRRayInteractor>,
|
||||
Option<&AimPose>,
|
||||
),
|
||||
Without<XRInteractable>,
|
||||
>,
|
||||
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
|
||||
) {
|
||||
let root = tracking_root_query.get_single().unwrap().0;
|
||||
for (global_transform, interactable_state) in interactable_query.iter() {
|
||||
let transform = global_transform.compute_transform();
|
||||
let color = match interactable_state {
|
||||
XRInteractableState::Idle => Color::RED,
|
||||
XRInteractableState::Hover => Color::YELLOW,
|
||||
XRInteractableState::Select => Color::GREEN,
|
||||
};
|
||||
gizmos.sphere(transform.translation, transform.rotation, 0.1, color);
|
||||
}
|
||||
|
||||
for (interactor_global_transform, interactor_state, direct, ray, aim) in interactor_query.iter()
|
||||
{
|
||||
let transform = interactor_global_transform.compute_transform();
|
||||
match direct {
|
||||
Some(_) => {
|
||||
let mut local = transform.clone();
|
||||
local.scale = Vec3::splat(0.1);
|
||||
let quat = Quat::from_euler(
|
||||
bevy::prelude::EulerRot::XYZ,
|
||||
45.0 * (PI / 180.0),
|
||||
0.0,
|
||||
45.0 * (PI / 180.0),
|
||||
);
|
||||
local.rotation = quat;
|
||||
let color = match interactor_state {
|
||||
XRInteractorState::Idle => Color::BLUE,
|
||||
XRInteractorState::Selecting => Color::PURPLE,
|
||||
};
|
||||
gizmos.cuboid(local, color);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
match ray {
|
||||
Some(_) => match aim {
|
||||
Some(aim) => {
|
||||
let color = match interactor_state {
|
||||
XRInteractorState::Idle => Color::BLUE,
|
||||
XRInteractorState::Selecting => Color::PURPLE,
|
||||
};
|
||||
gizmos.ray(
|
||||
root.translation + root.rotation.mul_vec3(aim.0.translation),
|
||||
root.rotation.mul_vec3(aim.0.forward()),
|
||||
color,
|
||||
);
|
||||
}
|
||||
None => todo!(),
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct InteractionEvent {
|
||||
pub interactor: Entity,
|
||||
pub interactable: Entity,
|
||||
pub interactable_state: XRInteractableState,
|
||||
}
|
||||
|
||||
pub fn socket_interactions(
|
||||
interactable_query: Query<
|
||||
(&GlobalTransform, &mut XRInteractableState, Entity),
|
||||
(With<XRInteractable>, Without<XRSocketInteractor>),
|
||||
>,
|
||||
interactor_query: Query<
|
||||
(
|
||||
&GlobalTransform,
|
||||
&XRInteractorState,
|
||||
Entity,
|
||||
&XRSocketInteractor,
|
||||
),
|
||||
Without<XRInteractable>,
|
||||
>,
|
||||
mut writer: EventWriter<InteractionEvent>,
|
||||
) {
|
||||
for interactable in interactable_query.iter() {
|
||||
//for the interactables
|
||||
for socket in interactor_query.iter() {
|
||||
let interactor_global_transform = socket.0;
|
||||
let xr_interactable_global_transform = interactable.0;
|
||||
let interactor_state = socket.1;
|
||||
//check for sphere overlaps
|
||||
let size = 0.1;
|
||||
if interactor_global_transform
|
||||
.compute_transform()
|
||||
.translation
|
||||
.distance_squared(
|
||||
xr_interactable_global_transform
|
||||
.compute_transform()
|
||||
.translation,
|
||||
)
|
||||
< (size * size) * 2.0
|
||||
{
|
||||
//check for selections first
|
||||
match interactor_state {
|
||||
XRInteractorState::Idle => {
|
||||
let event = InteractionEvent {
|
||||
interactor: socket.2,
|
||||
interactable: interactable.2,
|
||||
interactable_state: XRInteractableState::Hover,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
XRInteractorState::Selecting => {
|
||||
let event = InteractionEvent {
|
||||
interactor: socket.2,
|
||||
interactable: interactable.2,
|
||||
interactable_state: XRInteractableState::Select,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interactions(
|
||||
interactable_query: Query<
|
||||
(&GlobalTransform, Entity),
|
||||
(With<XRInteractable>, Without<XRDirectInteractor>),
|
||||
>,
|
||||
interactor_query: Query<
|
||||
(
|
||||
&GlobalTransform,
|
||||
&XRInteractorState,
|
||||
Entity,
|
||||
Option<&XRDirectInteractor>,
|
||||
Option<&XRRayInteractor>,
|
||||
Option<&AimPose>,
|
||||
),
|
||||
Without<XRInteractable>,
|
||||
>,
|
||||
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
|
||||
mut writer: EventWriter<InteractionEvent>,
|
||||
) {
|
||||
for (xr_interactable_global_transform, interactable_entity) in interactable_query.iter() {
|
||||
for (interactor_global_transform, interactor_state, interactor_entity, direct, ray, aim) in
|
||||
interactor_query.iter()
|
||||
{
|
||||
match direct {
|
||||
Some(_) => {
|
||||
//check for sphere overlaps
|
||||
let size = 0.1;
|
||||
if interactor_global_transform
|
||||
.compute_transform()
|
||||
.translation
|
||||
.distance_squared(
|
||||
xr_interactable_global_transform
|
||||
.compute_transform()
|
||||
.translation,
|
||||
)
|
||||
< (size * size) * 2.0
|
||||
{
|
||||
//check for selections first
|
||||
match interactor_state {
|
||||
XRInteractorState::Idle => {
|
||||
let event = InteractionEvent {
|
||||
interactor: interactor_entity,
|
||||
interactable: interactable_entity,
|
||||
interactable_state: XRInteractableState::Hover,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
XRInteractorState::Selecting => {
|
||||
let event = InteractionEvent {
|
||||
interactor: interactor_entity,
|
||||
interactable: interactable_entity,
|
||||
interactable_state: XRInteractableState::Select,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
match ray {
|
||||
Some(_) => {
|
||||
//check for ray-sphere intersection
|
||||
let sphere_transform = xr_interactable_global_transform.compute_transform();
|
||||
let center = sphere_transform.translation;
|
||||
let radius: f32 = 0.1;
|
||||
//I hate this but the aim pose needs the root for now
|
||||
let root = tracking_root_query.get_single().unwrap().0;
|
||||
match aim {
|
||||
Some(aim) => {
|
||||
let ray_origin =
|
||||
root.translation + root.rotation.mul_vec3(aim.0.translation);
|
||||
let ray_dir = root.rotation.mul_vec3(aim.0.forward());
|
||||
|
||||
if ray_sphere_intersection(
|
||||
center,
|
||||
radius,
|
||||
ray_origin,
|
||||
ray_dir.normalize_or_zero(),
|
||||
) {
|
||||
//check for selections first
|
||||
match interactor_state {
|
||||
XRInteractorState::Idle => {
|
||||
let event = InteractionEvent {
|
||||
interactor: interactor_entity,
|
||||
interactable: interactable_entity,
|
||||
interactable_state: XRInteractableState::Hover,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
XRInteractorState::Selecting => {
|
||||
let event = InteractionEvent {
|
||||
interactor: interactor_entity,
|
||||
interactable: interactable_entity,
|
||||
interactable_state: XRInteractableState::Select,
|
||||
};
|
||||
writer.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => info!("no aim pose"),
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_interactable_states(
|
||||
mut events: EventReader<InteractionEvent>,
|
||||
mut interactable_query: Query<
|
||||
(Entity, &mut XRInteractableState, &mut Touched),
|
||||
With<XRInteractable>,
|
||||
>,
|
||||
) {
|
||||
//i very much dislike this
|
||||
for (_entity, _state, mut touched) in interactable_query.iter_mut() {
|
||||
*touched = Touched(false);
|
||||
}
|
||||
for event in events.read() {
|
||||
//lets change the state
|
||||
match interactable_query.get_mut(event.interactable) {
|
||||
Ok((_entity, mut entity_state, mut touched)) => {
|
||||
//since we have an event we were touched this frame, i hate this name
|
||||
*touched = Touched(true);
|
||||
if event.interactable_state > *entity_state {
|
||||
// info!(
|
||||
// "event.state: {:?}, interactable.state: {:?}",
|
||||
// event.interactable_state, entity_state
|
||||
// );
|
||||
// info!("event has a higher state");
|
||||
}
|
||||
*entity_state = event.interactable_state;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
//lets go through all the untouched interactables and set them to idle
|
||||
for (_entity, mut state, touched) in interactable_query.iter_mut() {
|
||||
if !touched.0 {
|
||||
*state = XRInteractableState::Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ray_sphere_intersection(center: Vec3, radius: f32, ray_origin: Vec3, ray_dir: Vec3) -> bool {
|
||||
let l = center - ray_origin;
|
||||
let adj = l.dot(ray_dir);
|
||||
let d2 = l.dot(l) - (adj * adj);
|
||||
let radius2 = radius * radius;
|
||||
if d2 > radius2 {
|
||||
return false;
|
||||
}
|
||||
let thc = (radius2 - d2).sqrt();
|
||||
let t0 = adj - thc;
|
||||
let t1 = adj + thc;
|
||||
|
||||
if t0 < 0.0 && t1 < 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// let distance = if t0 < t1 { t0 } else { t1 };
|
||||
return true;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
pub mod actions;
|
||||
pub mod controllers;
|
||||
pub mod debug_gizmos;
|
||||
pub mod hand_poses;
|
||||
pub mod hands;
|
||||
pub mod interactions;
|
||||
pub mod oculus_touch;
|
||||
pub mod prototype_locomotion;
|
||||
pub mod trackers;
|
||||
pub mod xr_camera;
|
||||
|
||||
use crate::xr_init::{XrPostSetup, XrSetup, xr_only};
|
||||
use crate::resources::{XrInstance, XrSession};
|
||||
use crate::xr_begin_frame;
|
||||
use crate::xr_input::controllers::XrControllerType;
|
||||
use crate::xr_input::oculus_touch::setup_oculus_controller;
|
||||
use crate::xr_input::xr_camera::{xr_camera_head_sync, Eye, XRProjection, XrCameraBundle};
|
||||
use bevy::app::{App, PostUpdate, Startup};
|
||||
use bevy::log::{warn, info};
|
||||
use bevy::prelude::{BuildChildren, Component, Deref, DerefMut, IntoSystemConfigs, Resource};
|
||||
use bevy::prelude::{Commands, Plugin, PreUpdate, Quat, Res, SpatialBundle, Update, Vec3};
|
||||
use bevy::render::camera::CameraProjectionPlugin;
|
||||
use bevy::render::view::{update_frusta, VisibilitySystems};
|
||||
use bevy::transform::TransformSystem;
|
||||
use bevy::utils::HashMap;
|
||||
use openxr::Binding;
|
||||
|
||||
use self::actions::{setup_oxr_actions, OpenXrActionsPlugin};
|
||||
use self::oculus_touch::{post_action_setup_oculus_controller, ActionSets};
|
||||
use self::trackers::{
|
||||
adopt_open_xr_trackers, update_open_xr_controllers, OpenXRLeftEye, OpenXRRightEye,
|
||||
OpenXRTrackingRoot,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct OpenXrInput {
|
||||
pub controller_type: XrControllerType,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Component)]
|
||||
pub enum Hand {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
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::<XRProjection>::default());
|
||||
app.add_plugins(OpenXrActionsPlugin);
|
||||
app.add_systems(
|
||||
XrPostSetup,
|
||||
post_action_setup_oculus_controller,
|
||||
);
|
||||
match self.controller_type {
|
||||
XrControllerType::OculusTouch => {
|
||||
app.add_systems(XrSetup, setup_oculus_controller);
|
||||
}
|
||||
}
|
||||
//adopt any new trackers
|
||||
app.add_systems(PreUpdate, adopt_open_xr_trackers.run_if(xr_only()));
|
||||
app.add_systems(PreUpdate, action_set_system);
|
||||
app.add_systems(PreUpdate, xr_camera_head_sync.run_if(xr_only()).after(xr_begin_frame));
|
||||
//update controller trackers
|
||||
app.add_systems(Update, update_open_xr_controllers.run_if(xr_only()));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_frusta::<XRProjection>
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.before(VisibilitySystems::UpdatePerspectiveFrusta),
|
||||
);
|
||||
app.add_systems(XrSetup, setup_xr_cameras);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Resource)]
|
||||
pub struct InteractionProfileBindings(pub HashMap<&'static str, Vec<Binding<'static>>>);
|
||||
|
||||
fn setup_binding_recommendations(
|
||||
mut commands: Commands,
|
||||
instance: Res<XrInstance>,
|
||||
bindings: Res<InteractionProfileBindings>,
|
||||
) {
|
||||
commands.remove_resource::<InteractionProfileBindings>();
|
||||
}
|
||||
|
||||
fn setup_xr_cameras(mut commands: Commands) {
|
||||
//this needs to do the whole xr tracking volume not just cameras
|
||||
//get the root?
|
||||
let tracking_root = commands
|
||||
.spawn((SpatialBundle::default(), OpenXRTrackingRoot))
|
||||
.id();
|
||||
let right = commands
|
||||
.spawn((XrCameraBundle::new(Eye::Right), OpenXRRightEye))
|
||||
.id();
|
||||
let left = commands
|
||||
.spawn((XrCameraBundle::new(Eye::Left), OpenXRLeftEye))
|
||||
.id();
|
||||
commands.entity(tracking_root).push_children(&[right, left]);
|
||||
}
|
||||
|
||||
fn action_set_system(action_sets: Res<ActionSets>, session: Res<XrSession>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,478 +0,0 @@
|
||||
use crate::input::XrInput;
|
||||
use crate::resources::{XrInstance, XrSession};
|
||||
use crate::xr_input::controllers::Handed;
|
||||
use crate::xr_input::Hand;
|
||||
use bevy::prelude::{Commands, Res, ResMut, Resource};
|
||||
use openxr::{
|
||||
ActionSet, AnyGraphics, FrameState, Instance, Path, Posef, Session, Space, SpaceLocation,
|
||||
SpaceVelocity,
|
||||
};
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::actions::{ActionHandednes, ActionType, SetupActionSets, XrActionSets, XrBinding};
|
||||
|
||||
pub fn post_action_setup_oculus_controller(
|
||||
action_sets: Res<XrActionSets>,
|
||||
mut controller: ResMut<OculusController>,
|
||||
instance: Res<XrInstance>,
|
||||
session: Res<XrSession>,
|
||||
) {
|
||||
let s = Session::<AnyGraphics>::clone(&session);
|
||||
let left_path = instance.string_to_path("/user/hand/left").unwrap();
|
||||
let right_path = instance.string_to_path("/user/hand/right").unwrap();
|
||||
let grip_action = action_sets
|
||||
.get_action_posef("oculus_input", "hand_pose")
|
||||
.unwrap();
|
||||
let aim_action = action_sets
|
||||
.get_action_posef("oculus_input", "pointer_pose")
|
||||
.unwrap();
|
||||
controller.grip_space = Some(Handed {
|
||||
left: grip_action
|
||||
.create_space(s.clone(), left_path, Posef::IDENTITY)
|
||||
.unwrap(),
|
||||
right: grip_action
|
||||
.create_space(s.clone(), right_path, Posef::IDENTITY)
|
||||
.unwrap(),
|
||||
});
|
||||
controller.aim_space = Some(Handed {
|
||||
left: aim_action
|
||||
.create_space(s.clone(), left_path, Posef::IDENTITY)
|
||||
.unwrap(),
|
||||
right: aim_action
|
||||
.create_space(s.clone(), right_path, Posef::IDENTITY)
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
pub fn setup_oculus_controller(
|
||||
mut commands: Commands,
|
||||
instance: Res<XrInstance>,
|
||||
action_sets: ResMut<SetupActionSets>,
|
||||
) {
|
||||
let oculus_controller = OculusController::new(action_sets).unwrap();
|
||||
init_subaction_path(&instance);
|
||||
commands.insert_resource(oculus_controller);
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct ActionSets(pub Vec<ActionSet>);
|
||||
|
||||
pub struct OculusControllerRef<'a> {
|
||||
oculus_controller: &'a OculusController,
|
||||
action_sets: &'a XrActionSets,
|
||||
session: &'a Session<AnyGraphics>,
|
||||
frame_state: &'a FrameState,
|
||||
xr_input: &'a XrInput,
|
||||
}
|
||||
|
||||
static RIGHT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
||||
static LEFT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
||||
|
||||
pub fn init_subaction_path(instance: &Instance) {
|
||||
let _ = LEFT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/left").unwrap());
|
||||
let _ = RIGHT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/right").unwrap());
|
||||
}
|
||||
|
||||
pub fn subaction_path(hand: Hand) -> Path {
|
||||
*match hand {
|
||||
Hand::Left => LEFT_SUBACTION_PATH.get().unwrap(),
|
||||
Hand::Right => RIGHT_SUBACTION_PATH.get().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
impl OculusControllerRef<'_> {
|
||||
pub fn grip_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
||||
match hand {
|
||||
Hand::Left => self
|
||||
.oculus_controller
|
||||
.grip_space
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.left
|
||||
.relate(
|
||||
&self.xr_input.stage,
|
||||
self.frame_state.predicted_display_time,
|
||||
),
|
||||
Hand::Right => self
|
||||
.oculus_controller
|
||||
.grip_space
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.right
|
||||
.relate(
|
||||
&self.xr_input.stage,
|
||||
self.frame_state.predicted_display_time,
|
||||
),
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
pub fn aim_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
||||
match hand {
|
||||
Hand::Left => self
|
||||
.oculus_controller
|
||||
.aim_space
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.left
|
||||
.relate(
|
||||
&self.xr_input.stage,
|
||||
self.frame_state.predicted_display_time,
|
||||
),
|
||||
Hand::Right => self
|
||||
.oculus_controller
|
||||
.aim_space
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.right
|
||||
.relate(
|
||||
&self.xr_input.stage,
|
||||
self.frame_state.predicted_display_time,
|
||||
),
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
pub fn squeeze(&self, hand: Hand) -> f32 {
|
||||
let action = &self
|
||||
.action_sets
|
||||
.get_action_f32("oculus_input", "squeeze")
|
||||
.unwrap();
|
||||
action
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn trigger(&self, hand: Hand) -> f32 {
|
||||
self.action_sets
|
||||
.get_action_f32("oculus_input", "trigger")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn trigger_touched(&self, hand: Hand) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "trigger_touched")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn x_button(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "x_button")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn x_button_touched(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "x_button_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn y_button(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "y_button")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn y_button_touched(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "y_button_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn menu_button(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "menu_button")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn a_button(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "a_button")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn a_button_touched(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "a_button_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn b_button(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "b_button")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn b_button_touched(&self) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "b_button_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, Path::NULL)
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn thumbstick_touch(&self, hand: Hand) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "thumbstick_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
pub fn thumbstick(&self, hand: Hand) -> Thumbstick {
|
||||
Thumbstick {
|
||||
x: self
|
||||
.action_sets
|
||||
.get_action_f32("oculus_input", "thumbstick_x")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state,
|
||||
y: self
|
||||
.action_sets
|
||||
.get_action_f32("oculus_input", "thumbstick_y")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state,
|
||||
click: self
|
||||
.action_sets
|
||||
.get_action_bool("oculus_input", "thumbstick_click")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state,
|
||||
}
|
||||
}
|
||||
pub fn thumbrest_touch(&self, hand: Hand) -> bool {
|
||||
self.action_sets
|
||||
.get_action_bool("oculus_input", "thumbrest_touch")
|
||||
.unwrap()
|
||||
.state(&self.session, subaction_path(hand))
|
||||
.unwrap()
|
||||
.current_state
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Thumbstick {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub click: bool,
|
||||
}
|
||||
|
||||
impl OculusController {
|
||||
pub fn get_ref<'a>(
|
||||
&'a self,
|
||||
session: &'a Session<AnyGraphics>,
|
||||
frame_state: &'a FrameState,
|
||||
xr_input: &'a XrInput,
|
||||
action_sets: &'a XrActionSets,
|
||||
) -> OculusControllerRef {
|
||||
OculusControllerRef {
|
||||
oculus_controller: self,
|
||||
session,
|
||||
frame_state,
|
||||
xr_input,
|
||||
action_sets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct OculusController {
|
||||
pub grip_space: Option<Handed<Space>>,
|
||||
pub aim_space: Option<Handed<Space>>,
|
||||
}
|
||||
impl OculusController {
|
||||
pub fn new(mut action_sets: ResMut<SetupActionSets>) -> anyhow::Result<Self> {
|
||||
let action_set =
|
||||
action_sets.add_action_set("oculus_input", "Oculus Touch Controller Input", 0);
|
||||
action_set.new_action(
|
||||
"hand_pose",
|
||||
"Hand Pose",
|
||||
ActionType::PoseF,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"pointer_pose",
|
||||
"Pointer Pose",
|
||||
ActionType::PoseF,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"squeeze",
|
||||
"Grip Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"trigger",
|
||||
"Trigger Pull",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"trigger_touched",
|
||||
"Trigger Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"haptic_feedback",
|
||||
"Haptic Feedback",
|
||||
ActionType::Haptic,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"x_button",
|
||||
"X Button",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"x_button_touch",
|
||||
"X Button Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"y_button",
|
||||
"Y Button",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"y_button_touch",
|
||||
"Y Button Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"a_button",
|
||||
"A Button",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"a_button_touch",
|
||||
"A Button Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"b_button",
|
||||
"B Button",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"b_button_touch",
|
||||
"B Button Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"menu_button",
|
||||
"Menu Button",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Single,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumbstick_x",
|
||||
"Thumbstick X",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumbstick_y",
|
||||
"Thumbstick y",
|
||||
ActionType::F32,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumbstick_touch",
|
||||
"Thumbstick Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumbstick_click",
|
||||
"Thumbstick Click",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
action_set.new_action(
|
||||
"thumbrest_touch",
|
||||
"Thumbrest Touch",
|
||||
ActionType::Bool,
|
||||
ActionHandednes::Double,
|
||||
);
|
||||
|
||||
let this = OculusController {
|
||||
grip_space: None,
|
||||
aim_space: None,
|
||||
};
|
||||
action_set.suggest_binding(
|
||||
"/interaction_profiles/oculus/touch_controller",
|
||||
&[
|
||||
XrBinding::new("hand_pose", "/user/hand/left/input/grip/pose"),
|
||||
XrBinding::new("hand_pose", "/user/hand/right/input/grip/pose"),
|
||||
XrBinding::new("pointer_pose", "/user/hand/left/input/aim/pose"),
|
||||
XrBinding::new("pointer_pose", "/user/hand/right/input/aim/pose"),
|
||||
XrBinding::new("squeeze", "/user/hand/left/input/squeeze/value"),
|
||||
XrBinding::new("squeeze", "/user/hand/right/input/squeeze/value"),
|
||||
XrBinding::new("trigger", "/user/hand/left/input/trigger/value"),
|
||||
XrBinding::new("trigger", "/user/hand/right/input/trigger/value"),
|
||||
XrBinding::new("trigger_touched", "/user/hand/left/input/trigger/touch"),
|
||||
XrBinding::new("trigger_touched", "/user/hand/right/input/trigger/touch"),
|
||||
XrBinding::new("haptic_feedback", "/user/hand/left/output/haptic"),
|
||||
XrBinding::new("haptic_feedback", "/user/hand/right/output/haptic"),
|
||||
XrBinding::new("x_button", "/user/hand/left/input/x/click"),
|
||||
XrBinding::new("x_button_touch", "/user/hand/left/input/x/touch"),
|
||||
XrBinding::new("y_button", "/user/hand/left/input/y/click"),
|
||||
XrBinding::new("y_button_touch", "/user/hand/left/input/y/touch"),
|
||||
XrBinding::new("a_button", "/user/hand/right/input/a/click"),
|
||||
XrBinding::new("a_button_touch", "/user/hand/right/input/a/touch"),
|
||||
XrBinding::new("b_button", "/user/hand/right/input/b/click"),
|
||||
XrBinding::new("b_button_touch", "/user/hand/right/input/b/touch"),
|
||||
XrBinding::new("menu_button", "/user/hand/left/input/menu/click"),
|
||||
XrBinding::new("thumbstick_x", "/user/hand/left/input/thumbstick/x"),
|
||||
XrBinding::new("thumbstick_y", "/user/hand/left/input/thumbstick/y"),
|
||||
XrBinding::new("thumbstick_x", "/user/hand/right/input/thumbstick/x"),
|
||||
XrBinding::new("thumbstick_y", "/user/hand/right/input/thumbstick/y"),
|
||||
XrBinding::new("thumbstick_click", "/user/hand/left/input/thumbstick/click"),
|
||||
XrBinding::new(
|
||||
"thumbstick_click",
|
||||
"/user/hand/right/input/thumbstick/click",
|
||||
),
|
||||
XrBinding::new("thumbstick_touch", "/user/hand/left/input/thumbstick/touch"),
|
||||
XrBinding::new(
|
||||
"thumbstick_touch",
|
||||
"/user/hand/right/input/thumbstick/touch",
|
||||
),
|
||||
XrBinding::new("thumbrest_touch", "/user/hand/left/input/thumbrest/touch"),
|
||||
XrBinding::new("thumbrest_touch", "/user/hand/right/input/thumbrest/touch"),
|
||||
],
|
||||
);
|
||||
Ok(this)
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
time::{Time, Timer, TimerMode},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
input::XrInput,
|
||||
resources::{XrFrameState, XrInstance, XrSession, XrViews},
|
||||
};
|
||||
|
||||
use super::{
|
||||
actions::XrActionSets, oculus_touch::OculusController, trackers::OpenXRTrackingRoot, Hand,
|
||||
QuatConv, Vec3Conv,
|
||||
};
|
||||
|
||||
pub enum LocomotionType {
|
||||
Head,
|
||||
Hand,
|
||||
}
|
||||
|
||||
pub enum RotationType {
|
||||
Smooth,
|
||||
Snap,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct RotationTimer {
|
||||
pub timer: Timer,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct PrototypeLocomotionConfig {
|
||||
pub locomotion_type: LocomotionType,
|
||||
pub locomotion_speed: f32,
|
||||
pub rotation_type: RotationType,
|
||||
pub snap_angle: f32,
|
||||
pub smooth_rotation_speed: f32,
|
||||
pub rotation_stick_deadzone: f32,
|
||||
pub rotation_timer: RotationTimer,
|
||||
}
|
||||
|
||||
impl Default for PrototypeLocomotionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
locomotion_type: LocomotionType::Head,
|
||||
locomotion_speed: 1.0,
|
||||
rotation_type: RotationType::Smooth,
|
||||
snap_angle: 45.0 * (PI / 180.0),
|
||||
smooth_rotation_speed: 0.5 * PI,
|
||||
rotation_stick_deadzone: 0.2,
|
||||
rotation_timer: RotationTimer {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Once),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proto_locomotion(
|
||||
time: Res<Time>,
|
||||
mut tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>,
|
||||
oculus_controller: Res<OculusController>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
instance: Res<XrInstance>,
|
||||
session: Res<XrSession>,
|
||||
views: ResMut<XrViews>,
|
||||
mut gizmos: Gizmos,
|
||||
config_option: Option<ResMut<PrototypeLocomotionConfig>>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
match config_option {
|
||||
Some(_) => (),
|
||||
None => {
|
||||
info!("no locomotion config");
|
||||
return;
|
||||
}
|
||||
}
|
||||
//i hate this but im too tired to think
|
||||
let mut config = config_option.unwrap();
|
||||
//lock frame
|
||||
let frame_state = *frame_state.lock().unwrap();
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
let root = tracking_root_query.get_single_mut();
|
||||
match root {
|
||||
Ok(mut position) => {
|
||||
//get the stick input and do some maths
|
||||
let stick = controller.thumbstick(Hand::Left);
|
||||
let input = stick.x * position.0.right() + stick.y * position.0.forward();
|
||||
let reference_quat;
|
||||
match config.locomotion_type {
|
||||
LocomotionType::Head => {
|
||||
let v = views.lock().unwrap();
|
||||
let views = v.get(0);
|
||||
match views {
|
||||
Some(view) => {
|
||||
reference_quat = view.pose.orientation.to_quat();
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
LocomotionType::Hand => {
|
||||
let grip = controller.grip_space(Hand::Left);
|
||||
reference_quat = grip.0.pose.orientation.to_quat();
|
||||
}
|
||||
}
|
||||
let (yaw, _pitch, _roll) = reference_quat.to_euler(EulerRot::YXZ);
|
||||
let reference_quat = Quat::from_axis_angle(position.0.up(), yaw);
|
||||
let locomotion_vec = reference_quat.mul_vec3(input);
|
||||
position.0.translation +=
|
||||
locomotion_vec * config.locomotion_speed * time.delta_seconds();
|
||||
|
||||
//now time for rotation
|
||||
|
||||
match config.rotation_type {
|
||||
RotationType::Smooth => {
|
||||
//once again with the math
|
||||
let control_stick = controller.thumbstick(Hand::Right);
|
||||
let rot_input = -control_stick.x; //why is this negative i dont know
|
||||
if rot_input.abs() <= config.rotation_stick_deadzone {
|
||||
return;
|
||||
}
|
||||
let smoth_rot = Quat::from_axis_angle(
|
||||
position.0.up(),
|
||||
rot_input * config.smooth_rotation_speed * time.delta_seconds(),
|
||||
);
|
||||
//apply rotation
|
||||
let v = views.lock().unwrap();
|
||||
let views = v.get(0);
|
||||
match views {
|
||||
Some(view) => {
|
||||
let mut hmd_translation = view.pose.position.to_vec3();
|
||||
hmd_translation.y = 0.0;
|
||||
let local = position.0.translation;
|
||||
let global = position.0.rotation.mul_vec3(hmd_translation) + local;
|
||||
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN);
|
||||
position.0.rotate_around(global, smoth_rot);
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
RotationType::Snap => {
|
||||
//tick the timer
|
||||
config.rotation_timer.timer.tick(time.delta());
|
||||
if config.rotation_timer.timer.finished() {
|
||||
//now we can snap turn?
|
||||
//once again with the math
|
||||
let control_stick = controller.thumbstick(Hand::Right);
|
||||
let rot_input = -control_stick.x;
|
||||
if rot_input.abs() <= config.rotation_stick_deadzone {
|
||||
return;
|
||||
}
|
||||
let dir: f32 = match rot_input > 0.0 {
|
||||
true => 1.0,
|
||||
false => -1.0,
|
||||
};
|
||||
let smoth_rot =
|
||||
Quat::from_axis_angle(position.0.up(), config.snap_angle * dir);
|
||||
//apply rotation
|
||||
let v = views.lock().unwrap();
|
||||
let views = v.get(0);
|
||||
match views {
|
||||
Some(view) => {
|
||||
let mut hmd_translation = view.pose.position.to_vec3();
|
||||
hmd_translation.y = 0.0;
|
||||
let local = position.0.translation;
|
||||
let global = position.0.rotation.mul_vec3(hmd_translation) + local;
|
||||
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN);
|
||||
position.0.rotate_around(global, smoth_rot);
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
config.rotation_timer.timer.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => info!("too many tracking roots"),
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
use bevy::log::{debug, info};
|
||||
use bevy::prelude::{
|
||||
Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, Without,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
input::XrInput,
|
||||
resources::{XrFrameState, XrSession},
|
||||
};
|
||||
|
||||
use super::{actions::XrActionSets, oculus_touch::OculusController, Hand, QuatConv, Vec3Conv};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRTrackingRoot;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRTracker;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRLeftEye;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRRightEye;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRHMD;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRLeftController;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRRightController;
|
||||
#[derive(Component)]
|
||||
pub struct OpenXRController;
|
||||
#[derive(Component)]
|
||||
pub struct AimPose(pub Transform);
|
||||
|
||||
pub fn adopt_open_xr_trackers(
|
||||
query: Query<Entity, Added<OpenXRTracker>>,
|
||||
mut commands: Commands,
|
||||
tracking_root_query: Query<(Entity, With<OpenXRTrackingRoot>)>,
|
||||
) {
|
||||
let root = tracking_root_query.get_single();
|
||||
match root {
|
||||
Ok(thing) => {
|
||||
// info!("root is");
|
||||
for tracker in query.iter() {
|
||||
info!("we got a new tracker");
|
||||
commands.entity(thing.0).add_child(tracker);
|
||||
}
|
||||
}
|
||||
Err(_) => info!("root isnt spawned yet?"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_open_xr_controllers(
|
||||
oculus_controller: Res<OculusController>,
|
||||
mut left_controller_query: Query<(
|
||||
&mut Transform,
|
||||
Option<&mut AimPose>,
|
||||
With<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
)>,
|
||||
mut right_controller_query: Query<(
|
||||
&mut Transform,
|
||||
Option<&mut AimPose>,
|
||||
With<OpenXRRightController>,
|
||||
Without<OpenXRLeftController>,
|
||||
)>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
//lock dat frame?
|
||||
let frame_state = *frame_state.lock().unwrap();
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
//get left controller
|
||||
let left_grip_space = controller.grip_space(Hand::Left);
|
||||
let left_aim_space = controller.aim_space(Hand::Left);
|
||||
let left_postion = left_grip_space.0.pose.position.to_vec3();
|
||||
//TODO figure out how to not get the entity multiple times
|
||||
let left_aim_pose = left_controller_query.get_single_mut();
|
||||
//set aim pose
|
||||
match left_aim_pose {
|
||||
Ok(left_entity) => match left_entity.1 {
|
||||
Some(mut pose) => {
|
||||
*pose = AimPose(Transform {
|
||||
translation: left_aim_space.0.pose.position.to_vec3(),
|
||||
rotation: left_aim_space.0.pose.orientation.to_quat(),
|
||||
scale: Vec3::splat(1.0),
|
||||
});
|
||||
}
|
||||
None => (),
|
||||
},
|
||||
Err(_) => debug!("no left controlelr entity found"),
|
||||
}
|
||||
//set translation
|
||||
let left_translation = left_controller_query.get_single_mut();
|
||||
match left_translation {
|
||||
Ok(mut left_entity) => left_entity.0.translation = left_postion,
|
||||
Err(_) => (),
|
||||
}
|
||||
//set rotation
|
||||
let left_rotataion = left_controller_query.get_single_mut();
|
||||
match left_rotataion {
|
||||
Ok(mut left_entity) => {
|
||||
left_entity.0.rotation = left_grip_space.0.pose.orientation.to_quat()
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
//get right controller
|
||||
let right_grip_space = controller.grip_space(Hand::Right);
|
||||
let right_aim_space = controller.aim_space(Hand::Right);
|
||||
let right_postion = right_grip_space.0.pose.position.to_vec3();
|
||||
|
||||
let right_aim_pose = right_controller_query.get_single_mut();
|
||||
match right_aim_pose {
|
||||
Ok(right_entity) => match right_entity.1 {
|
||||
Some(mut pose) => {
|
||||
*pose = AimPose(Transform {
|
||||
translation: right_aim_space.0.pose.position.to_vec3(),
|
||||
rotation: right_aim_space.0.pose.orientation.to_quat(),
|
||||
scale: Vec3::splat(1.0),
|
||||
});
|
||||
}
|
||||
None => (),
|
||||
},
|
||||
Err(_) => debug!("no right controlelr entity found"),
|
||||
}
|
||||
//set translation
|
||||
let right_translation = right_controller_query.get_single_mut();
|
||||
match right_translation {
|
||||
Ok(mut right_entity) => right_entity.0.translation = right_postion,
|
||||
Err(_) => (),
|
||||
}
|
||||
//set rotation
|
||||
let right_rotataion = right_controller_query.get_single_mut();
|
||||
match right_rotataion {
|
||||
Ok(mut right_entity) => {
|
||||
right_entity.0.rotation = right_grip_space.0.pose.orientation.to_quat()
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
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::math::Vec3A;
|
||||
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
|
||||
}
|
||||
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
let tan_angle_left = self.fov.angle_left.tan();
|
||||
let tan_angle_right = self.fov.angle_right.tan();
|
||||
|
||||
let tan_angle_bottom = self.fov.angle_down.tan();
|
||||
let tan_angle_top = self.fov.angle_up.tan();
|
||||
|
||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
||||
[
|
||||
Vec3A::new(tan_angle_right, tan_angle_bottom, 1.0) * z_near, // bottom right
|
||||
Vec3A::new(tan_angle_right, tan_angle_top, 1.0) * z_near, // top right
|
||||
Vec3A::new(tan_angle_left, tan_angle_top, 1.0) * z_near, // top left
|
||||
Vec3A::new(tan_angle_left, tan_angle_bottom, 1.0) * z_near, // bottom left
|
||||
Vec3A::new(tan_angle_right, tan_angle_bottom, 1.0) * z_far, // bottom right
|
||||
Vec3A::new(tan_angle_right, tan_angle_top, 1.0) * z_far, // top right
|
||||
Vec3A::new(tan_angle_left, tan_angle_top, 1.0) * z_far, // top left
|
||||
Vec3A::new(tan_angle_left, tan_angle_bottom, 1.0) * z_far, // bottom left
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xr_camera_head_sync(
|
||||
views: ResMut<crate::resources::XrViews>,
|
||||
mut query: Query<(&mut Transform, &XrCameraType, &mut XRProjection)>,
|
||||
) {
|
||||
let mut f = || -> Option<()> {
|
||||
//TODO calculate HMD position
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user