remove everything

Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
Schmarni
2024-07-05 01:32:23 +02:00
parent 856300b661
commit e33adc85ce
33 changed files with 0 additions and 13625 deletions

View File

@@ -1,34 +0,0 @@
name: Build
on:
push:
branches:
- "main"
paths-ignore:
- "/docs"
- "README.md"
pull_request:
paths-ignore:
- "/docs"
- "README.md"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Cache"
uses: Swatinem/rust-cache@v2
- name: "External dependencies"
run: sudo apt-get update -y && sudo apt-get install -y libasound2-dev portaudio19-dev build-essential libpulse-dev libdbus-1-dev libudev-dev libopenxr-loader1 libopenxr-dev
- name: "Checks"
run: |
cargo fmt --check --all
#cargo clippy --no-deps --tests -- -D warnings
#cargo rustdoc -- -D warnings
- name: "Build"
run: |
cargo build
cargo build --examples
- name: "Test"
run: |
cargo test --verbose

5758
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +0,0 @@
[workspace]
resolver = "2"
members = [
"examples/android",
"examples/demo",
]
[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/awtterpip/bevy_oxr"
[workspace.dependencies]
eyre = "0.6.2"
bevy = "0.14.0"
openxr = "0.18"
color-eyre = "0.6.2"
[package]
name = "bevy_oxr"
version = "0.2.0"
description = "Community crate for OpenXR in Bevy"
edition.workspace = true
license.workspace = true
repository.workspace = true
[features]
default = ["vulkan", "d3d12"]
force-link = ["openxr/linked"]
vulkan = ["wgpu-core/vulkan"]
d3d12 = ["wgpu-core/dx12", "dep:winapi", "dep:d3d12"]
[dependencies]
ash = "0.37.3"
bevy.workspace = true
eyre.workspace = true
futures-lite = "2.0.1"
mint = "0.5.9"
wgpu = "0.20"
wgpu-core = "0.21"
wgpu-hal = "0.21"
[target.'cfg(windows)'.dependencies]
openxr = { workspace = true, features = [ "linked", "static", "mint" ] }
winapi = { version = "0.3.9", optional = true }
d3d12 = { version = "0.20", features = ["libloading"], optional = true }
[target.'cfg(all(target_family = "unix", not(target_arch = "wasm32")) )'.dependencies]
openxr = { workspace = true, features = [ "mint" ] }
[target.'cfg(all(not(target_family = "unix"), not(target_arch = "wasm32")))'.dependencies]
openxr = { workspace = true, features = [ "mint", "static" ] }
[target.'cfg(target_os = "android")'.dependencies]
ndk-context = "0.1"
jni = "0.20"
[dev-dependencies]
color-eyre.workspace = true
[[example]]
name = "xr"
path = "examples/xr.rs"
[profile.release]
debug = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]

View File

@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1,19 +0,0 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,41 +0,0 @@
# Bevy OpenXR
A crate for adding openxr support to Bevy ( planned to be upstreamed ).
To see it in action run the example in `examples` with `cargo run --example xr`
## This crate will not recive any feature or performance updates
the current implementation will not be updated!
we will release a version for bevy 0.14 once that releases but no further version will be supported!
there is a new rewrite that will be upstreamed into bevy in the future.
you can use that from this git repo if you want, but be warned it has a completly different public api.
## Discord
Come hang out if you have questions or issues
https://discord.gg/sqMw7UJhNc
![](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExY2FlOXJrOG1pbzFkYTVjZHIybndqamF1a2YwZHU3dXgyZGcwdmFzMiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/CHbQyXOT5yZZ1VQRh7/giphy-downsized-large.gif)
![](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbHVmZXc2b3VhcGE2eHE2c2Y3NDR6cXNibHdjNjk5MmtyOHlkMXkwZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/Hsvp5el2o7tzgOf9GQ/giphy-downsized-large.gif)
## Troubleshooting
- Make sure, if you're on Linux, that you have the `openxr` package installed on your system.
- I'm getting poor performance.
- Like other bevy projects, make sure you're building in release (example: `cargo run --example xr --release`)
## License
Unless otherwise specified, all code in this repository is dual-licensed under
either:
- MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
at your option. This means you can select the license you prefer!
### Your contributions
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.

View File

@@ -1 +0,0 @@
# Use defaults

View File

@@ -1,476 +0,0 @@
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
// use anyhow::Context;
use bevy::math::uvec2;
use bevy::prelude::*;
use bevy::render::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper,
};
use bevy::window::RawHandleWrapper;
use eyre::{Context, ContextCompat};
use openxr as xr;
use wgpu::Instance;
use wgpu_hal::{api::Dx12, Api};
use wgpu_hal::{Adapter as HalAdapter, Instance as HalInstance};
use winapi::shared::dxgiformat::{self, DXGI_FORMAT};
use winapi::um::{d3d12 as winapi_d3d12, d3dcommon};
use xr::EnvironmentBlendMode;
use crate::graphics::extensions::XrExtensions;
use crate::input::XrInput;
use crate::resources::{
OXrSessionSetupInfo, Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState,
XrFrameWaiter, XrInstance, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews,
};
#[cfg(all(feature = "d3d12", windows))]
use crate::resources::D3D12OXrSessionSetupInfo;
#[cfg(feature = "vulkan")]
use crate::resources::VulkanOXrSessionSetupInfo;
use super::{XrAppInfo, XrPreferdBlendMode};
use crate::VIEW_TYPE;
pub fn initialize_xr_instance(
window: Option<RawHandleWrapper>,
xr_entry: xr::Entry,
reqeusted_extensions: XrExtensions,
available_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo,
) -> eyre::Result<(
XrInstance,
OXrSessionSetupInfo,
XrEnvironmentBlendMode,
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
)> {
#[cfg(target_os = "android")]
xr_entry.initialize_android_loader()?;
assert!(available_extensions.raw().khr_d3d12_enable);
//info!("available xr exts: {:#?}", available_extensions);
let mut enabled_extensions: xr::ExtensionSet =
(available_extensions & reqeusted_extensions).into();
enabled_extensions.khr_d3d12_enable = true;
let available_layers = xr_entry.enumerate_layers()?;
//info!("available xr layers: {:#?}", available_layers);
let xr_instance = xr_entry.create_instance(
&xr::ApplicationInfo {
application_name: &app_info.name,
engine_name: "Bevy",
..Default::default()
},
&enabled_extensions,
&[],
)?;
info!("created OpenXR instance");
let instance_props = xr_instance.properties()?;
let xr_system_id = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
info!("created OpenXR system");
let system_props = xr_instance.system_properties(xr_system_id).unwrap();
info!(
"loaded OpenXR runtime: {} {} {}",
instance_props.runtime_name,
instance_props.runtime_version,
if system_props.system_name.is_empty() {
"<unnamed>"
} else {
&system_props.system_name
}
);
let blend_modes = xr_instance.enumerate_environment_blend_modes(xr_system_id, VIEW_TYPE)?;
let blend_mode: EnvironmentBlendMode = match prefered_blend_mode {
XrPreferdBlendMode::Opaque if blend_modes.contains(&EnvironmentBlendMode::OPAQUE) => {
bevy::log::info!("Using Opaque");
EnvironmentBlendMode::OPAQUE
}
XrPreferdBlendMode::Additive if blend_modes.contains(&EnvironmentBlendMode::ADDITIVE) => {
bevy::log::info!("Using Additive");
EnvironmentBlendMode::ADDITIVE
}
XrPreferdBlendMode::AlphaBlend
if blend_modes.contains(&EnvironmentBlendMode::ALPHA_BLEND) =>
{
bevy::log::info!("Using AlphaBlend");
EnvironmentBlendMode::ALPHA_BLEND
}
_ => {
bevy::log::info!("Using Opaque");
EnvironmentBlendMode::OPAQUE
}
};
let reqs = xr_instance.graphics_requirements::<xr::D3D12>(xr_system_id)?;
let instance_descriptor = &wgpu_hal::InstanceDescriptor {
name: &app_info.name,
dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or(
wgpu::Dx12Compiler::Dxc {
dxil_path: None,
dxc_path: None,
},
),
flags: wgpu::InstanceFlags::from_build_config().with_env(),
gles_minor_version: Default::default(),
};
let wgpu_raw_instance: wgpu_hal::dx12::Instance =
unsafe { wgpu_hal::dx12::Instance::init(instance_descriptor)? };
let wgpu_adapters: Vec<wgpu_hal::ExposedAdapter<wgpu_hal::dx12::Api>> =
unsafe { wgpu_raw_instance.enumerate_adapters() };
let wgpu_exposed_adapter = wgpu_adapters
.into_iter()
.find(|a| {
let mut desc = unsafe { std::mem::zeroed() };
unsafe { a.adapter.raw_adapter().GetDesc1(&mut desc) };
desc.AdapterLuid.HighPart == reqs.adapter_luid.HighPart
&& desc.AdapterLuid.LowPart == reqs.adapter_luid.LowPart
})
.context("failed to find DXGI adapter matching LUID provided by runtime")?;
let wgpu_instance =
unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Dx12>(wgpu_raw_instance) };
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::MULTIVIEW
| wgpu::Features::MULTI_DRAW_INDIRECT_COUNT
| wgpu::Features::MULTI_DRAW_INDIRECT;
let wgpu_limits = wgpu::Limits {
max_bind_groups: 8,
max_storage_buffer_binding_size: wgpu_exposed_adapter
.capabilities
.limits
.max_storage_buffer_binding_size,
max_push_constant_size: 4,
..Default::default()
};
let wgpu_open_device = unsafe {
wgpu_exposed_adapter
.adapter
.open(wgpu_features, &wgpu_limits)?
};
let device_supported_feature_level: d3d12::FeatureLevel =
get_device_feature_level(wgpu_open_device.device.raw_device());
if (device_supported_feature_level as u32) < (reqs.min_feature_level as u32) {
panic!(
"OpenXR runtime requires D3D12 feature level >= {}",
reqs.min_feature_level
);
}
let (session, frame_wait, frame_stream) = unsafe {
xr_instance.create_session::<xr::D3D12>(
xr_system_id,
&xr::d3d::SessionCreateInfoD3D12 {
device: wgpu_open_device.device.raw_device().as_mut_ptr().cast(),
queue: wgpu_open_device.device.raw_queue().as_mut_ptr().cast(),
},
)
}?;
let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) };
let raw_device = wgpu_open_device.device.raw_device().as_mut_ptr();
let raw_queue = wgpu_open_device.device.raw_queue().as_mut_ptr();
let (wgpu_device, wgpu_queue) = unsafe {
wgpu_adapter.create_device_from_hal(
wgpu_open_device,
&wgpu::DeviceDescriptor {
label: Some("bevy_oxr device"),
required_features: wgpu_features,
required_limits: wgpu_limits,
},
None,
)?
};
Ok((
xr_instance.into(),
OXrSessionSetupInfo::D3D12(D3D12OXrSessionSetupInfo {
raw_device,
raw_queue,
xr_system_id,
}),
blend_mode.into(),
wgpu_device.into(),
RenderQueue(Arc::new(WgpuWrapper::new(wgpu_queue))),
RenderAdapterInfo(WgpuWrapper::new(wgpu_adapter.get_info())),
RenderAdapter(Arc::new(WgpuWrapper::new(wgpu_adapter))),
wgpu_instance.into(),
))
}
pub fn start_xr_session(
window: Option<RawHandleWrapper>,
ptrs: &OXrSessionSetupInfo,
xr_instance: &XrInstance,
render_device: &RenderDevice,
render_adapter: &RenderAdapter,
wgpu_instance: &Instance,
) -> eyre::Result<(
XrSession,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)> {
let wgpu_device = render_device.wgpu_device();
let wgpu_adapter = &render_adapter.0;
#[allow(unreachable_patterns)]
let setup_info = match ptrs {
OXrSessionSetupInfo::D3D12(v) => v,
_ => eyre::bail!("Wrong Graphics Api"),
};
let (session, frame_wait, frame_stream) = unsafe {
xr_instance.create_session::<xr::D3D12>(
setup_info.xr_system_id,
&xr::d3d::SessionCreateInfoD3D12 {
device: setup_info.raw_device.cast(),
queue: setup_info.raw_queue.cast(),
},
)
}?;
let views =
xr_instance.enumerate_view_configuration_views(setup_info.xr_system_id, VIEW_TYPE)?;
let surface = window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle();
wgpu_instance
.create_surface(handle)
.expect("Failed to create wgpu surface")
});
let swapchain_format = surface
.as_ref()
.map(|surface| surface.get_capabilities(wgpu_adapter).formats[0])
.unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
// TODO: Log swapchain format
let resolution = uvec2(
views[0].recommended_image_rect_width,
views[0].recommended_image_rect_height,
);
let handle = session
.create_swapchain(&xr::SwapchainCreateInfo {
create_flags: xr::SwapchainCreateFlags::EMPTY,
usage_flags: xr::SwapchainUsageFlags::COLOR_ATTACHMENT
| xr::SwapchainUsageFlags::SAMPLED,
format: wgpu_to_d3d12(swapchain_format).expect("Unsupported texture format"),
// 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`.
sample_count: 1,
width: resolution.x,
height: resolution.y,
face_count: 1,
array_size: 2,
mip_count: 1,
})
.unwrap();
let images = handle.enumerate_images().unwrap();
let buffers = images
.into_iter()
.map(|color_image| {
info!("image map swapchain");
let wgpu_hal_texture = unsafe {
<Dx12 as Api>::Device::texture_from_raw(
d3d12::ComPtr::from_raw(color_image as *mut _),
swapchain_format,
wgpu::TextureDimension::D2,
wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
1,
1,
)
};
let texture = unsafe {
wgpu_device.create_texture_from_hal::<Dx12>(
wgpu_hal_texture,
&wgpu::TextureDescriptor {
label: Some("bevy_openxr swapchain"),
size: wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: swapchain_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
},
)
};
texture
})
.collect();
Ok((
XrSession::D3D12(session.clone()),
resolution.into(),
swapchain_format.into(),
// TODO: this shouldn't be in here
AtomicBool::new(false).into(),
frame_wait.into(),
Swapchain::D3D12(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())?,
Vec::default().into(),
// TODO: Feels wrong to return a FrameState here, we probably should just wait for the next frame
xr::FrameState {
predicted_display_time: xr::Time::from_nanos(1),
predicted_display_period: xr::Duration::from_nanos(1),
should_render: true,
}
.into(),
))
}
// Extracted from https://github.com/gfx-rs/wgpu/blob/1161a22f4fbb4fc204eb06f2ac4243f83e0e980d/wgpu-hal/src/dx12/adapter.rs#L73-L94
// license: MIT OR Apache-2.0
fn get_device_feature_level(
device: &d3d12::ComPtr<winapi_d3d12::ID3D12Device>,
) -> d3d12::FeatureLevel {
// Detect the highest supported feature level.
let d3d_feature_level = [
d3d12::FeatureLevel::L12_1,
d3d12::FeatureLevel::L12_0,
d3d12::FeatureLevel::L11_1,
d3d12::FeatureLevel::L11_0,
];
type FeatureLevelsInfo = winapi_d3d12::D3D12_FEATURE_DATA_FEATURE_LEVELS;
let mut device_levels: FeatureLevelsInfo = unsafe { std::mem::zeroed() };
device_levels.NumFeatureLevels = d3d_feature_level.len() as u32;
device_levels.pFeatureLevelsRequested = d3d_feature_level.as_ptr().cast();
unsafe {
device.CheckFeatureSupport(
winapi_d3d12::D3D12_FEATURE_FEATURE_LEVELS,
(&mut device_levels as *mut FeatureLevelsInfo).cast(),
std::mem::size_of::<FeatureLevelsInfo>() as _,
)
};
// This cast should never fail because we only requested feature levels that are already in the enum.
let max_feature_level = d3d12::FeatureLevel::try_from(device_levels.MaxSupportedFeatureLevel)
.expect("Unexpected feature level");
max_feature_level
}
fn wgpu_to_d3d12(format: wgpu::TextureFormat) -> Option<DXGI_FORMAT> {
// Copied wholesale from:
// https://github.com/gfx-rs/wgpu/blob/v0.19/wgpu-hal/src/auxil/dxgi/conv.rs#L12-L94
// license: MIT OR Apache-2.0
use wgpu::TextureFormat as Tf;
use winapi::shared::dxgiformat::*;
Some(match format {
Tf::R8Unorm => DXGI_FORMAT_R8_UNORM,
Tf::R8Snorm => DXGI_FORMAT_R8_SNORM,
Tf::R8Uint => DXGI_FORMAT_R8_UINT,
Tf::R8Sint => DXGI_FORMAT_R8_SINT,
Tf::R16Uint => DXGI_FORMAT_R16_UINT,
Tf::R16Sint => DXGI_FORMAT_R16_SINT,
Tf::R16Unorm => DXGI_FORMAT_R16_UNORM,
Tf::R16Snorm => DXGI_FORMAT_R16_SNORM,
Tf::R16Float => DXGI_FORMAT_R16_FLOAT,
Tf::Rg8Unorm => DXGI_FORMAT_R8G8_UNORM,
Tf::Rg8Snorm => DXGI_FORMAT_R8G8_SNORM,
Tf::Rg8Uint => DXGI_FORMAT_R8G8_UINT,
Tf::Rg8Sint => DXGI_FORMAT_R8G8_SINT,
Tf::Rg16Unorm => DXGI_FORMAT_R16G16_UNORM,
Tf::Rg16Snorm => DXGI_FORMAT_R16G16_SNORM,
Tf::R32Uint => DXGI_FORMAT_R32_UINT,
Tf::R32Sint => DXGI_FORMAT_R32_SINT,
Tf::R32Float => DXGI_FORMAT_R32_FLOAT,
Tf::Rg16Uint => DXGI_FORMAT_R16G16_UINT,
Tf::Rg16Sint => DXGI_FORMAT_R16G16_SINT,
Tf::Rg16Float => DXGI_FORMAT_R16G16_FLOAT,
Tf::Rgba8Unorm => DXGI_FORMAT_R8G8B8A8_UNORM,
Tf::Rgba8UnormSrgb => DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
Tf::Bgra8UnormSrgb => DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
Tf::Rgba8Snorm => DXGI_FORMAT_R8G8B8A8_SNORM,
Tf::Bgra8Unorm => DXGI_FORMAT_B8G8R8A8_UNORM,
Tf::Rgba8Uint => DXGI_FORMAT_R8G8B8A8_UINT,
Tf::Rgba8Sint => DXGI_FORMAT_R8G8B8A8_SINT,
Tf::Rgb9e5Ufloat => DXGI_FORMAT_R9G9B9E5_SHAREDEXP,
Tf::Rgb10a2Uint => DXGI_FORMAT_R10G10B10A2_UINT,
Tf::Rgb10a2Unorm => DXGI_FORMAT_R10G10B10A2_UNORM,
Tf::Rg11b10Float => DXGI_FORMAT_R11G11B10_FLOAT,
Tf::Rg32Uint => DXGI_FORMAT_R32G32_UINT,
Tf::Rg32Sint => DXGI_FORMAT_R32G32_SINT,
Tf::Rg32Float => DXGI_FORMAT_R32G32_FLOAT,
Tf::Rgba16Uint => DXGI_FORMAT_R16G16B16A16_UINT,
Tf::Rgba16Sint => DXGI_FORMAT_R16G16B16A16_SINT,
Tf::Rgba16Unorm => DXGI_FORMAT_R16G16B16A16_UNORM,
Tf::Rgba16Snorm => DXGI_FORMAT_R16G16B16A16_SNORM,
Tf::Rgba16Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
Tf::Rgba32Uint => DXGI_FORMAT_R32G32B32A32_UINT,
Tf::Rgba32Sint => DXGI_FORMAT_R32G32B32A32_SINT,
Tf::Rgba32Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
Tf::Stencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT,
Tf::Depth16Unorm => DXGI_FORMAT_D16_UNORM,
Tf::Depth24Plus => DXGI_FORMAT_D24_UNORM_S8_UINT,
Tf::Depth24PlusStencil8 => DXGI_FORMAT_D24_UNORM_S8_UINT,
Tf::Depth32Float => DXGI_FORMAT_D32_FLOAT,
Tf::Depth32FloatStencil8 => DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
Tf::NV12 => DXGI_FORMAT_NV12,
Tf::Bc1RgbaUnorm => DXGI_FORMAT_BC1_UNORM,
Tf::Bc1RgbaUnormSrgb => DXGI_FORMAT_BC1_UNORM_SRGB,
Tf::Bc2RgbaUnorm => DXGI_FORMAT_BC2_UNORM,
Tf::Bc2RgbaUnormSrgb => DXGI_FORMAT_BC2_UNORM_SRGB,
Tf::Bc3RgbaUnorm => DXGI_FORMAT_BC3_UNORM,
Tf::Bc3RgbaUnormSrgb => DXGI_FORMAT_BC3_UNORM_SRGB,
Tf::Bc4RUnorm => DXGI_FORMAT_BC4_UNORM,
Tf::Bc4RSnorm => DXGI_FORMAT_BC4_SNORM,
Tf::Bc5RgUnorm => DXGI_FORMAT_BC5_UNORM,
Tf::Bc5RgSnorm => DXGI_FORMAT_BC5_SNORM,
Tf::Bc6hRgbUfloat => DXGI_FORMAT_BC6H_UF16,
Tf::Bc6hRgbFloat => DXGI_FORMAT_BC6H_SF16,
Tf::Bc7RgbaUnorm => DXGI_FORMAT_BC7_UNORM,
Tf::Bc7RgbaUnormSrgb => DXGI_FORMAT_BC7_UNORM_SRGB,
Tf::Etc2Rgb8Unorm
| Tf::Etc2Rgb8UnormSrgb
| Tf::Etc2Rgb8A1Unorm
| Tf::Etc2Rgb8A1UnormSrgb
| Tf::Etc2Rgba8Unorm
| Tf::Etc2Rgba8UnormSrgb
| Tf::EacR11Unorm
| Tf::EacR11Snorm
| Tf::EacRg11Unorm
| Tf::EacRg11Snorm
| Tf::Astc {
block: _,
channel: _,
} => return None,
})
}

View File

@@ -1,252 +0,0 @@
use openxr::ExtensionSet;
use std::ops;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct XrExtensions(ExtensionSet);
impl XrExtensions {
pub fn raw_mut(&mut self) -> &mut ExtensionSet {
&mut self.0
}
pub fn raw(&self) -> &ExtensionSet {
&self.0
}
pub fn enable_fb_passthrough(&mut self) -> &mut Self {
self.0.fb_passthrough = true;
self
}
pub fn disable_fb_passthrough(&mut self) -> &mut Self {
self.0.fb_passthrough = false;
self
}
pub fn enable_hand_tracking(&mut self) -> &mut Self {
self.0.ext_hand_tracking = true;
self
}
pub fn disable_hand_tracking(&mut self) -> &mut Self {
self.0.ext_hand_tracking = false;
self
}
pub fn enable_local_floor(&mut self) -> &mut Self {
self.0.ext_local_floor = true;
self
}
pub fn disable_local_floor(&mut self) -> &mut Self {
self.0.ext_local_floor = false;
self
}
}
impl From<ExtensionSet> for XrExtensions {
fn from(value: ExtensionSet) -> Self {
Self(value)
}
}
impl From<XrExtensions> for ExtensionSet {
fn from(val: XrExtensions) -> Self {
val.0
}
}
impl Default for XrExtensions {
fn default() -> Self {
let mut exts = ExtensionSet::default();
exts.ext_hand_tracking = true;
exts.ext_local_floor = true;
Self(exts)
}
}
impl ops::BitAnd for XrExtensions {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
let mut out = ExtensionSet::default();
out.ext_local_floor = self.0.ext_local_floor && rhs.0.ext_local_floor;
out.almalence_digital_lens_control =
self.0.almalence_digital_lens_control && rhs.0.almalence_digital_lens_control;
out.epic_view_configuration_fov =
self.0.epic_view_configuration_fov && rhs.0.epic_view_configuration_fov;
out.ext_performance_settings =
self.0.ext_performance_settings && rhs.0.ext_performance_settings;
out.ext_thermal_query = self.0.ext_thermal_query && rhs.0.ext_thermal_query;
out.ext_debug_utils = self.0.ext_debug_utils && rhs.0.ext_debug_utils;
out.ext_eye_gaze_interaction =
self.0.ext_eye_gaze_interaction && rhs.0.ext_eye_gaze_interaction;
out.ext_view_configuration_depth_range =
self.0.ext_view_configuration_depth_range && rhs.0.ext_view_configuration_depth_range;
out.ext_conformance_automation =
self.0.ext_conformance_automation && rhs.0.ext_conformance_automation;
out.ext_hand_tracking = self.0.ext_hand_tracking && rhs.0.ext_hand_tracking;
out.ext_dpad_binding = self.0.ext_dpad_binding && rhs.0.ext_dpad_binding;
out.ext_hand_joints_motion_range =
self.0.ext_hand_joints_motion_range && rhs.0.ext_hand_joints_motion_range;
out.ext_samsung_odyssey_controller =
self.0.ext_samsung_odyssey_controller && rhs.0.ext_samsung_odyssey_controller;
out.ext_hp_mixed_reality_controller =
self.0.ext_hp_mixed_reality_controller && rhs.0.ext_hp_mixed_reality_controller;
out.ext_palm_pose = self.0.ext_palm_pose && rhs.0.ext_palm_pose;
out.ext_uuid = self.0.ext_uuid && rhs.0.ext_uuid;
// out.extx_overlay = self.0.extx_overlay && rhs.0.extx_overlay;
out.fb_composition_layer_image_layout =
self.0.fb_composition_layer_image_layout && rhs.0.fb_composition_layer_image_layout;
out.fb_composition_layer_alpha_blend =
self.0.fb_composition_layer_alpha_blend && rhs.0.fb_composition_layer_alpha_blend;
out.fb_swapchain_update_state =
self.0.fb_swapchain_update_state && rhs.0.fb_swapchain_update_state;
out.fb_composition_layer_secure_content =
self.0.fb_composition_layer_secure_content && rhs.0.fb_composition_layer_secure_content;
out.fb_display_refresh_rate =
self.0.fb_display_refresh_rate && rhs.0.fb_display_refresh_rate;
out.fb_color_space = self.0.fb_color_space && rhs.0.fb_color_space;
out.fb_hand_tracking_mesh = self.0.fb_hand_tracking_mesh && rhs.0.fb_hand_tracking_mesh;
out.fb_hand_tracking_aim = self.0.fb_hand_tracking_aim && rhs.0.fb_hand_tracking_aim;
out.fb_hand_tracking_capsules =
self.0.fb_hand_tracking_capsules && rhs.0.fb_hand_tracking_capsules;
out.fb_spatial_entity = self.0.fb_spatial_entity && rhs.0.fb_spatial_entity;
out.fb_foveation = self.0.fb_foveation && rhs.0.fb_foveation;
out.fb_foveation_configuration =
self.0.fb_foveation_configuration && rhs.0.fb_foveation_configuration;
out.fb_keyboard_tracking = self.0.fb_keyboard_tracking && rhs.0.fb_keyboard_tracking;
out.fb_triangle_mesh = self.0.fb_triangle_mesh && rhs.0.fb_triangle_mesh;
out.fb_passthrough = self.0.fb_passthrough && rhs.0.fb_passthrough;
out.fb_render_model = self.0.fb_render_model && rhs.0.fb_render_model;
out.fb_spatial_entity_query =
self.0.fb_spatial_entity_query && rhs.0.fb_spatial_entity_query;
out.fb_spatial_entity_storage =
self.0.fb_spatial_entity_storage && rhs.0.fb_spatial_entity_storage;
out.fb_foveation_vulkan = self.0.fb_foveation_vulkan && rhs.0.fb_foveation_vulkan;
out.fb_swapchain_update_state_opengl_es =
self.0.fb_swapchain_update_state_opengl_es && rhs.0.fb_swapchain_update_state_opengl_es;
out.fb_swapchain_update_state_vulkan =
self.0.fb_swapchain_update_state_vulkan && rhs.0.fb_swapchain_update_state_vulkan;
out.fb_space_warp = self.0.fb_space_warp && rhs.0.fb_space_warp;
out.fb_scene = self.0.fb_scene && rhs.0.fb_scene;
out.fb_spatial_entity_container =
self.0.fb_spatial_entity_container && rhs.0.fb_spatial_entity_container;
out.fb_passthrough_keyboard_hands =
self.0.fb_passthrough_keyboard_hands && rhs.0.fb_passthrough_keyboard_hands;
out.fb_composition_layer_settings =
self.0.fb_composition_layer_settings && rhs.0.fb_composition_layer_settings;
out.htc_vive_cosmos_controller_interaction = self.0.htc_vive_cosmos_controller_interaction
&& rhs.0.htc_vive_cosmos_controller_interaction;
out.htc_facial_tracking = self.0.htc_facial_tracking && rhs.0.htc_facial_tracking;
out.htc_vive_focus3_controller_interaction = self.0.htc_vive_focus3_controller_interaction
&& rhs.0.htc_vive_focus3_controller_interaction;
out.htc_hand_interaction = self.0.htc_hand_interaction && rhs.0.htc_hand_interaction;
out.htc_vive_wrist_tracker_interaction =
self.0.htc_vive_wrist_tracker_interaction && rhs.0.htc_vive_wrist_tracker_interaction;
// out.htcx_vive_tracker_interaction =
// self.0.htcx_vive_tracker_interaction && rhs.0.htcx_vive_tracker_interaction;
out.huawei_controller_interaction =
self.0.huawei_controller_interaction && rhs.0.huawei_controller_interaction;
out.khr_composition_layer_cube =
self.0.khr_composition_layer_cube && rhs.0.khr_composition_layer_cube;
out.khr_composition_layer_depth =
self.0.khr_composition_layer_depth && rhs.0.khr_composition_layer_depth;
out.khr_vulkan_swapchain_format_list =
self.0.khr_vulkan_swapchain_format_list && rhs.0.khr_vulkan_swapchain_format_list;
out.khr_composition_layer_cylinder =
self.0.khr_composition_layer_cylinder && rhs.0.khr_composition_layer_cylinder;
out.khr_composition_layer_equirect =
self.0.khr_composition_layer_equirect && rhs.0.khr_composition_layer_equirect;
out.khr_opengl_enable = self.0.khr_opengl_enable && rhs.0.khr_opengl_enable;
out.khr_opengl_es_enable = self.0.khr_opengl_es_enable && rhs.0.khr_opengl_es_enable;
out.khr_vulkan_enable = self.0.khr_vulkan_enable && rhs.0.khr_vulkan_enable;
out.khr_visibility_mask = self.0.khr_visibility_mask && rhs.0.khr_visibility_mask;
out.khr_composition_layer_color_scale_bias = self.0.khr_composition_layer_color_scale_bias
&& rhs.0.khr_composition_layer_color_scale_bias;
out.khr_convert_timespec_time =
self.0.khr_convert_timespec_time && rhs.0.khr_convert_timespec_time;
out.khr_loader_init = self.0.khr_loader_init && rhs.0.khr_loader_init;
out.khr_vulkan_enable2 = self.0.khr_vulkan_enable2 && rhs.0.khr_vulkan_enable2;
out.khr_composition_layer_equirect2 =
self.0.khr_composition_layer_equirect2 && rhs.0.khr_composition_layer_equirect2;
out.khr_binding_modification =
self.0.khr_binding_modification && rhs.0.khr_binding_modification;
out.khr_swapchain_usage_input_attachment_bit =
self.0.khr_swapchain_usage_input_attachment_bit
&& rhs.0.khr_swapchain_usage_input_attachment_bit;
out.meta_vulkan_swapchain_create_info =
self.0.meta_vulkan_swapchain_create_info && rhs.0.meta_vulkan_swapchain_create_info;
out.meta_performance_metrics =
self.0.meta_performance_metrics && rhs.0.meta_performance_metrics;
out.ml_ml2_controller_interaction =
self.0.ml_ml2_controller_interaction && rhs.0.ml_ml2_controller_interaction;
out.mnd_headless = self.0.mnd_headless && rhs.0.mnd_headless;
out.mnd_swapchain_usage_input_attachment_bit =
self.0.mnd_swapchain_usage_input_attachment_bit
&& rhs.0.mnd_swapchain_usage_input_attachment_bit;
// out.mndx_egl_enable = self.0.mndx_egl_enable && rhs.0.mndx_egl_enable;
out.msft_unbounded_reference_space =
self.0.msft_unbounded_reference_space && rhs.0.msft_unbounded_reference_space;
out.msft_spatial_anchor = self.0.msft_spatial_anchor && rhs.0.msft_spatial_anchor;
out.msft_spatial_graph_bridge =
self.0.msft_spatial_graph_bridge && rhs.0.msft_spatial_graph_bridge;
out.msft_hand_interaction = self.0.msft_hand_interaction && rhs.0.msft_hand_interaction;
out.msft_hand_tracking_mesh =
self.0.msft_hand_tracking_mesh && rhs.0.msft_hand_tracking_mesh;
out.msft_secondary_view_configuration =
self.0.msft_secondary_view_configuration && rhs.0.msft_secondary_view_configuration;
out.msft_first_person_observer =
self.0.msft_first_person_observer && rhs.0.msft_first_person_observer;
out.msft_controller_model = self.0.msft_controller_model && rhs.0.msft_controller_model;
out.msft_composition_layer_reprojection =
self.0.msft_composition_layer_reprojection && rhs.0.msft_composition_layer_reprojection;
out.msft_spatial_anchor_persistence =
self.0.msft_spatial_anchor_persistence && rhs.0.msft_spatial_anchor_persistence;
out.oculus_audio_device_guid =
self.0.oculus_audio_device_guid && rhs.0.oculus_audio_device_guid;
out.ultraleap_hand_tracking_forearm =
self.0.ultraleap_hand_tracking_forearm && rhs.0.ultraleap_hand_tracking_forearm;
out.valve_analog_threshold = self.0.valve_analog_threshold && rhs.0.valve_analog_threshold;
out.varjo_quad_views = self.0.varjo_quad_views && rhs.0.varjo_quad_views;
out.varjo_foveated_rendering =
self.0.varjo_foveated_rendering && rhs.0.varjo_foveated_rendering;
out.varjo_composition_layer_depth_test =
self.0.varjo_composition_layer_depth_test && rhs.0.varjo_composition_layer_depth_test;
out.varjo_environment_depth_estimation =
self.0.varjo_environment_depth_estimation && rhs.0.varjo_environment_depth_estimation;
out.varjo_marker_tracking = self.0.varjo_marker_tracking && rhs.0.varjo_marker_tracking;
out.varjo_view_offset = self.0.varjo_view_offset && rhs.0.varjo_view_offset;
and_android_only_exts(&self, &rhs, &mut out);
and_windows_only_exts(&self, &rhs, &mut out);
for ext in self.0.other {
if rhs.0.other.contains(&ext) {
out.other.push(ext);
}
}
Self(out)
}
}
#[cfg(not(target_os = "android"))]
fn and_android_only_exts(lhs: &XrExtensions, rhs: &XrExtensions, out: &mut ExtensionSet) {}
#[cfg(not(windows))]
fn and_windows_only_exts(lhs: &XrExtensions, rhs: &XrExtensions, out: &mut ExtensionSet) {}
#[cfg(target_os = "android")]
fn and_android_only_exts(lhs: &XrExtensions, rhs: &XrExtensions, out: &mut ExtensionSet) {
out.oculus_android_session_state_enable =
lhs.0.oculus_android_session_state_enable && rhs.0.oculus_android_session_state_enable;
out.khr_loader_init_android = lhs.0.khr_loader_init_android && rhs.0.khr_loader_init_android;
out.fb_android_surface_swapchain_create =
lhs.0.fb_android_surface_swapchain_create && rhs.0.fb_android_surface_swapchain_create;
out.fb_swapchain_update_state_android_surface = lhs.0.fb_swapchain_update_state_android_surface
&& rhs.0.fb_swapchain_update_state_android_surface;
out.khr_android_thread_settings =
lhs.0.khr_android_thread_settings && rhs.0.khr_android_thread_settings;
out.khr_android_surface_swapchain =
lhs.0.khr_android_surface_swapchain && rhs.0.khr_android_surface_swapchain;
out.khr_android_create_instance =
lhs.0.khr_android_create_instance && rhs.0.khr_android_create_instance;
}
#[cfg(windows)]
fn and_windows_only_exts(lhs: &XrExtensions, rhs: &XrExtensions, out: &mut ExtensionSet) {
out.ext_win32_appcontainer_compatible =
lhs.0.ext_win32_appcontainer_compatible && rhs.0.ext_win32_appcontainer_compatible;
out.khr_d3d11_enable = lhs.0.khr_d3d11_enable && rhs.0.khr_d3d11_enable;
out.khr_d3d12_enable = lhs.0.khr_d3d12_enable && rhs.0.khr_d3d12_enable;
out.khr_win32_convert_performance_counter_time =
lhs.0.khr_win32_convert_performance_counter_time
&& rhs.0.khr_win32_convert_performance_counter_time;
out.msft_perception_anchor_interop =
lhs.0.msft_perception_anchor_interop && rhs.0.msft_perception_anchor_interop;
out.msft_holographic_window_attachment =
lhs.0.msft_holographic_window_attachment && rhs.0.msft_holographic_window_attachment;
}

View File

@@ -1,243 +0,0 @@
pub mod extensions;
#[cfg(all(feature = "d3d12", windows))]
mod d3d12;
#[cfg(feature = "vulkan")]
mod vulkan;
use std::sync::Arc;
use bevy::ecs::query::With;
use bevy::ecs::system::{Query, SystemState};
use bevy::ecs::world::World;
use bevy::render::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue, WgpuWrapper,
};
use bevy::window::{PrimaryWindow, RawHandleWrapper};
use wgpu::Instance;
use crate::input::XrInput;
use crate::resources::{
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrSession, XrSessionRunning, XrSwapchain, XrViews,
};
use crate::OXrSessionSetupInfo;
use crate::Backend;
use openxr as xr;
use self::extensions::XrExtensions;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum XrPreferdBlendMode {
Opaque,
Additive,
AlphaBlend,
}
impl Default for XrPreferdBlendMode {
fn default() -> Self {
Self::Opaque
}
}
#[derive(Clone, Debug)]
pub struct XrAppInfo {
pub name: String,
}
impl Default for XrAppInfo {
fn default() -> Self {
Self {
name: "Ambient".into(),
}
}
}
pub fn start_xr_session(
window: Option<RawHandleWrapper>,
session_setup_data: &OXrSessionSetupInfo,
xr_instance: &XrInstance,
render_device: &RenderDevice,
render_adapter: &RenderAdapter,
wgpu_instance: &Instance,
) -> eyre::Result<(
XrSession,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)> {
match session_setup_data {
#[cfg(feature = "vulkan")]
OXrSessionSetupInfo::Vulkan(_) => vulkan::start_xr_session(
window,
session_setup_data,
xr_instance,
render_device,
render_adapter,
wgpu_instance,
),
#[cfg(all(feature = "d3d12", windows))]
OXrSessionSetupInfo::D3D12(_) => d3d12::start_xr_session(
window,
session_setup_data,
xr_instance,
render_device,
render_adapter,
wgpu_instance,
),
}
}
pub fn initialize_xr_instance(
backend_preference: &[Backend],
window: Option<RawHandleWrapper>,
reqeusted_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo,
) -> eyre::Result<(
XrInstance,
OXrSessionSetupInfo,
XrEnvironmentBlendMode,
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
)> {
if backend_preference.is_empty() {
eyre::bail!("Cannot initialize with no backend selected");
}
let xr_entry = xr_entry()?;
#[cfg(target_os = "android")]
xr_entry.initialize_android_loader()?;
let available_extensions: XrExtensions = xr_entry.enumerate_extensions()?.into();
for backend in backend_preference {
match backend {
#[cfg(feature = "vulkan")]
Backend::Vulkan => {
if !available_extensions.raw().khr_vulkan_enable2 {
continue;
}
return vulkan::initialize_xr_instance(
window,
xr_entry,
reqeusted_extensions,
available_extensions,
prefered_blend_mode,
app_info,
);
}
#[cfg(all(feature = "d3d12", windows))]
Backend::D3D12 => {
if !available_extensions.raw().khr_d3d12_enable {
continue;
}
return d3d12::initialize_xr_instance(
window,
xr_entry,
reqeusted_extensions,
available_extensions,
prefered_blend_mode,
app_info,
);
}
}
}
eyre::bail!(
"No selected backend was supported by the runtime. Selected: {:?}",
backend_preference
);
}
pub fn try_full_init(
world: &mut World,
backend_preference: &[Backend],
reqeusted_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo,
) -> eyre::Result<(
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
RenderInstance,
)> {
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
SystemState::new(world);
let primary_window = system_state.get(world).get_single().ok().cloned();
let (
xr_instance,
setup_info,
blend_mode,
render_device,
render_queue,
render_adapter_info,
render_adapter,
wgpu_instance,
) = initialize_xr_instance(
backend_preference,
primary_window.clone(),
reqeusted_extensions,
prefered_blend_mode,
app_info,
)?;
world.insert_resource(xr_instance);
world.insert_non_send_resource(setup_info);
// TODO: move BlendMode the session init?
world.insert_resource(blend_mode);
let setup_info = world
.get_non_send_resource::<OXrSessionSetupInfo>()
.unwrap();
let xr_instance = world.get_resource::<XrInstance>().unwrap();
let (
xr_session,
xr_resolution,
xr_format,
xr_session_running,
xr_frame_waiter,
xr_swapchain,
xr_input,
xr_views,
xr_frame_state,
) = start_xr_session(
primary_window,
setup_info,
xr_instance,
&render_device,
&render_adapter,
&wgpu_instance,
)?;
world.insert_resource(xr_session);
world.insert_resource(xr_resolution);
world.insert_resource(xr_format);
world.insert_resource(xr_session_running);
world.insert_resource(xr_frame_waiter);
world.insert_resource(xr_swapchain);
world.insert_resource(xr_input);
world.insert_resource(xr_views);
world.insert_resource(xr_frame_state);
Ok((
render_device,
render_queue,
render_adapter_info,
render_adapter,
RenderInstance(Arc::new(WgpuWrapper::new(wgpu_instance))),
))
}
pub fn xr_entry() -> eyre::Result<xr::Entry> {
#[cfg(windows)]
let entry = Ok(xr::Entry::linked());
#[cfg(not(windows))]
let entry = unsafe { xr::Entry::load().map_err(|e| eyre::eyre!(e)) };
entry
}

View File

@@ -1,606 +0,0 @@
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::math::uvec2;
use bevy::prelude::*;
use bevy::render::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper,
};
use bevy::window::RawHandleWrapper;
use eyre::{Context, ContextCompat};
use openxr as xr;
use wgpu::Instance;
use wgpu_hal::{api::Vulkan as V, Api};
use xr::EnvironmentBlendMode;
use crate::graphics::extensions::XrExtensions;
use crate::input::XrInput;
use crate::resources::{
OXrSessionSetupInfo, Swapchain, SwapchainInner, VulkanOXrSessionSetupInfo,
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrSession, XrSessionRunning, XrSwapchain, XrViews,
};
use crate::VIEW_TYPE;
use super::{XrAppInfo, XrPreferdBlendMode};
pub fn initialize_xr_instance(
window: Option<RawHandleWrapper>,
xr_entry: xr::Entry,
reqeusted_extensions: XrExtensions,
available_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo,
) -> eyre::Result<(
XrInstance,
OXrSessionSetupInfo,
XrEnvironmentBlendMode,
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
)> {
#[cfg(target_os = "android")]
xr_entry.initialize_android_loader()?;
assert!(available_extensions.raw().khr_vulkan_enable2);
// info!("available OpenXR extensions: {:#?}", available_extensions);
let mut enabled_extensions: xr::ExtensionSet =
(available_extensions & reqeusted_extensions).into();
enabled_extensions.khr_vulkan_enable2 = true;
#[cfg(target_os = "android")]
{
enabled_extensions.khr_android_create_instance = true;
}
let available_layers = xr_entry.enumerate_layers()?;
// info!("available OpenXR layers: {:#?}", available_layers);
let xr_instance = xr_entry.create_instance(
&xr::ApplicationInfo {
application_name: &app_info.name,
engine_name: "Bevy",
..Default::default()
},
&enabled_extensions,
&[],
)?;
info!("created OpenXR instance");
let instance_props = xr_instance.properties()?;
let xr_system_id = xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
info!("created OpenXR system");
let system_props = xr_instance.system_properties(xr_system_id).unwrap();
info!(
"loaded OpenXR runtime: {} {} {}",
instance_props.runtime_name,
instance_props.runtime_version,
if system_props.system_name.is_empty() {
"<unnamed>"
} else {
&system_props.system_name
}
);
let blend_modes = xr_instance.enumerate_environment_blend_modes(xr_system_id, VIEW_TYPE)?;
let blend_mode: EnvironmentBlendMode = match prefered_blend_mode {
XrPreferdBlendMode::Opaque if blend_modes.contains(&EnvironmentBlendMode::OPAQUE) => {
bevy::log::info!("Using Opaque");
EnvironmentBlendMode::OPAQUE
}
XrPreferdBlendMode::Additive if blend_modes.contains(&EnvironmentBlendMode::ADDITIVE) => {
bevy::log::info!("Using Additive");
EnvironmentBlendMode::ADDITIVE
}
XrPreferdBlendMode::AlphaBlend
if blend_modes.contains(&EnvironmentBlendMode::ALPHA_BLEND) =>
{
bevy::log::info!("Using AlphaBlend");
EnvironmentBlendMode::ALPHA_BLEND
}
_ => {
bevy::log::info!("Using Opaque");
EnvironmentBlendMode::OPAQUE
}
};
#[cfg(not(target_os = "android"))]
let vk_target_version = vk::make_api_version(0, 1, 2, 0);
#[cfg(not(target_os = "android"))]
let vk_target_version_xr = xr::Version::new(1, 2, 0);
#[cfg(target_os = "android")]
let vk_target_version = vk::make_api_version(0, 1, 1, 0);
#[cfg(target_os = "android")]
let vk_target_version_xr = xr::Version::new(1, 1, 0);
let reqs = xr_instance.graphics_requirements::<xr::Vulkan>(xr_system_id)?;
if vk_target_version_xr < reqs.min_api_version_supported
|| vk_target_version_xr.major() > reqs.max_api_version_supported.major()
{
panic!(
"OpenXR runtime requires Vulkan version >= {}, < {}.0.0",
reqs.min_api_version_supported,
reqs.max_api_version_supported.major() + 1
);
}
let vk_entry = unsafe { ash::Entry::load() }?;
let flags = wgpu::InstanceFlags::from_build_config();
let extensions = <V as Api>::Instance::desired_extensions(&vk_entry, vk_target_version, flags)?;
let device_extensions = vec![
ash::extensions::khr::Swapchain::name(),
ash::extensions::khr::DrawIndirectCount::name(),
#[cfg(target_os = "android")]
ash::extensions::khr::TimelineSemaphore::name(),
];
info!(
"creating Vulkan instance with these extensions: {:#?}",
extensions
);
let vk_instance = unsafe {
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
let app_name = CString::new(app_info.name)?;
let vk_app_info = vk::ApplicationInfo::builder()
.application_name(&app_name)
.application_version(1)
.engine_name(&app_name)
.engine_version(1)
.api_version(vk_target_version);
let vk_instance = xr_instance
.create_vulkan_instance(
xr_system_id,
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
&vk::InstanceCreateInfo::builder()
.application_info(&vk_app_info)
.enabled_extension_names(&extensions_cchar) as *const _
as *const _,
)
.context("OpenXR error creating Vulkan instance")
.unwrap()
.map_err(vk::Result::from_raw)
.context("Vulkan error creating Vulkan instance")
.unwrap();
ash::Instance::load(
vk_entry.static_fn(),
vk::Instance::from_raw(vk_instance as _),
)
};
info!("created Vulkan instance");
let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void;
let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe {
xr_instance.vulkan_graphics_device(xr_system_id, vk_instance.handle().as_raw() as _)? as _
});
let vk_physical_device_ptr = vk_physical_device.as_raw() as *const c_void;
let vk_device_properties =
unsafe { vk_instance.get_physical_device_properties(vk_physical_device) };
if vk_device_properties.api_version < vk_target_version {
unsafe { vk_instance.destroy_instance(None) }
panic!("Vulkan physical device doesn't support version 1.1");
}
let wgpu_vk_instance = unsafe {
<V as Api>::Instance::from_raw(
vk_entry.clone(),
vk_instance.clone(),
vk_target_version,
0,
None,
extensions,
flags,
false,
Some(Box::new(())),
)?
};
let wgpu_features = wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
| wgpu::Features::MULTIVIEW
| wgpu::Features::MULTI_DRAW_INDIRECT_COUNT
| wgpu::Features::MULTI_DRAW_INDIRECT;
let wgpu_exposed_adapter = wgpu_vk_instance
.expose_adapter(vk_physical_device)
.context("failed to expose adapter")?;
let enabled_extensions = wgpu_exposed_adapter
.adapter
.required_device_extensions(wgpu_features);
let (wgpu_open_device, vk_device_ptr, queue_family_index) = {
let extensions_cchar: Vec<_> = device_extensions.iter().map(|s| s.as_ptr()).collect();
let mut enabled_phd_features = wgpu_exposed_adapter
.adapter
.physical_device_features(&enabled_extensions, wgpu_features);
let family_index = 0;
let family_info = vk::DeviceQueueCreateInfo::builder()
.queue_family_index(family_index)
.queue_priorities(&[1.0])
.build();
let family_infos = [family_info];
let info = enabled_phd_features
.add_to_device_create_builder(
vk::DeviceCreateInfo::builder()
.queue_create_infos(&family_infos)
.push_next(&mut vk::PhysicalDeviceMultiviewFeatures {
multiview: vk::TRUE,
..Default::default()
}),
)
.enabled_extension_names(&extensions_cchar)
.build();
let vk_device = unsafe {
let vk_device = xr_instance
.create_vulkan_device(
xr_system_id,
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
vk_physical_device.as_raw() as _,
&info as *const _ as *const _,
)
.context("OpenXR error creating Vulkan device")?
.map_err(vk::Result::from_raw)
.context("Vulkan error creating Vulkan device")?;
ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _))
};
let vk_device_ptr = vk_device.handle().as_raw() as *const c_void;
let wgpu_open_device = unsafe {
wgpu_exposed_adapter.adapter.device_from_raw(
vk_device,
true,
&enabled_extensions,
wgpu_features,
family_info.queue_family_index,
0,
)
}?;
(
wgpu_open_device,
vk_device_ptr,
family_info.queue_family_index,
)
};
let wgpu_instance =
unsafe { wgpu::Instance::from_hal::<wgpu_hal::api::Vulkan>(wgpu_vk_instance) };
let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(wgpu_exposed_adapter) };
let (wgpu_device, wgpu_queue) = unsafe {
wgpu_adapter.create_device_from_hal(
wgpu_open_device,
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu_features,
required_limits: wgpu::Limits {
max_bind_groups: 8,
max_storage_buffer_binding_size: wgpu_adapter
.limits()
.max_storage_buffer_binding_size,
max_push_constant_size: 4,
..Default::default()
},
},
None,
)
}?;
Ok((
xr_instance.into(),
OXrSessionSetupInfo::Vulkan(VulkanOXrSessionSetupInfo {
device_ptr: vk_device_ptr,
physical_device_ptr: vk_physical_device_ptr,
vk_instance_ptr,
queue_family_index,
xr_system_id,
}),
blend_mode.into(),
wgpu_device.into(),
RenderQueue(Arc::new(WgpuWrapper::new(wgpu_queue))),
RenderAdapterInfo(WgpuWrapper::new(wgpu_adapter.get_info())),
RenderAdapter(Arc::new(WgpuWrapper::new(wgpu_adapter))),
wgpu_instance.into(),
))
}
pub fn start_xr_session(
window: Option<RawHandleWrapper>,
ptrs: &OXrSessionSetupInfo,
xr_instance: &XrInstance,
render_device: &RenderDevice,
render_adapter: &RenderAdapter,
wgpu_instance: &Instance,
) -> eyre::Result<(
XrSession,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)> {
let wgpu_device = render_device.wgpu_device();
let wgpu_adapter = &render_adapter.0;
#[allow(unreachable_patterns)]
let setup_info = match ptrs {
OXrSessionSetupInfo::Vulkan(v) => v,
_ => eyre::bail!("Wrong Graphics Api"),
};
let (session, frame_wait, frame_stream) = unsafe {
xr_instance.create_session::<xr::Vulkan>(
xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?,
&xr::vulkan::SessionCreateInfo {
instance: setup_info.vk_instance_ptr,
physical_device: setup_info.physical_device_ptr,
device: setup_info.device_ptr,
queue_family_index: setup_info.queue_family_index,
queue_index: 0,
},
)
}?;
let views =
xr_instance.enumerate_view_configuration_views(setup_info.xr_system_id, VIEW_TYPE)?;
let surface = window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle();
wgpu_instance
.create_surface(handle)
.expect("Failed to create wgpu surface")
});
let swapchain_format = surface
.as_ref()
.map(|surface| surface.get_capabilities(wgpu_adapter).formats[0])
.unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
// TODO: Log swapchain format
let resolution = uvec2(
views[0].recommended_image_rect_width,
views[0].recommended_image_rect_height,
);
let handle = 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 _,
// 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`.
sample_count: 1,
width: resolution.x,
height: resolution.y,
face_count: 1,
array_size: 2,
mip_count: 1,
})
.unwrap();
let images = handle.enumerate_images().unwrap();
let buffers = images
.into_iter()
.map(|color_image| {
info!("image map swapchain");
let color_image = vk::Image::from_raw(color_image);
let wgpu_hal_texture = unsafe {
<V as Api>::Device::texture_from_raw(
color_image,
&wgpu_hal::TextureDescriptor {
label: Some("bevy_openxr swapchain"), // unused internally
size: wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: swapchain_format,
usage: wgpu_hal::TextureUses::COLOR_TARGET
| wgpu_hal::TextureUses::COPY_DST,
memory_flags: wgpu_hal::MemoryFlags::empty(),
view_formats: vec![],
},
None,
)
};
let texture = unsafe {
wgpu_device.create_texture_from_hal::<V>(
wgpu_hal_texture,
&wgpu::TextureDescriptor {
label: Some("bevy_openxr swapchain"),
size: wgpu::Extent3d {
width: resolution.x,
height: resolution.y,
depth_or_array_layers: 2,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: swapchain_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST,
view_formats: &[],
},
)
};
texture
})
.collect();
Ok((
XrSession::Vulkan(session.clone()),
resolution.into(),
swapchain_format.into(),
// TODO: this shouldn't be in here
AtomicBool::new(false).into(),
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())?,
Vec::default().into(),
// TODO: Feels wrong to return a FrameState here, we probably should just wait for the next frame
xr::FrameState {
predicted_display_time: xr::Time::from_nanos(1),
predicted_display_period: xr::Duration::from_nanos(1),
should_render: true,
}
.into(),
))
}
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> vk::Format {
// Copied with minor modification from:
// https://github.com/gfx-rs/wgpu/blob/v0.19/wgpu-hal/src/vulkan/conv.rs#L5C1-L153
// license: MIT OR Apache-2.0
use ash::vk::Format as F;
use wgpu::TextureFormat as Tf;
use wgpu::{AstcBlock, AstcChannel};
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::Rgb10a2Uint => F::A2B10G10R10_UINT_PACK32,
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 => {
panic!("Cannot convert format that is dependent on device properties")
}
Tf::Depth16Unorm => F::D16_UNORM,
Tf::NV12 => F::G8_B8R8_2PLANE_420_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,
},
},
}
}

View File

@@ -1,86 +0,0 @@
use std::sync::Arc;
use bevy::{prelude::*, render::extract_resource::ExtractResource};
use openxr as xr;
use xr::{FrameState, FrameWaiter, ViewConfigurationType};
#[derive(Clone, Resource, ExtractResource)]
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>,
// frame_state: &FrameState,
) -> xr::Result<Self> {
// let right_hand_subaction_path = instance.string_to_path("/user/hand/right").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 = match instance.exts().ext_local_floor {
None => session
.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?,
Some(_) => session.create_reference_space(
xr::ReferenceSpaceType::LOCAL_FLOOR_EXT,
xr::Posef::IDENTITY,
)?,
};
let head =
session.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)?;
// let y = stage
// .locate(&head, frame_state.predicted_display_time).unwrap()
// .pose
// .position
// .y;
// let local = session.create_reference_space(
// xr::ReferenceSpaceType::LOCAL,
// xr::Posef {
// position: xr::Vector3f { x: 0.0, y, z: 0.0 },
// orientation: xr::Quaternionf::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),
})
}
}

View File

@@ -1,541 +0,0 @@
pub mod graphics;
pub mod input;
pub mod passthrough;
pub mod prelude;
pub mod resource_macros;
pub mod resources;
pub mod xr_init;
pub mod xr_input;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use crate::xr_init::{StartXrSession, XrInitPlugin};
use crate::xr_input::oculus_touch::ActionSets;
use crate::xr_input::trackers::verify_quat;
use bevy::app::{AppExit, PluginGroupBuilder};
use bevy::core::TaskPoolThreadAssignmentPolicy;
use bevy::ecs::system::SystemState;
use bevy::prelude::*;
use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews};
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
use bevy::render::renderer::{render_system, RenderInstance, WgpuWrapper};
use bevy::render::settings::RenderCreation;
use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet};
use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper};
use graphics::extensions::XrExtensions;
use graphics::{XrAppInfo, XrPreferdBlendMode};
use input::XrInput;
use openxr as xr;
use passthrough::{PassthroughPlugin, XrPassthroughLayer, XrPassthroughState};
use resources::*;
use xr_init::{
xr_after_wait_only, xr_only, xr_render_only, CleanupRenderWorld, CleanupXrData,
ExitAppOnSessionExit, SetupXrData, StartSessionOnStartup, XrCleanup, XrEarlyInitPlugin,
XrHasWaited, XrPostCleanup, XrShouldRender, XrStatus,
};
use xr_input::actions::XrActionsPlugin;
use xr_input::hands::emulated::HandEmulationPlugin;
use xr_input::hands::hand_tracking::HandTrackingPlugin;
use xr_input::hands::HandPlugin;
use xr_input::xr_camera::XrCameraPlugin;
use xr_input::XrInputPlugin;
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 {
pub backend_preference: Vec<Backend>,
pub reqeusted_extensions: XrExtensions,
pub prefered_blend_mode: XrPreferdBlendMode,
pub app_info: XrAppInfo,
pub synchronous_pipeline_compilation: bool,
}
impl Plugin for OpenXrPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(XrSessionRunning::new(AtomicBool::new(false)));
app.insert_resource(ExitAppOnSessionExit::default());
#[cfg(not(target_arch = "wasm32"))]
match graphics::initialize_xr_instance(
&self.backend_preference,
SystemState::<Query<&RawHandleWrapper, With<PrimaryWindow>>>::new(app.world_mut())
.get(&app.world())
.get_single()
.ok()
.cloned(),
self.reqeusted_extensions.clone(),
self.prefered_blend_mode,
self.app_info.clone(),
) {
Ok((
xr_instance,
oxr_session_setup_info,
blend_mode,
device,
queue,
adapter_info,
render_adapter,
instance,
)) => {
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
debug!("Configured wgpu adapter Features: {:#?}", device.features());
warn!("Starting with OpenXR Instance");
app.insert_resource(xr_instance.clone());
app.insert_resource(blend_mode);
app.insert_resource(ActionSets(vec![]));
app.insert_resource(xr_instance);
app.insert_resource(blend_mode);
app.insert_non_send_resource(oxr_session_setup_info);
let render_instance = RenderInstance(Arc::new(WgpuWrapper::new(instance)));
app.insert_resource(render_instance.clone());
app.add_plugins(RenderPlugin {
render_creation: RenderCreation::Manual(
device,
queue,
adapter_info,
render_adapter,
render_instance,
),
// Expose this? if yes we also have to set this in the non xr case
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
});
app.insert_resource(XrStatus::Disabled);
// app.world.send_event(StartXrSession);
}
Err(err) => {
warn!("OpenXR Instance Failed to initialize: {}", err);
app.add_plugins(RenderPlugin {
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
..Default::default()
});
app.insert_resource(XrStatus::NoInstance);
}
}
#[cfg(target_arch = "wasm32")]
{
app.add_plugins(RenderPlugin::default());
app.insert_resource(XrStatus::Disabled);
}
app.add_systems(XrPostCleanup, clean_resources);
app.add_systems(XrPostCleanup, || info!("Main World Post Cleanup!"));
app.add_systems(
PreUpdate,
xr_poll_events.run_if(|status: Res<XrStatus>| *status != XrStatus::NoInstance),
);
app.add_systems(
PreUpdate,
(
xr_reset_per_frame_resources,
xr_wait_frame.run_if(xr_only()),
locate_views.run_if(xr_only()),
apply_deferred,
)
.chain()
.after(xr_poll_events),
);
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(
Render,
xr_pre_frame
.run_if(xr_only())
.run_if(xr_after_wait_only())
.run_if(xr_render_only())
.before(render_system)
.after(RenderSet::ExtractCommands),
);
render_app.add_systems(
Render,
xr_end_frame
.run_if(xr_only())
.run_if(xr_after_wait_only())
.run_if(xr_render_only())
.in_set(RenderSet::Cleanup),
);
render_app.add_systems(
Render,
xr_skip_frame
.run_if(xr_only())
.run_if(xr_after_wait_only())
.run_if(not(xr_render_only()))
.in_set(RenderSet::Cleanup),
);
render_app.add_systems(
Render,
clean_resources_render
.run_if(resource_exists::<CleanupRenderWorld>)
.after(RenderSet::ExtractCommands),
);
}
}
#[cfg(all(not(feature = "vulkan"), not(all(feature = "d3d12", windows))))]
compile_error!("At least one platform-compatible backend feature must be enabled.");
#[derive(Debug)]
pub enum Backend {
#[cfg(feature = "vulkan")]
Vulkan,
#[cfg(all(feature = "d3d12", windows))]
D3D12,
}
fn clean_resources_render(cmds: &mut World) {
// let session = cmds.remove_resource::<XrSession>().unwrap();
cmds.remove_resource::<XrSession>();
cmds.remove_resource::<XrResolution>();
cmds.remove_resource::<XrFormat>();
// cmds.remove_resource::<XrSessionRunning>();
cmds.remove_resource::<XrFrameWaiter>();
cmds.remove_resource::<XrSwapchain>();
cmds.remove_resource::<XrInput>();
cmds.remove_resource::<XrViews>();
cmds.remove_resource::<XrFrameState>();
cmds.remove_resource::<CleanupRenderWorld>();
// unsafe {
// (session.instance().fp().destroy_session)(session.as_raw());
// }
warn!("Cleanup Resources Render");
}
fn clean_resources(cmds: &mut World) {
cmds.remove_resource::<XrSession>();
cmds.remove_resource::<XrResolution>();
cmds.remove_resource::<XrFormat>();
// cmds.remove_resource::<XrSessionRunning>();
cmds.remove_resource::<XrFrameWaiter>();
cmds.remove_resource::<XrSwapchain>();
cmds.remove_resource::<XrInput>();
cmds.remove_resource::<XrViews>();
cmds.remove_resource::<XrFrameState>();
// cmds.remove_resource::<CleanupRenderWorld>();
// unsafe {
// (session.instance().fp().destroy_session)(session.as_raw());
// }
warn!("Cleanup Resources");
}
fn xr_skip_frame(
xr_swapchain: Res<XrSwapchain>,
xr_frame_state: Res<XrFrameState>,
environment_blend_mode: Res<XrEnvironmentBlendMode>,
) {
let swapchain: &Swapchain = &xr_swapchain;
match swapchain {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swap) => &swap
.stream
.lock()
.unwrap()
.end(
xr_frame_state.predicted_display_time,
**environment_blend_mode,
&[],
)
.unwrap(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swap) => &swap
.stream
.lock()
.unwrap()
.end(
xr_frame_state.predicted_display_time,
**environment_blend_mode,
&[],
)
.unwrap(),
};
}
pub struct DefaultXrPlugins {
pub backend_preference: Vec<Backend>,
pub reqeusted_extensions: XrExtensions,
pub prefered_blend_mode: XrPreferdBlendMode,
pub app_info: XrAppInfo,
pub synchronous_pipeline_compilation: bool,
}
impl Default for DefaultXrPlugins {
fn default() -> Self {
Self {
backend_preference: vec![
#[cfg(feature = "vulkan")]
Backend::Vulkan,
#[cfg(all(feature = "d3d12", windows))]
Backend::D3D12,
],
reqeusted_extensions: default(),
prefered_blend_mode: default(),
app_info: default(),
synchronous_pipeline_compilation: false,
}
}
}
impl PluginGroup for DefaultXrPlugins {
fn build(self) -> PluginGroupBuilder {
DefaultPlugins
.build()
.set(TaskPoolPlugin {
task_pool_options: TaskPoolOptions {
compute: TaskPoolThreadAssignmentPolicy {
min_threads: 2,
max_threads: std::usize::MAX, // unlimited max threads
percent: 1.0, // this value is irrelevant in this case
},
// keep the defaults for everything else
..default()
},
})
.disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(OpenXrPlugin {
backend_preference: self.backend_preference,
prefered_blend_mode: self.prefered_blend_mode,
reqeusted_extensions: self.reqeusted_extensions,
app_info: self.app_info.clone(),
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
})
.add_after::<OpenXrPlugin, _>(XrInitPlugin)
.add(XrInputPlugin)
.add(XrActionsPlugin)
.add(XrCameraPlugin)
.add_before::<OpenXrPlugin, _>(XrEarlyInitPlugin)
.add(HandPlugin)
.add(HandTrackingPlugin)
.add(HandEmulationPlugin)
.add(PassthroughPlugin)
.add(XrResourcePlugin)
.add(StartSessionOnStartup)
.set(WindowPlugin {
#[cfg(not(target_os = "android"))]
primary_window: Some(Window {
transparent: true,
present_mode: PresentMode::AutoNoVsync,
title: self.app_info.name.clone(),
..default()
}),
#[cfg(target_os = "android")]
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..default()
}),
#[cfg(target_os = "android")]
exit_condition: bevy::window::ExitCondition::DontExit,
#[cfg(target_os = "android")]
close_when_requested: true,
..default()
})
}
}
fn xr_reset_per_frame_resources(
mut should: ResMut<XrShouldRender>,
mut waited: ResMut<XrHasWaited>,
) {
**should = false;
**waited = false;
}
fn xr_poll_events(
instance: Option<Res<XrInstance>>,
session: Option<Res<XrSession>>,
session_running: Res<XrSessionRunning>,
exit_type: Res<ExitAppOnSessionExit>,
mut app_exit: EventWriter<AppExit>,
mut start_session: EventWriter<StartXrSession>,
mut setup_xr: EventWriter<SetupXrData>,
mut cleanup_xr: EventWriter<CleanupXrData>,
) {
if let (Some(instance), Some(session)) = (instance, session) {
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 => {
info!("Calling Session begin :3");
session.begin(VIEW_TYPE).unwrap();
setup_xr.send_default();
session_running.store(true, std::sync::atomic::Ordering::Relaxed);
}
xr::SessionState::STOPPING => {
session.end().unwrap();
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
cleanup_xr.send_default();
}
xr::SessionState::EXITING => {
if *exit_type == ExitAppOnSessionExit::Always
|| *exit_type == ExitAppOnSessionExit::OnlyOnExit
{
app_exit.send_default();
}
}
xr::SessionState::LOSS_PENDING => {
if *exit_type == ExitAppOnSessionExit::Always {
app_exit.send_default();
}
if *exit_type == ExitAppOnSessionExit::OnlyOnExit {
start_session.send_default();
}
}
_ => {}
}
}
InstanceLossPending(_) => {
app_exit.send_default();
}
EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count());
}
_ => {}
}
}
}
}
pub fn xr_wait_frame(
world: &mut World,
// mut frame_state: ResMut<XrFrameState>,
// mut frame_waiter: ResMut<XrFrameWaiter>,
// mut should_render: ResMut<XrShouldRender>,
// mut waited: ResMut<XrHasWaited>,
) {
let mut frame_waiter = world.get_resource_mut::<XrFrameWaiter>().unwrap();
{
let _span = info_span!("xr_wait_frame").entered();
*world.get_resource_mut::<XrFrameState>().unwrap() = match frame_waiter.wait() {
Ok(a) => a.into(),
Err(e) => {
warn!("error: {}", e);
return;
}
};
let should_render = world.get_resource::<XrFrameState>().unwrap().should_render;
**world.get_resource_mut::<XrShouldRender>().unwrap() = should_render;
**world.get_resource_mut::<XrHasWaited>().unwrap() = true;
}
world
.get_resource::<XrSwapchain>()
.unwrap()
.begin()
.unwrap();
}
pub fn xr_pre_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);
}
}
#[allow(clippy::too_many_arguments)]
pub fn xr_end_frame(
xr_frame_state: Res<XrFrameState>,
views: Res<XrViews>,
input: Res<XrInput>,
swapchain: Res<XrSwapchain>,
resolution: Res<XrResolution>,
environment_blend_mode: Res<XrEnvironmentBlendMode>,
passthrough_layer: Option<Res<XrPassthroughLayer>>,
passthrough_state: Option<Res<XrPassthroughState>>,
) {
#[cfg(target_os = "android")]
{
let ctx = ndk_context::android_context();
let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }.unwrap();
let env = vm.attach_current_thread_as_daemon();
}
{
let _span = info_span!("xr_release_image").entered();
swapchain.release_image().unwrap();
}
{
let _span = info_span!("xr_end_frame").entered();
let pass_layer = match passthrough_state.as_deref() {
Some(XrPassthroughState::Running) => passthrough_layer.as_deref(),
_ => None,
};
let result = swapchain.end(
xr_frame_state.predicted_display_time,
&views,
&input.stage,
**resolution,
**environment_blend_mode,
pass_layer,
);
match result {
Ok(_) => {}
Err(e) => warn!("error: {}", e),
}
}
}
pub fn locate_views(
mut views: ResMut<XrViews>,
input: Res<XrInput>,
session: Res<XrSession>,
xr_frame_state: Res<XrFrameState>,
) {
let _span = info_span!("xr_locate_views").entered();
**views = match session.locate_views(
VIEW_TYPE,
xr_frame_state.predicted_display_time,
&input.stage,
) {
Ok(this) => this
.1
.into_iter()
.map(|mut view| {
use crate::prelude::*;
let quat = view.pose.orientation.to_quat();
let fixed_quat = verify_quat(quat);
let oxr_quat = xr::Quaternionf {
x: fixed_quat.x,
y: fixed_quat.y,
z: fixed_quat.z,
w: fixed_quat.w,
};
view.pose.orientation = oxr_quat;
view
})
.collect(),
Err(err) => {
warn!("error: {}", err);
return;
}
}
}

View File

@@ -1,229 +0,0 @@
use bevy::color::palettes;
use bevy::render::extract_resource::ExtractResource;
use bevy::{prelude::*, render::extract_resource::ExtractResourcePlugin};
use std::{marker::PhantomData, mem, ptr};
use crate::resources::XrSession;
use crate::{
resources::XrInstance,
xr_arc_resource_wrapper,
xr_init::{XrCleanup, XrSetup},
};
use openxr as xr;
use xr::{
sys::{Space, SystemPassthroughProperties2FB},
CompositionLayerBase, CompositionLayerFlags, FormFactor, Graphics,
PassthroughCapabilityFlagsFB,
};
#[derive(
Clone, Copy, Default, Debug, Resource, PartialEq, PartialOrd, Ord, Eq, Reflect, ExtractResource,
)]
pub enum XrPassthroughState {
#[default]
Unsupported,
Running,
Paused,
}
xr_arc_resource_wrapper!(XrPassthrough, xr::Passthrough);
xr_arc_resource_wrapper!(XrPassthroughLayer, xr::PassthroughLayer);
pub struct PassthroughPlugin;
impl Plugin for PassthroughPlugin {
fn build(&self, app: &mut App) {
app.add_event::<ResumePassthrough>();
app.add_event::<PausePassthrough>();
app.add_plugins(ExtractResourcePlugin::<XrPassthroughLayer>::default());
app.add_plugins(ExtractResourcePlugin::<XrPassthroughState>::default());
app.register_type::<XrPassthroughState>();
app.add_systems(Startup, check_passthrough_support);
app.add_systems(
XrSetup,
setup_passthrough
.run_if(|state: Res<XrPassthroughState>| *state != XrPassthroughState::Unsupported),
);
app.add_systems(XrCleanup, cleanup_passthrough);
app.add_systems(
Update,
resume_passthrough.run_if(
resource_exists_and_equals(XrPassthroughState::Paused)
.and_then(on_event::<ResumePassthrough>()),
),
);
app.add_systems(
Update,
pause_passthrough.run_if(
resource_exists_and_equals(XrPassthroughState::Running)
.and_then(on_event::<PausePassthrough>()),
),
);
}
}
fn check_passthrough_support(mut cmds: Commands, instance: Option<Res<XrInstance>>) {
match instance {
None => cmds.insert_resource(XrPassthroughState::Unsupported),
Some(instance) => {
let supported = instance.exts().fb_passthrough.is_some()
&& supports_passthrough(
&instance,
instance.system(FormFactor::HEAD_MOUNTED_DISPLAY).unwrap(),
)
.is_ok_and(|v| v);
match supported {
false => cmds.insert_resource(XrPassthroughState::Unsupported),
true => cmds.insert_resource(XrPassthroughState::Paused),
}
}
}
}
fn resume_passthrough(
layer: Res<XrPassthroughLayer>,
mut state: ResMut<XrPassthroughState>,
mut clear_color: ResMut<ClearColor>,
) {
if let Err(e) = layer.resume() {
warn!("Unable to resume Passthrough: {}", e);
return;
}
**clear_color = Srgba::NONE.into();
*state = XrPassthroughState::Running;
}
fn pause_passthrough(
layer: Res<XrPassthroughLayer>,
mut state: ResMut<XrPassthroughState>,
mut clear_color: ResMut<ClearColor>,
) {
if let Err(e) = layer.pause() {
warn!("Unable to resume Passthrough: {}", e);
return;
}
clear_color.set_alpha(1.0);
*state = XrPassthroughState::Paused;
}
fn cleanup_passthrough(mut cmds: Commands) {
cmds.remove_resource::<XrPassthrough>();
cmds.remove_resource::<XrPassthroughLayer>();
}
fn setup_passthrough(mut cmds: Commands, session: Res<XrSession>) {
match create_passthrough(&session) {
Ok((passthrough, layer)) => {
cmds.insert_resource(XrPassthrough::from(passthrough));
cmds.insert_resource(XrPassthroughLayer::from(layer));
}
Err(e) => {
warn!("Unable to create passthrough: {}", e);
}
}
}
#[derive(Clone, Copy, Debug, Default, Reflect, Event)]
pub struct ResumePassthrough;
#[derive(Clone, Copy, Debug, Default, Reflect, Event)]
pub struct PausePassthrough;
fn cvt(x: xr::sys::Result) -> xr::Result<xr::sys::Result> {
if x.into_raw() >= 0 {
Ok(x)
} else {
Err(x)
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub(crate) struct CompositionLayerPassthrough<'a, G: xr::Graphics> {
inner: xr::sys::CompositionLayerPassthroughFB,
_marker: PhantomData<&'a G>,
}
impl<'a, G: Graphics> std::ops::Deref for CompositionLayerPassthrough<'a, G> {
type Target = CompositionLayerBase<'a, G>;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { mem::transmute(&self.inner) }
}
}
impl<'a, G: xr::Graphics> CompositionLayerPassthrough<'a, G> {
pub(crate) fn from_xr_passthrough_layer(layer: &XrPassthroughLayer) -> Self {
Self {
inner: xr::sys::CompositionLayerPassthroughFB {
ty: xr::sys::CompositionLayerPassthroughFB::TYPE,
next: ptr::null(),
flags: CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA,
space: Space::NULL,
layer_handle: *layer.inner(),
},
_marker: PhantomData,
}
}
}
#[inline]
pub fn supports_passthrough(instance: &XrInstance, system: xr::SystemId) -> xr::Result<bool> {
unsafe {
let mut hand = xr::sys::SystemPassthroughProperties2FB {
ty: SystemPassthroughProperties2FB::TYPE,
next: ptr::null(),
capabilities: PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY,
};
let mut p = xr::sys::SystemProperties::out(&mut hand as *mut _ as _);
cvt((instance.fp().get_system_properties)(
instance.as_raw(),
system,
p.as_mut_ptr(),
))?;
bevy::log::info!(
"From supports_passthrough: Passthrough capabilities: {:?}",
hand.capabilities
);
Ok(
(hand.capabilities & PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY)
== PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY,
)
}
}
#[inline]
pub fn create_passthrough(
xr_session: &XrSession,
) -> xr::Result<(xr::Passthrough, xr::PassthroughLayer)> {
let flags = xr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION;
let purpose = xr::PassthroughLayerPurposeFB::RECONSTRUCTION;
let passthrough = match xr_session {
#[cfg(feature = "vulkan")]
XrSession::Vulkan(session) => {
session.create_passthrough(xr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION)
}
#[cfg(all(feature = "d3d12", windows))]
XrSession::D3D12(session) => {
session.create_passthrough(xr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION)
}
}?;
let passthrough_layer = match xr_session {
#[cfg(feature = "vulkan")]
XrSession::Vulkan(session) => {
session.create_passthrough_layer(&passthrough, flags, purpose)
}
#[cfg(all(feature = "d3d12", windows))]
XrSession::D3D12(session) => session.create_passthrough_layer(&passthrough, flags, purpose),
}?;
Ok((passthrough, passthrough_layer))
}
/// Enable Passthrough on xr startup
/// just sends the [`ResumePassthrough`] event in [`XrSetup`]
pub struct EnablePassthroughStartup;
impl Plugin for EnablePassthroughStartup {
fn build(&self, app: &mut App) {
app.add_systems(XrSetup, |mut e: EventWriter<ResumePassthrough>| {
e.send_default();
});
}
}

View File

@@ -1,15 +0,0 @@
use bevy::ecs::schedule::{IntoSystemConfigs, SystemConfigs};
pub use crate::xr_init::schedules::XrSetup;
use crate::xr_init::xr_only;
pub use crate::xr_input::{QuatConv, Vec2Conv, Vec3Conv};
pub trait XrSystems<Marker> {
fn xr_only(self) -> SystemConfigs;
}
impl<T: IntoSystemConfigs<M>, M> XrSystems<M> for T {
fn xr_only(self) -> SystemConfigs {
self.into_configs().run_if(xr_only())
}
}

View File

@@ -1,133 +0,0 @@
#[macro_export]
macro_rules! xr_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => {
#[derive(
Clone,
bevy::prelude::Resource,
bevy::prelude::Deref,
bevy::prelude::DerefMut,
bevy::render::extract_resource::ExtractResource,
)]
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_resource_wrapper_copy {
($wrapper_type:ident, $xr_type:ty) => {
#[derive(
Clone,
Copy,
bevy::prelude::Resource,
bevy::prelude::Deref,
bevy::prelude::DerefMut,
bevy::render::extract_resource::ExtractResource,
)]
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,
bevy::prelude::Deref,
bevy::prelude::DerefMut,
bevy::render::extract_resource::ExtractResource,
)]
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)
}
}
};
}
#[macro_export]
macro_rules! xr_no_clone_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => {
#[derive(bevy::prelude::Resource, bevy::prelude::Deref, bevy::prelude::DerefMut)]
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)
}
}
};
}
pub use xr_arc_resource_wrapper;
pub use xr_no_clone_resource_wrapper;
pub use xr_resource_wrapper;

View File

@@ -1,319 +0,0 @@
use std::ffi::c_void;
use std::sync::atomic::AtomicBool;
use std::sync::Mutex;
use crate::input::XrInput;
use crate::passthrough::{CompositionLayerPassthrough, XrPassthroughLayer};
use crate::resource_macros::*;
use crate::xr::sys::CompositionLayerPassthroughFB;
use crate::xr::{CompositionLayerBase, CompositionLayerFlags};
use crate::{resource_macros::*, xr_resource_wrapper_copy};
use bevy::prelude::*;
use bevy::prelude::*;
use bevy::render::extract_component::ExtractComponent;
use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin};
use core::ptr;
use openxr as xr;
#[cfg(all(feature = "d3d12", windows))]
use winapi::um::d3d12::{ID3D12CommandQueue, ID3D12Device};
xr_resource_wrapper!(XrInstance, xr::Instance);
xr_resource_wrapper_copy!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
xr_resource_wrapper_copy!(XrResolution, UVec2);
xr_resource_wrapper_copy!(XrFormat, wgpu::TextureFormat);
xr_resource_wrapper_copy!(XrFrameState, xr::FrameState);
xr_resource_wrapper!(XrViews, Vec<xr::View>);
xr_arc_resource_wrapper!(XrSessionRunning, AtomicBool);
xr_arc_resource_wrapper!(XrSwapchain, Swapchain);
xr_no_clone_resource_wrapper!(XrFrameWaiter, xr::FrameWaiter);
#[derive(Clone, Resource, ExtractResource)]
pub enum XrSession {
#[cfg(feature = "vulkan")]
Vulkan(xr::Session<xr::Vulkan>),
#[cfg(all(feature = "d3d12", windows))]
D3D12(xr::Session<xr::D3D12>),
}
impl std::ops::Deref for XrSession {
type Target = xr::Session<xr::AnyGraphics>;
fn deref(&self) -> &Self::Target {
// SAFTEY: should be fine i think -Schmarni
unsafe {
match self {
#[cfg(feature = "vulkan")]
XrSession::Vulkan(sess) => std::mem::transmute(sess),
#[cfg(all(feature = "d3d12", windows))]
XrSession::D3D12(sess) => std::mem::transmute(sess),
}
}
}
}
#[cfg(feature = "vulkan")]
pub struct VulkanOXrSessionSetupInfo {
pub(crate) device_ptr: *const c_void,
pub(crate) physical_device_ptr: *const c_void,
pub(crate) vk_instance_ptr: *const c_void,
pub(crate) queue_family_index: u32,
pub(crate) xr_system_id: xr::SystemId,
}
#[cfg(all(feature = "d3d12", windows))]
pub struct D3D12OXrSessionSetupInfo {
pub(crate) raw_device: *mut ID3D12Device,
pub(crate) raw_queue: *mut ID3D12CommandQueue,
pub(crate) xr_system_id: xr::SystemId,
}
pub enum OXrSessionSetupInfo {
#[cfg(feature = "vulkan")]
Vulkan(VulkanOXrSessionSetupInfo),
#[cfg(all(feature = "d3d12", windows))]
D3D12(D3D12OXrSessionSetupInfo),
}
pub struct XrResourcePlugin;
impl Plugin for XrResourcePlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ExtractResourcePlugin::<XrResolution>::default());
app.add_plugins(ExtractResourcePlugin::<XrFormat>::default());
app.add_plugins(ExtractResourcePlugin::<XrSwapchain>::default());
app.add_plugins(ExtractResourcePlugin::<XrFrameState>::default());
app.add_plugins(ExtractResourcePlugin::<XrViews>::default());
app.add_plugins(ExtractResourcePlugin::<XrInput>::default());
app.add_plugins(ExtractResourcePlugin::<XrEnvironmentBlendMode>::default());
// app.add_plugins(ExtractResourcePlugin::<XrSessionRunning>::default());
app.add_plugins(ExtractResourcePlugin::<XrSession>::default());
}
}
pub enum Swapchain {
#[cfg(feature = "vulkan")]
Vulkan(SwapchainInner<xr::Vulkan>),
#[cfg(all(feature = "d3d12", windows))]
D3D12(SwapchainInner<xr::D3D12>),
}
impl Swapchain {
pub(crate) fn begin(&self) -> xr::Result<()> {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.begin(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swapchain) => swapchain.begin(),
}
}
pub(crate) fn get_render_views(&self) -> (wgpu::TextureView, wgpu::TextureView) {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.get_render_views(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swapchain) => swapchain.get_render_views(),
}
}
pub(crate) fn acquire_image(&self) -> xr::Result<()> {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.acquire_image(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swapchain) => swapchain.acquire_image(),
}
}
pub(crate) fn wait_image(&self) -> xr::Result<()> {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.wait_image(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swapchain) => swapchain.wait_image(),
}
}
pub(crate) fn release_image(&self) -> xr::Result<()> {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.release_image(),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(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,
passthrough_layer: Option<&XrPassthroughLayer>,
) -> xr::Result<()> {
match self {
#[cfg(feature = "vulkan")]
Swapchain::Vulkan(swapchain) => swapchain.end(
predicted_display_time,
views,
stage,
resolution,
environment_blend_mode,
passthrough_layer,
),
#[cfg(all(feature = "d3d12", windows))]
Swapchain::D3D12(swapchain) => swapchain.end(
predicted_display_time,
views,
stage,
resolution,
environment_blend_mode,
passthrough_layer,
),
}
}
}
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> Drop for SwapchainInner<G> {
fn drop(&mut self) {
for _ in 0..self.buffers.len() {
let v = self.buffers.remove(0);
Box::leak(Box::new(v));
}
}
}
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,
passthrough_layer: Option<&XrPassthroughLayer>,
) -> 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.is_empty() {
warn!("views are len of 0");
return Ok(());
}
match passthrough_layer {
Some(pass) => {
//bevy::log::info!("Rendering with pass through");
self.stream.lock().unwrap().end(
predicted_display_time,
environment_blend_mode,
&[
&CompositionLayerPassthrough::from_xr_passthrough_layer(pass),
&xr::CompositionLayerProjection::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.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),
),
]),
],
)
}
None => {
// bevy::log::info!("Rendering without pass through");
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),
),
])],
)
}
}
}
}

View File

@@ -1,263 +0,0 @@
pub mod schedules;
pub use schedules::*;
use bevy::{
prelude::*,
render::{
camera::{ManualTextureView, ManualTextureViews},
extract_resource::{ExtractResource, ExtractResourcePlugin},
renderer::{RenderAdapter, RenderDevice, RenderInstance},
Render, RenderApp, RenderSet,
},
window::{PrimaryWindow, RawHandleWrapper},
};
use crate::{
clean_resources, graphics,
resources::{OXrSessionSetupInfo, XrFormat, XrInstance, XrResolution, XrSession, XrSwapchain},
LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE,
};
#[derive(Resource, Event, Clone, Copy, PartialEq, Eq, Reflect, Debug, ExtractResource)]
pub enum XrStatus {
NoInstance,
Enabled,
Enabling,
Disabled,
Disabling,
}
#[derive(
Resource, Clone, Copy, PartialEq, Eq, Reflect, Debug, ExtractResource, Default, Deref, DerefMut,
)]
pub struct XrShouldRender(bool);
#[derive(
Resource, Clone, Copy, PartialEq, Eq, Reflect, Debug, ExtractResource, Default, Deref, DerefMut,
)]
pub struct XrHasWaited(bool);
pub struct XrEarlyInitPlugin;
pub struct XrInitPlugin;
pub fn xr_only() -> impl FnMut(Res<XrStatus>) -> bool {
resource_equals(XrStatus::Enabled)
}
pub fn xr_render_only() -> impl FnMut(Res<XrShouldRender>) -> bool {
resource_equals(XrShouldRender(true))
}
pub fn xr_after_wait_only() -> impl FnMut(Res<XrHasWaited>) -> bool {
resource_equals(XrHasWaited(true))
}
#[derive(Resource, Clone, Copy, ExtractResource)]
pub struct CleanupRenderWorld;
impl Plugin for XrEarlyInitPlugin {
fn build(&self, app: &mut App) {
add_schedules(app);
app.add_event::<SetupXrData>()
.add_event::<CleanupXrData>()
.add_event::<StartXrSession>()
.add_event::<EndXrSession>();
}
}
impl Plugin for XrInitPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(ExtractResourcePlugin::<XrStatus>::default());
app.add_plugins(ExtractResourcePlugin::<XrShouldRender>::default());
app.add_plugins(ExtractResourcePlugin::<XrHasWaited>::default());
app.add_plugins(ExtractResourcePlugin::<CleanupRenderWorld>::default());
app.init_resource::<XrShouldRender>();
app.init_resource::<XrHasWaited>();
app.add_systems(PreUpdate, setup_xr.run_if(on_event::<SetupXrData>()))
.add_systems(PreUpdate, cleanup_xr.run_if(on_event::<CleanupXrData>()));
app.add_systems(
PostUpdate,
start_xr_session.run_if(on_event::<StartXrSession>()),
);
app.add_systems(
PostUpdate,
stop_xr_session.run_if(on_event::<EndXrSession>()),
);
app.add_systems(XrSetup, setup_manual_texture_views);
app.add_systems(XrCleanup, set_cleanup_res);
app.add_systems(PreUpdate, remove_cleanup_res.before(cleanup_xr));
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(
Render,
remove_cleanup_res
.in_set(RenderSet::Cleanup)
.after(clean_resources),
);
}
}
#[derive(Resource, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExitAppOnSessionExit {
#[default]
/// Restart XrSession when session is lost
OnlyOnExit,
/// Always exit the app
Always,
/// Keep app open when XrSession wants to exit or is lost
Never,
}
pub struct StartSessionOnStartup;
impl Plugin for StartSessionOnStartup {
fn build(&self, app: &mut App) {
app.add_systems(Startup, |mut event: EventWriter<StartXrSession>| {
event.send_default();
});
}
}
fn set_cleanup_res(mut commands: Commands) {
info!("Set Cleanup Res");
commands.insert_resource(CleanupRenderWorld);
}
fn remove_cleanup_res(mut commands: Commands) {
commands.remove_resource::<CleanupRenderWorld>();
}
fn setup_manual_texture_views(
mut manual_texture_views: ResMut<ManualTextureViews>,
swapchain: Res<XrSwapchain>,
xr_resolution: Res<XrResolution>,
xr_format: Res<XrFormat>,
) {
info!("Creating Texture views");
let (left, right) = swapchain.get_render_views();
let left = ManualTextureView {
texture_view: left.into(),
size: **xr_resolution,
format: **xr_format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: **xr_resolution,
format: **xr_format,
};
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
}
pub fn setup_xr(world: &mut World) {
info!("Pre XrPreSetup");
world.run_schedule(XrPreSetup);
info!("Post XrPreSetup");
world.run_schedule(XrSetup);
world.run_schedule(XrPrePostSetup);
world.run_schedule(XrPostSetup);
*world.resource_mut::<XrStatus>() = XrStatus::Enabled;
}
fn cleanup_xr(world: &mut World) {
world.run_schedule(XrPreCleanup);
world.run_schedule(XrCleanup);
world.run_schedule(XrPostCleanup);
*world.resource_mut::<XrStatus>() = XrStatus::Disabled;
}
#[derive(Event, Clone, Copy, Default)]
pub struct StartXrSession;
#[derive(Event, Clone, Copy, Default)]
pub struct EndXrSession;
#[derive(Event, Clone, Copy, Default)]
pub(crate) struct SetupXrData;
#[derive(Event, Clone, Copy, Default)]
pub(crate) struct CleanupXrData;
#[allow(clippy::too_many_arguments)]
fn start_xr_session(
mut commands: Commands,
mut status: ResMut<XrStatus>,
instance: Option<Res<XrInstance>>,
primary_window: Query<&RawHandleWrapper, With<PrimaryWindow>>,
setup_info: Option<NonSend<OXrSessionSetupInfo>>,
render_device: Option<Res<RenderDevice>>,
render_adapter: Option<Res<RenderAdapter>>,
render_instance: Option<Res<RenderInstance>>,
) {
info!("start Session");
match *status {
XrStatus::Disabled => {}
XrStatus::NoInstance => {
warn!("Trying to start OpenXR Session without instance, ignoring");
return;
}
XrStatus::Enabled | XrStatus::Enabling => {
warn!("Trying to start OpenXR Session while one already exists, ignoring");
return;
}
XrStatus::Disabling => {
warn!("Trying to start OpenXR Session while one is stopping, ignoring");
return;
}
}
let (
Some(instance),
Some(setup_info),
Some(render_device),
Some(render_adapter),
Some(render_instance),
) = (
instance,
setup_info,
render_device,
render_adapter,
render_instance,
)
else {
error!("Missing resources after passing status check");
return;
};
let (
xr_session,
xr_resolution,
xr_format,
xr_session_running,
xr_frame_waiter,
xr_swapchain,
xr_input,
xr_views,
xr_frame_state,
) = match graphics::start_xr_session(
primary_window.get_single().cloned().ok(),
&setup_info,
&instance,
&render_device,
&render_adapter,
&render_instance,
) {
Ok(data) => data,
Err(err) => {
error!("Unable to start OpenXR Session: {}", err);
return;
}
};
commands.insert_resource(xr_session);
commands.insert_resource(xr_resolution);
commands.insert_resource(xr_format);
commands.insert_resource(xr_session_running);
commands.insert_resource(xr_frame_waiter);
commands.insert_resource(xr_swapchain);
commands.insert_resource(xr_input);
commands.insert_resource(xr_views);
commands.insert_resource(xr_frame_state);
*status = XrStatus::Enabling;
}
fn stop_xr_session(session: ResMut<XrSession>, mut status: ResMut<XrStatus>) {
match session.request_exit() {
Ok(_) => {}
Err(err) => {
error!("Error while trying to request session exit: {}", err)
}
}
*status = XrStatus::Disabling;
}

View File

@@ -1,54 +0,0 @@
use bevy::{
app::App,
ecs::schedule::{ExecutorKind, Schedule, ScheduleLabel},
};
#[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(super) 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);
}
}

View File

@@ -1,409 +0,0 @@
use std::error::Error;
use bevy::{prelude::*, utils::HashMap};
use openxr as xr;
use xr::{Action, Binding, Haptic, Posef, Vector2f};
use crate::{
resources::{XrInstance, XrSession},
xr_init::{xr_only, XrCleanup, XrPrePostSetup, XrPreSetup},
};
use super::oculus_touch::ActionSets;
pub use xr::sys::NULL_PATH;
pub struct XrActionsPlugin;
impl Plugin for XrActionsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, sync_actions.run_if(xr_only()));
app.add_systems(
XrPreSetup,
(insert_setup_action_sets, apply_deferred).chain(),
);
app.add_systems(XrPrePostSetup, setup_oxr_actions);
app.add_systems(XrCleanup, clean_actions);
}
}
fn insert_setup_action_sets(mut cmds: Commands) {
info!("WHAT?!");
cmds.insert_resource(SetupActionSets {
sets: HashMap::new(),
});
}
fn clean_actions(mut cmds: Commands) {
cmds.remove_resource::<ActionSets>();
cmds.remove_resource::<XrActionSets>();
}
#[inline(always)]
fn create_action<T: xr::ActionTy>(
action: &SetupAction,
action_name: &'static str,
oxr_action_set: &xr::ActionSet,
hands: &[xr::Path],
) -> xr::Action<T> {
match action.handednes {
ActionHandednes::Single => oxr_action_set
.create_action(action_name, &action.pretty_name, &[])
.unwrap_or_else(|_| panic!("Unable to create action: {}", action_name)),
ActionHandednes::Double => oxr_action_set
.create_action(action_name, &action.pretty_name, hands)
.unwrap_or_else(|_| panic!("Unable to create action: {}", action_name)),
}
}
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 action_sets = XrActionSets { sets: default() };
// let mut action_bindings: HashMap<&'static str, Vec<xr::Path>> = HashMap::new();
let mut action_bindings: HashMap<
(&'static str, &'static str),
HashMap<&'static str, Vec<xr::Path>>,
> = HashMap::new();
for (set_name, set) in actions.sets.into_iter() {
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() {
use self::create_action as ca;
let typed_action = match action.action_type {
ActionType::Vec2 => {
TypedAction::Vec2(ca(&action, action_name, &oxr_action_set, &hands))
}
ActionType::F32 => {
TypedAction::F32(ca(&action, action_name, &oxr_action_set, &hands))
}
ActionType::Bool => {
TypedAction::Bool(ca(&action, action_name, &oxr_action_set, &hands))
}
ActionType::PoseF => {
TypedAction::PoseF(ca(&action, action_name, &oxr_action_set, &hands))
}
ActionType::Haptic => {
TypedAction::Haptic(ca(&action, action_name, &oxr_action_set, &hands))
}
};
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 as &'static str, action_name as &'static str))
.unwrap()
.iter()
.map(move |(dev, bindings)| (action, dev, bindings))
})
.map(|(action, dev, bindings)| {
(
dev,
bindings
.iter()
.map(move |binding| match &action {
TypedAction::Vec2(a) => Binding::new(a, *binding),
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() {
instance
.suggest_interaction_profile_bindings(instance.string_to_path(dev).unwrap(), &bindings)
.expect("Unable to suggest interaction bindings!");
}
session
.attach_action_sets(
&action_sets
.sets
.values()
.map(|set| &set.oxr_action_set)
.collect::<Vec<_>>(),
)
.expect("Unable to attach action sets!");
world.insert_resource(action_sets);
}
pub enum ActionHandednes {
Single,
Double,
}
#[derive(Clone, Copy)]
pub enum ActionType {
F32,
Bool,
PoseF,
Haptic,
Vec2,
}
pub enum TypedAction {
F32(Action<f32>),
Bool(Action<bool>),
PoseF(Action<Posef>),
Haptic(Action<Haptic>),
Vec2(Action<Vector2f>),
}
pub struct SetupAction {
pretty_name: String,
action_type: ActionType,
handednes: ActionHandednes,
bindings: HashMap<&'static str, Vec<&'static str>>,
}
pub struct SetupActionSet {
pretty_name: String,
priority: u32,
actions: HashMap<&'static str, SetupAction>,
}
impl SetupActionSet {
pub fn new_action(
&mut self,
name: &'static str,
pretty_name: String,
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(eyre::eyre!("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: String,
priority: u32,
) -> &mut SetupActionSet {
self.sets.insert(
name,
SetupActionSet {
pretty_name,
priority,
actions: HashMap::new(),
},
);
self.sets.get_mut(name).unwrap()
}
}
pub struct ActionSet {
// add functionality to enable/disable action sets
enabled: bool,
actions: HashMap<&'static str, TypedAction>,
oxr_action_set: xr::ActionSet,
}
#[derive(Resource)]
pub struct XrActionSets {
sets: HashMap<&'static str, ActionSet>,
}
use std::fmt::Display as FmtDisplay;
impl FmtDisplay for ActionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let err = match self {
ActionError::NoActionSet => "Action Set Not Found!",
ActionError::NoAction => "Action Not Found!",
ActionError::WrongActionType => "Wrong Action Type!",
};
write!(f, "{}", err)
}
}
impl Error for ActionError {}
#[derive(Debug)]
pub enum ActionError {
NoActionSet,
NoAction,
WrongActionType,
}
impl XrActionSets {
pub fn get_action_vec2(
&self,
action_set: &'static str,
action_name: &'static str,
) -> Result<&Action<Vector2f>, ActionError> {
let action = self
.sets
.get(action_set)
.ok_or(ActionError::NoActionSet)?
.actions
.get(action_name)
.ok_or(ActionError::NoAction)?;
match action {
TypedAction::Vec2(a) => Ok(a),
_ => Err(ActionError::WrongActionType),
}
}
pub fn get_action_f32(
&self,
action_set: &'static str,
action_name: &'static str,
) -> Result<&Action<f32>, ActionError> {
let action = self
.sets
.get(action_set)
.ok_or(ActionError::NoActionSet)?
.actions
.get(action_name)
.ok_or(ActionError::NoAction)?;
match action {
TypedAction::F32(a) => Ok(a),
_ => Err(ActionError::WrongActionType),
}
}
pub fn get_action_bool(
&self,
action_set: &'static str,
action_name: &'static str,
) -> Result<&Action<bool>, ActionError> {
let action = self
.sets
.get(action_set)
.ok_or(ActionError::NoActionSet)?
.actions
.get(action_name)
.ok_or(ActionError::NoAction)?;
match action {
TypedAction::Bool(a) => Ok(a),
_ => Err(ActionError::WrongActionType),
}
}
pub fn get_action_posef(
&self,
action_set: &'static str,
action_name: &'static str,
) -> Result<&Action<Posef>, ActionError> {
let action = self
.sets
.get(action_set)
.ok_or(ActionError::NoActionSet)?
.actions
.get(action_name)
.ok_or(ActionError::NoAction)?;
match action {
TypedAction::PoseF(a) => Ok(a),
_ => Err(ActionError::WrongActionType),
}
}
pub fn get_action_haptic(
&self,
action_set: &'static str,
action_name: &'static str,
) -> Result<&Action<Haptic>, ActionError> {
let action = self
.sets
.get(action_set)
.ok_or(ActionError::NoActionSet)?
.actions
.get(action_name)
.ok_or(ActionError::NoAction)?;
match action {
TypedAction::Haptic(a) => Ok(a),
_ => Err(ActionError::WrongActionType),
}
}
}
pub fn sync_actions(action_sets: Res<XrActionSets>, session: Res<XrSession>) {
let active_sets = action_sets
.sets
.values()
.filter_map(|set| {
if set.enabled {
Some(xr::ActiveActionSet::new(&set.oxr_action_set))
} else {
None
}
})
.collect::<Vec<_>>();
if let Err(err) = session.sync_actions(&active_sets) {
warn!("OpenXR action sync error: {}", err);
}
}

View File

@@ -1,10 +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,
}

View File

@@ -1,396 +0,0 @@
use bevy::color::palettes;
use bevy::ecs::schedule::IntoSystemConfigs;
use bevy::log::{debug, info};
use bevy::math::Dir3;
use bevy::prelude::{
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,
// palettes::css::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,
// palettes::css::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,
},
Dir3::Y,
0.2,
palettes::css::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 = palettes::css::YELLOW_GREEN;
let off_color = palettes::css::BLUE;
let touch_color = palettes::css::GREEN;
let pressed_color = palettes::css::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,
Dir3::new_unchecked(face_quat_normal),
0.04,
palettes::css::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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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 = palettes::css::YELLOW_GREEN;
let off_color = palettes::css::BLUE;
let touch_color = palettes::css::GREEN;
let pressed_color = palettes::css::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,
Dir3::new_unchecked(face_quat_normal),
0.04,
palettes::css::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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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,
Dir3::new_unchecked(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);
}
}
}

View File

@@ -1,519 +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,
},
];
bones_to_transforms(test_hand_bones, hand)
}
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;
}

View File

@@ -1,258 +0,0 @@
use bevy::{
color::{palettes, Srgba},
core::Name,
prelude::{
default, Color, Commands, Component, Deref, DerefMut, Entity, Gizmos, Plugin, PostUpdate,
Query, Resource, SpatialBundle, Startup, Transform,
},
transform::components::GlobalTransform,
};
use crate::xr_input::{trackers::OpenXRTracker, Hand};
use super::{BoneTrackingStatus, HandBone};
/// 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((
Name::new(format!("{:?} {:?}", hand, bone)),
SpatialBundle::default(),
*bone,
OpenXRTracker,
*hand,
BoneTrackingStatus::Tracked,
HandBoneRadius(0.1),
))
.id();
let hand_res = match hand {
Hand::Left => &mut hand_resource.left,
Hand::Right => &mut hand_resource.right,
};
match bone {
HandBone::Palm => hand_res.palm = boneid,
HandBone::Wrist => hand_res.wrist = boneid,
HandBone::ThumbMetacarpal => hand_res.thumb.metacarpal = boneid,
HandBone::ThumbProximal => hand_res.thumb.proximal = boneid,
HandBone::ThumbDistal => hand_res.thumb.distal = boneid,
HandBone::ThumbTip => hand_res.thumb.tip = boneid,
HandBone::IndexMetacarpal => hand_res.index.metacarpal = boneid,
HandBone::IndexProximal => hand_res.index.proximal = boneid,
HandBone::IndexIntermediate => hand_res.index.intermediate = boneid,
HandBone::IndexDistal => hand_res.index.distal = boneid,
HandBone::IndexTip => hand_res.index.tip = boneid,
HandBone::MiddleMetacarpal => hand_res.middle.metacarpal = boneid,
HandBone::MiddleProximal => hand_res.middle.proximal = boneid,
HandBone::MiddleIntermediate => hand_res.middle.intermediate = boneid,
HandBone::MiddleDistal => hand_res.middle.distal = boneid,
HandBone::MiddleTip => hand_res.middle.tip = boneid,
HandBone::RingMetacarpal => hand_res.ring.metacarpal = boneid,
HandBone::RingProximal => hand_res.ring.proximal = boneid,
HandBone::RingIntermediate => hand_res.ring.intermediate = boneid,
HandBone::RingDistal => hand_res.ring.distal = boneid,
HandBone::RingTip => hand_res.ring.tip = boneid,
HandBone::LittleMetacarpal => hand_res.little.metacarpal = boneid,
HandBone::LittleProximal => hand_res.little.proximal = boneid,
HandBone::LittleIntermediate => hand_res.little.intermediate = boneid,
HandBone::LittleDistal => hand_res.little.distal = boneid,
HandBone::LittleTip => hand_res.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<(&GlobalTransform, &HandBone, &HandBoneRadius)>,
) {
for (transform, hand_bone, hand_bone_radius) in query.iter() {
let (_, color) = get_bone_gizmo_style(hand_bone);
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos.sphere(translation, rotation, hand_bone_radius.0, color);
}
}
pub(crate) fn get_bone_gizmo_style(hand_bone: &HandBone) -> (f32, Srgba) {
match hand_bone {
HandBone::Palm => (0.01, palettes::css::WHITE),
HandBone::Wrist => (0.01, palettes::css::GRAY),
HandBone::ThumbMetacarpal => (0.01, palettes::css::RED),
HandBone::ThumbProximal => (0.008, palettes::css::RED),
HandBone::ThumbDistal => (0.006, palettes::css::RED),
HandBone::ThumbTip => (0.004, palettes::css::RED),
HandBone::IndexMetacarpal => (0.01, palettes::css::ORANGE),
HandBone::IndexProximal => (0.008, palettes::css::ORANGE),
HandBone::IndexIntermediate => (0.006, palettes::css::ORANGE),
HandBone::IndexDistal => (0.004, palettes::css::ORANGE),
HandBone::IndexTip => (0.002, palettes::css::ORANGE),
HandBone::MiddleMetacarpal => (0.01, palettes::css::YELLOW),
HandBone::MiddleProximal => (0.008, palettes::css::YELLOW),
HandBone::MiddleIntermediate => (0.006, palettes::css::YELLOW),
HandBone::MiddleDistal => (0.004, palettes::css::YELLOW),
HandBone::MiddleTip => (0.002, palettes::css::YELLOW),
HandBone::RingMetacarpal => (0.01, palettes::css::GREEN),
HandBone::RingProximal => (0.008, palettes::css::GREEN),
HandBone::RingIntermediate => (0.006, palettes::css::GREEN),
HandBone::RingDistal => (0.004, palettes::css::GREEN),
HandBone::RingTip => (0.002, palettes::css::GREEN),
HandBone::LittleMetacarpal => (0.01, palettes::css::BLUE),
HandBone::LittleProximal => (0.008, palettes::css::BLUE),
HandBone::LittleIntermediate => (0.006, palettes::css::BLUE),
HandBone::LittleDistal => (0.004, palettes::css::BLUE),
HandBone::LittleTip => (0.002, palettes::css::BLUE),
}
}

View File

@@ -1,546 +0,0 @@
use std::f32::consts::PI;
use bevy::prelude::*;
use openxr::{ActionTy, HandJoint};
use super::common::{get_bone_gizmo_style, HandBoneRadius};
use crate::{
resources::{XrInstance, XrSession},
xr_init::{xr_only, XrSetup},
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 action_set =
action_sets.add_action_set(HAND_ACTION_SET, "Hand Pose Approximaiton".into(), 0);
action_set.new_action(
"thumb_touch",
"Thumb Touched".into(),
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumb_x",
"Thumb X".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumb_y",
"Thumb Y".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"index_touch",
"Index Finger Touched".into(),
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"index_value",
"Index Finger Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"middle_value",
"Middle Finger Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"ring_value",
"Ring Finger Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"little_value",
"Little Finger Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
suggest_oculus_touch_profile(action_set);
}
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"),
],
);
}
#[allow(clippy::type_complexity)]
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>>,
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"),
},
}
}
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);
#[allow(clippy::needless_return)]
return curl_angle;
}

View File

@@ -1,212 +0,0 @@
use bevy::prelude::*;
use openxr::{HandTracker, Result, SpaceLocationFlags};
use super::common::HandBoneRadius;
use crate::{
input::XrInput,
resources::{XrFrameState, XrSession},
xr_init::xr_only,
xr_input::{hands::HandBone, Hand, QuatConv, Vec3Conv},
};
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,
}
#[derive(Debug)]
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.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>,
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 left_hand_data = hand_ref.get_poses(Hand::Left);
let right_hand_data = hand_ref.get_poses(Hand::Right);
// if left_hand_data.is_none() || right_hand_data.is_none() {
// error!("something is very wrong for hand_tracking!! doesn't have data for both hands!");
// }
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),
(hand, left_data, right_data) => {
*status = BoneTrackingStatus::Emulated;
return;
}
};
if *status == BoneTrackingStatus::Emulated {
*status = BoneTrackingStatus::Tracked;
}
radius.0 = bone_data.radius;
transform.translation = bone_data.position;
transform.rotation = bone_data.orientation;
});
}

View File

@@ -1,174 +0,0 @@
use bevy::prelude::*;
use openxr::FormFactor;
use crate::{
resources::{XrInstance, XrSession},
xr_init::{XrCleanup, XrPreSetup, XrSetup},
};
use self::{
common::{spawn_hand_entities, HandBoneRadius, HandsResource},
hand_tracking::{DisableHandTracking, HandTrackingData},
};
use super::{trackers::OpenXRTracker, Hand};
pub mod common;
pub mod emulated;
pub mod hand_tracking;
pub struct HandPlugin;
impl Plugin for HandPlugin {
fn build(&self, app: &mut App) {
app.add_systems(XrPreSetup, check_for_handtracking);
app.add_systems(XrSetup, spawn_hand_entities);
app.add_systems(XrCleanup, despawn_hand_entities);
}
}
#[allow(clippy::type_complexity)]
fn despawn_hand_entities(
mut commands: Commands,
hand_entities: Query<
Entity,
(
With<OpenXRTracker>,
With<HandBone>,
With<BoneTrackingStatus>,
),
>,
) {
for e in &hand_entities {
commands.entity(e).despawn_recursive();
}
commands.remove_resource::<HandsResource>()
}
fn check_for_handtracking(
mut commands: Commands,
instance: Res<XrInstance>,
session: Res<XrSession>,
) {
let hands = instance.exts().ext_hand_tracking.is_some()
&& instance
.supports_hand_tracking(instance.system(FormFactor::HEAD_MOUNTED_DISPLAY).unwrap())
.is_ok_and(|v| v);
if hands {
info!("handtracking!");
commands.insert_resource(HandTrackingData::new(&session).unwrap());
} else {
commands.insert_resource(DisableHandTracking::Both);
}
}
#[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 {
!matches!(self, HandBone::Wrist | HandBone::Palm)
}
pub fn is_metacarpal(&self) -> bool {
matches!(
self,
HandBone::ThumbMetacarpal
| HandBone::IndexMetacarpal
| HandBone::MiddleMetacarpal
| HandBone::RingMetacarpal
| HandBone::LittleMetacarpal
)
}
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,
}
}
}

View File

@@ -1,385 +0,0 @@
use std::f32::consts::PI;
use bevy::color::palettes;
use bevy::log::{info, warn};
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 => palettes::css::BLUE,
XRInteractorState::Selecting => palettes::css::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 Ok(root) = tracking_root_query.get_single() else {
warn!("no or more than one tracking root");
return;
};
for (global_transform, interactable_state) in interactable_query.iter() {
let transform = global_transform.compute_transform();
let color = match interactable_state {
XRInteractableState::Idle => palettes::css::RED,
XRInteractableState::Hover => palettes::css::YELLOW,
XRInteractableState::Select => palettes::css::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 => palettes::css::BLUE,
XRInteractorState::Selecting => palettes::css::PURPLE,
};
gizmos.cuboid(local, color);
}
None => (),
}
match ray {
Some(_) => match aim {
Some(aim) => {
let color = match interactor_state {
XRInteractorState::Idle => palettes::css::BLUE,
XRInteractorState::Selecting => palettes::css::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();
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;
}

View File

@@ -1,126 +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::resources::{XrInstance, XrSession};
use crate::xr_init::{xr_only, XrCleanup, XrPostSetup, XrPreSetup, XrSetup};
use crate::xr_input::oculus_touch::setup_oculus_controller;
use crate::xr_input::xr_camera::{xr_camera_head_sync, Eye, XRProjection, XrCameraBundle};
use crate::{locate_views, xr_wait_frame};
use bevy::app::{App, PostUpdate, Startup};
use bevy::ecs::entity::Entity;
use bevy::ecs::query::With;
use bevy::ecs::system::Query;
use bevy::hierarchy::DespawnRecursiveExt;
use bevy::log::{info, warn};
use bevy::math::Vec2;
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::extract_component::ExtractComponentPlugin;
use bevy::render::view::{update_frusta, VisibilitySystems};
use bevy::transform::TransformSystem;
use bevy::utils::HashMap;
use openxr::Binding;
use self::actions::{setup_oxr_actions, XrActionsPlugin};
use self::oculus_touch::{
init_subaction_path, post_action_setup_oculus_controller, ActionSets, OculusController,
};
use self::trackers::{
adopt_open_xr_trackers, update_open_xr_controllers, OpenXRLeftEye, OpenXRRightEye,
OpenXRTrackingRoot,
};
use self::xr_camera::{/* GlobalTransformExtract, TransformExtract, */ XrCamera};
#[derive(Copy, Clone)]
pub struct XrInputPlugin;
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Component)]
pub enum Hand {
Left,
Right,
}
impl Plugin for XrInputPlugin {
fn build(&self, app: &mut App) {
app.add_systems(XrPostSetup, post_action_setup_oculus_controller);
app.add_systems(XrSetup, setup_oculus_controller);
app.add_systems(XrCleanup, cleanup_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.run_if(xr_only()));
//update controller trackers
app.add_systems(Update, update_open_xr_controllers.run_if(xr_only()));
app.add_systems(XrPreSetup, init_subaction_path);
app.add_systems(XrSetup, setup_xr_root);
app.add_systems(XrCleanup, cleanup_xr_root);
}
}
fn cleanup_oculus_controller(mut commands: Commands) {
commands.remove_resource::<OculusController>();
}
fn cleanup_xr_root(
mut commands: Commands,
tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
) {
for e in &tracking_root_query {
commands.entity(e).despawn_recursive();
}
}
fn setup_xr_root(
mut commands: Commands,
tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
) {
if tracking_root_query.get_single().is_err() {
info!("Creating XrTrackingRoot!");
commands.spawn((SpatialBundle::default(), OpenXRTrackingRoot));
}
}
// pub 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());
// if let Err(err) = session.sync_actions(&active_action_sets) {
// warn!("{}", err);
// }
// }
pub trait Vec2Conv {
fn to_vec2(&self) -> Vec2;
}
impl Vec2Conv for openxr::Vector2f {
fn to_vec2(&self) -> Vec2 {
Vec2::new(self.x, self.y)
}
}
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)
}
}

View File

@@ -1,546 +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::{default, 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();
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,
}
pub static RIGHT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
pub static LEFT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
pub fn init_subaction_path(instance: Res<XrInstance>) {
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) {
let d = 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,
),
};
match d {
Ok(d) => d,
Err(_) => (SpaceLocation::default(), SpaceVelocity::default()),
}
}
pub fn aim_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
let d = 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,
),
};
match d {
Ok(d) => d,
Err(_) => (SpaceLocation::default(), SpaceVelocity::default()),
}
}
pub fn squeeze(&self, hand: Hand) -> f32 {
match &self
.action_sets
.get_action_f32("oculus_input", "squeeze")
.unwrap()
.state(&self.session, subaction_path(hand))
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn trigger(&self, hand: Hand) -> f32 {
match self
.action_sets
.get_action_f32("oculus_input", "trigger")
.unwrap()
.state(&self.session, subaction_path(hand))
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn trigger_touched(&self, hand: Hand) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "trigger_touched")
.unwrap()
.state(&self.session, subaction_path(hand))
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn x_button(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "x_button")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn x_button_touched(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "x_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn y_button(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "y_button")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn y_button_touched(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "y_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn menu_button(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "menu_button")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn a_button(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "a_button")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn a_button_touched(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "a_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn b_button(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "b_button")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn b_button_touched(&self) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "b_button_touch")
.unwrap()
.state(&self.session, Path::NULL)
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn thumbstick_touch(&self, hand: Hand) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "thumbstick_touch")
.unwrap()
.state(&self.session, subaction_path(hand))
{
Ok(v) => v,
Err(_) => return default(),
}
.current_state
}
pub fn thumbstick(&self, hand: Hand) -> Thumbstick {
Thumbstick {
x: match self
.action_sets
.get_action_f32("oculus_input", "thumbstick_x")
.unwrap()
.state(&self.session, subaction_path(hand))
.map(|v| v.current_state)
{
Ok(v) => v,
Err(_) => default(),
},
y: match self
.action_sets
.get_action_f32("oculus_input", "thumbstick_y")
.unwrap()
.state(&self.session, subaction_path(hand))
.map(|v| v.current_state)
{
Ok(v) => v,
Err(_) => default(),
},
click: match self
.action_sets
.get_action_bool("oculus_input", "thumbstick_click")
.unwrap()
.state(&self.session, subaction_path(hand))
.map(|v| v.current_state)
{
Ok(v) => v,
Err(_) => default(),
},
}
}
pub fn thumbrest_touch(&self, hand: Hand) -> bool {
match self
.action_sets
.get_action_bool("oculus_input", "thumbrest_touch")
.unwrap()
.state(&self.session, subaction_path(hand))
{
Ok(v) => v,
Err(_) => return default(),
}
.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>) -> eyre::Result<Self> {
let action_set =
action_sets.add_action_set("oculus_input", "Oculus Touch Controller Input".into(), 0);
action_set.new_action(
"hand_pose",
"Hand Pose".into(),
ActionType::PoseF,
ActionHandednes::Double,
);
action_set.new_action(
"pointer_pose",
"Pointer Pose".into(),
ActionType::PoseF,
ActionHandednes::Double,
);
action_set.new_action(
"squeeze",
"Grip Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"trigger",
"Trigger Pull".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"trigger_touched",
"Trigger Touch".into(),
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"haptic_feedback",
"Haptic Feedback".into(),
ActionType::Haptic,
ActionHandednes::Double,
);
action_set.new_action(
"x_button",
"X Button".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"x_button_touch",
"X Button Touch".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"y_button",
"Y Button".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"y_button_touch",
"Y Button Touch".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"a_button",
"A Button".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"a_button_touch",
"A Button Touch".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"b_button",
"B Button".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"b_button_touch",
"B Button Touch".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"menu_button",
"Menu Button".into(),
ActionType::Bool,
ActionHandednes::Single,
);
action_set.new_action(
"thumbstick_x",
"Thumbstick X".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_y",
"Thumbstick y".into(),
ActionType::F32,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_touch",
"Thumbstick Touch".into(),
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumbstick_click",
"Thumbstick Click".into(),
ActionType::Bool,
ActionHandednes::Double,
);
action_set.new_action(
"thumbrest_touch",
"Thumbrest Touch".into(),
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)
}
}

View File

@@ -1,175 +0,0 @@
use std::f32::consts::PI;
use bevy::{
color::palettes,
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>,
session: Res<XrSession>,
views: ResMut<XrViews>,
mut gizmos: Gizmos,
config_option: Option<ResMut<PrototypeLocomotionConfig>>,
action_sets: Res<XrActionSets>,
) {
let mut config = match config_option {
Some(c) => c,
None => {
info!("no locomotion config");
return;
}
};
//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.right() + stick.y * *position.forward();
let reference_quat;
match config.locomotion_type {
LocomotionType::Head => {
let views = views.first();
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.up(), yaw);
let locomotion_vec = reference_quat.mul_vec3(input);
position.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.up(),
rot_input * config.smooth_rotation_speed * time.delta_seconds(),
);
//apply rotation
let views = views.first();
match views {
Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0;
let local = position.translation;
let global = position.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.up(), 0.1, palettes::css::GREEN);
position.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.up(), config.snap_angle * dir);
//apply rotation
let v = views;
let views = v.first();
match views {
Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0;
let local = position.translation;
let global = position.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.up(), 0.1, palettes::css::GREEN);
position.rotate_around(global, smoth_rot);
}
None => return,
}
config.rotation_timer.timer.reset();
}
}
}
}
Err(_) => info!("too many tracking roots"),
}
}

View File

@@ -1,143 +0,0 @@
use bevy::hierarchy::Parent;
use bevy::log::{debug, info};
use bevy::math::Quat;
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, (With<OpenXRTracker>, Without<Parent>)>,
mut commands: Commands,
tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
) {
let root = tracking_root_query.get_single();
match root {
Ok(root) => {
// info!("root is");
for tracker in query.iter() {
info!("we got a new tracker");
commands.entity(root).add_child(tracker);
}
}
Err(_) => info!("root isnt spawned yet?"),
}
}
pub fn verify_quat(mut quat: Quat) -> Quat {
if quat.length() == 0.0 || !quat.is_finite() {
quat = Quat::IDENTITY;
}
quat.normalize()
}
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>,
) {
//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: verify_quat(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 = verify_quat(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: verify_quat(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 = verify_quat(right_grip_space.0.pose.orientation.to_quat())
}
Err(_) => (),
}
}

View File

@@ -1,409 +0,0 @@
use crate::prelude::XrSystems;
use crate::xr_init::{xr_only, XrCleanup, XrSetup};
use crate::xr_input::{QuatConv, Vec3Conv};
use crate::{locate_views, xr_wait_frame, LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE};
use bevy::core_pipeline::core_3d::graph::Core3d;
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
use bevy::math::Vec3A;
use bevy::prelude::*;
use bevy::render::camera::{
CameraMainTextureUsages, CameraProjection, CameraProjectionPlugin, CameraRenderGraph,
RenderTarget,
};
use bevy::render::extract_component::{ExtractComponent, ExtractComponentPlugin};
use bevy::render::primitives::Frustum;
use bevy::render::render_resource::TextureUsages;
use bevy::render::view::{
update_frusta, ColorGrading, ExtractedView, VisibilitySystems, VisibleEntities,
};
use bevy::render::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem;
use openxr::Fovf;
use super::trackers::{OpenXRLeftEye, OpenXRRightEye, OpenXRTracker, OpenXRTrackingRoot};
pub struct XrCameraPlugin;
impl Plugin for XrCameraPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(CameraProjectionPlugin::<XRProjection>::default());
app.add_systems(
PreUpdate,
xr_camera_head_sync
.run_if(xr_only())
.after(xr_wait_frame)
.after(locate_views),
);
// a little late latching
app.add_systems(
PostUpdate,
xr_camera_head_sync
.before(TransformSystem::TransformPropagate)
.run_if(xr_only()),
);
app.add_systems(
PostUpdate,
update_frusta::<XRProjection>
.after(TransformSystem::TransformPropagate)
.before(VisibilitySystems::UpdateFrusta),
);
app.add_systems(
PostUpdate,
update_root_transform_components
.after(TransformSystem::TransformPropagate)
.xr_only(),
);
app.add_systems(XrSetup, setup_xr_cameras);
app.add_systems(XrCleanup, cleanup_xr_cameras);
app.add_plugins(ExtractComponentPlugin::<XrCamera>::default());
app.add_plugins(ExtractComponentPlugin::<XRProjection>::default());
app.add_plugins(ExtractComponentPlugin::<RootTransform>::default());
// app.add_plugins(ExtractComponentPlugin::<TransformExtract>::default());
// app.add_plugins(ExtractComponentPlugin::<GlobalTransformExtract>::default());
let render_app = app.sub_app_mut(RenderApp);
render_app.add_systems(
Render,
(locate_views, xr_camera_head_sync_render_world)
.chain()
.run_if(xr_only())
.in_set(RenderSet::PrepareAssets),
// .after(xr_wait_frame)
// .after(locate_views),
);
}
}
// might be unnesesary since it should be parented to the root
fn cleanup_xr_cameras(mut commands: Commands, entities: Query<Entity, With<XrCamera>>) {
for e in &entities {
commands.entity(e).despawn_recursive();
}
}
fn setup_xr_cameras(mut commands: Commands) {
commands.spawn((
XrCameraBundle::new(Eye::Right),
OpenXRRightEye,
OpenXRTracker,
));
commands.spawn((XrCameraBundle::new(Eye::Left), OpenXRLeftEye, OpenXRTracker));
}
#[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 main_texture_usages: CameraMainTextureUsages,
pub xr_camera_type: XrCamera,
pub root_transform: RootTransform,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Component, ExtractComponent)]
pub struct XrCamera(Eye);
#[derive(Component, ExtractComponent, Clone, Copy, Debug, Default, Deref, DerefMut)]
pub struct RootTransform(pub GlobalTransform);
fn update_root_transform_components(
mut component_query: Query<&mut RootTransform>,
root_query: Query<&GlobalTransform, With<OpenXRTrackingRoot>>,
) {
let root = match root_query.get_single() {
Ok(v) => v,
Err(err) => {
warn!("No or too many XrTracking Roots: {}", err);
return;
}
};
component_query
.par_iter_mut()
.for_each(|mut root_transform| **root_transform = *root);
}
// #[derive(Component)]
// pub(super) struct TransformExtract;
//
// impl ExtractComponent for TransformExtract {
// type Query = Read<Transform>;
//
// type Filter = ();
//
// type Out = Transform;
//
// fn extract_component(item: bevy::ecs::query::QueryItem<'_, Self::Query>) -> Option<Self::Out> {
// Some(*item)
// }
// }
//
// #[derive(Component)]
// pub(super) struct GlobalTransformExtract;
//
// impl ExtractComponent for GlobalTransformExtract {
// type Query = Read<GlobalTransform>;
//
// type Filter = ();
//
// type Out = GlobalTransform;
//
// fn extract_component(item: bevy::ecs::query::QueryItem<'_, Self::Query>) -> Option<Self::Out> {
// Some(*item)
// }
// }
#[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(Core3d),
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: XrCamera(eye),
main_texture_usages: CameraMainTextureUsages(
TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_SRC,
),
root_transform: default(),
}
}
}
#[derive(Debug, Clone, Component, Reflect, ExtractComponent)]
#[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_clip_from_view(&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: Res<crate::resources::XrViews>,
mut query: Query<(&mut Transform, &XrCamera, &mut XRProjection)>,
) {
//TODO calculate HMD position
for (mut transform, camera_type, mut xr_projection) in query.iter_mut() {
let view_idx = camera_type.0 as usize;
let view = match views.get(view_idx) {
Some(views) => views,
None => continue,
};
xr_projection.fov = view.fov;
transform.rotation = view.pose.orientation.to_quat();
transform.translation = view.pose.position.to_vec3();
}
}
pub fn xr_camera_head_sync_render_world(
views: Res<crate::resources::XrViews>,
mut query: Query<(&mut ExtractedView, &XrCamera, &RootTransform)>,
) {
for (mut extracted_view, camera_type, root) in query.iter_mut() {
let view_idx = camera_type.0 as usize;
let view = match views.get(view_idx) {
Some(views) => views,
None => continue,
};
let mut transform = Transform::IDENTITY;
transform.rotation = view.pose.orientation.to_quat();
transform.translation = view.pose.position.to_vec3();
extracted_view.world_from_view = root.mul_transform(transform).into();
}
}