34
.github/workflows/build.yml
vendored
34
.github/workflows/build.yml
vendored
@@ -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
5758
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
71
Cargo.toml
71
Cargo.toml
@@ -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"]
|
||||
176
LICENSE-APACHE
176
LICENSE-APACHE
@@ -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
|
||||
19
LICENSE-MIT
19
LICENSE-MIT
@@ -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.
|
||||
41
README.md
41
README.md
@@ -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
|
||||
|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
@@ -1 +0,0 @@
|
||||
# Use defaults
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
86
src/input.rs
86
src/input.rs
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
541
src/lib.rs
541
src/lib.rs
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
319
src/resources.rs
319
src/resources.rs
@@ -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),
|
||||
),
|
||||
])],
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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(_) => (),
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user