3
.cargo/config.toml
Normal file
3
.cargo/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
runner = "wasm-server-runner"
|
||||
rustflags = ["--cfg","web_sys_unstable_apis",]
|
||||
1
.envrc.example
Normal file
1
.envrc.example
Normal file
@@ -0,0 +1 @@
|
||||
use flake
|
||||
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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
**/target
|
||||
**/runtime_libs/arm64-v8a/*
|
||||
.vscode
|
||||
\.DS_Store
|
||||
.envrc
|
||||
.direnv
|
||||
3189
Cargo.lock
generated
3189
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
75
Cargo.toml
75
Cargo.toml
@@ -1,71 +1,18 @@
|
||||
[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"
|
||||
name = "bevy_mod_xr_backends"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
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"]
|
||||
repository = "https://github.com/awtterpip/bevy_oxr"
|
||||
license = "MIT/Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
ash = "0.37.3"
|
||||
bevy_mod_openxr.path = "./crates/bevy_openxr"
|
||||
bevy_mod_xr.path = "./crates/bevy_xr"
|
||||
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 }
|
||||
[workspace]
|
||||
members = ["crates/*", "crates/bevy_openxr/examples/android"]
|
||||
|
||||
[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"]
|
||||
[workspace.dependencies]
|
||||
bevy = "0.13.0"
|
||||
|
||||
15
README.md
15
README.md
@@ -1,14 +1,8 @@
|
||||
# Bevy OpenXR
|
||||
# Bevy Mod XR
|
||||
|
||||
A crate for adding openxr support to Bevy ( planned to be upstreamed ).
|
||||
A crate for adding openxr (and in the future webxr) support to Bevy.
|
||||
|
||||
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.
|
||||
To see it in action run the example in `crates/bevy_openxr/examples` with `cargo run -p bevy_openxr --example 3d_scene`
|
||||
|
||||
## Discord
|
||||
|
||||
@@ -22,7 +16,7 @@ https://discord.gg/sqMw7UJhNc
|
||||
|
||||
- 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`)
|
||||
- Like other bevy projects, make sure you're building in release (example: `cargo run -p bevy_openxr --example 3d_scene --release`)
|
||||
|
||||
## License
|
||||
|
||||
@@ -39,3 +33,4 @@ at your option. This means you can select the license you prefer!
|
||||
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.
|
||||
|
||||
|
||||
40
crates/bevy_openxr/Cargo.toml
Normal file
40
crates/bevy_openxr/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "bevy_mod_openxr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["vulkan", "d3d12", "passthrough"]
|
||||
vulkan = ["dep:ash"]
|
||||
d3d12 = ["wgpu/dx12", "wgpu-hal/dx12", "dep:winapi", "dep:d3d12"]
|
||||
passthrough = []
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_xr_utils.path = "../bevy_xr_utils"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
ndk-context = "0.1"
|
||||
jni = "0.20"
|
||||
|
||||
# bevy can't be placed behind target or proc macros won't work properly
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
|
||||
# all other dependencies are placed under this since on wasm, this crate is completely empty
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
openxr = "0.18.0"
|
||||
thiserror = "1.0.57"
|
||||
wgpu = "0.19.3"
|
||||
wgpu-hal = "0.19.3"
|
||||
bevy_mod_xr.path = "../bevy_xr"
|
||||
|
||||
ash = { version = "0.37.3", optional = true }
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
openxr = { version = "0.18.0", features = ["mint"] }
|
||||
wgpu = { version = "0.19.3", features = ["vulkan-portability"] }
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
openxr = { version = "0.18.0", features = ["mint", "static"] }
|
||||
winapi = { version = "0.3.9", optional = true }
|
||||
d3d12 = { version = "0.19", features = ["libloading"], optional = true }
|
||||
47
crates/bevy_openxr/examples/3d_scene.rs
Normal file
47
crates/bevy_openxr/examples/3d_scene.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::add_xr_plugins;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
160
crates/bevy_openxr/examples/actions.rs
Normal file
160
crates/bevy_openxr/examples/actions.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
// a simple example showing basic actions using the xr utils actions
|
||||
use bevy::{math::vec3, prelude::*};
|
||||
use bevy_mod_openxr::{add_xr_plugins, helper_traits::ToQuat, resources::OxrViews};
|
||||
use bevy_mod_xr::session::XrTrackingRoot;
|
||||
use bevy_xr_utils::xr_utils_actions::{
|
||||
ActiveSet, XRUtilsAction, XRUtilsActionSet, XRUtilsActionState, XRUtilsActionSystemSet,
|
||||
XRUtilsActionsPlugin, XRUtilsBinding,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.add_systems(Startup, setup_scene)
|
||||
.add_systems(
|
||||
Startup,
|
||||
create_action_entities.before(XRUtilsActionSystemSet::CreateEvents),
|
||||
)
|
||||
.add_plugins(XRUtilsActionsPlugin)
|
||||
.add_systems(Update, read_action_with_marker_component)
|
||||
.add_systems(Update, handle_flight_input)
|
||||
// Realtime lighting is expensive, use ambient light instead
|
||||
.insert_resource(AmbientLight {
|
||||
color: Default::default(),
|
||||
brightness: 500.0,
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct FlightActionMarker;
|
||||
|
||||
fn create_action_entities(mut commands: Commands) {
|
||||
//create a set
|
||||
let set = commands
|
||||
.spawn((
|
||||
XRUtilsActionSet {
|
||||
name: "flight".into(),
|
||||
pretty_name: "pretty flight set".into(),
|
||||
priority: u32::MIN,
|
||||
},
|
||||
ActiveSet, //marker to indicate we want this synced
|
||||
))
|
||||
.id();
|
||||
//create an action
|
||||
let action = commands
|
||||
.spawn((
|
||||
XRUtilsAction {
|
||||
action_name: "flight_input".into(),
|
||||
localized_name: "flight_input_localized".into(),
|
||||
action_type: bevy_mod_xr::actions::ActionType::Vector,
|
||||
},
|
||||
FlightActionMarker, //lets try a marker component
|
||||
))
|
||||
.id();
|
||||
|
||||
//create a binding
|
||||
let binding_index = commands
|
||||
.spawn(XRUtilsBinding {
|
||||
profile: "/interaction_profiles/valve/index_controller".into(),
|
||||
binding: "/user/hand/right/input/thumbstick".into(),
|
||||
})
|
||||
.id();
|
||||
let binding_touch = commands
|
||||
.spawn(XRUtilsBinding {
|
||||
profile: "/interaction_profiles/oculus/touch_controller".into(),
|
||||
binding: "/user/hand/right/input/thumbstick".into(),
|
||||
})
|
||||
.id();
|
||||
//add action to set, this isnt the best
|
||||
//TODO look into a better system
|
||||
commands.entity(action).add_child(binding_index);
|
||||
commands.entity(action).add_child(binding_touch);
|
||||
commands.entity(set).add_child(action);
|
||||
}
|
||||
|
||||
fn read_action_with_marker_component(
|
||||
mut action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>,
|
||||
) {
|
||||
//now for the actual checking
|
||||
for state in action_query.iter_mut() {
|
||||
info!("action state is: {:?}", state);
|
||||
}
|
||||
}
|
||||
|
||||
//lets add some flycam stuff
|
||||
fn handle_flight_input(
|
||||
action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>,
|
||||
mut oxr_root: Query<&mut Transform, With<XrTrackingRoot>>,
|
||||
time: Res<Time>,
|
||||
//use the views for hmd orientation
|
||||
views: ResMut<OxrViews>,
|
||||
) {
|
||||
//now for the actual checking
|
||||
for state in action_query.iter() {
|
||||
// info!("action state is: {:?}", state);
|
||||
match state {
|
||||
XRUtilsActionState::Bool(_) => (),
|
||||
XRUtilsActionState::Float(_) => (),
|
||||
XRUtilsActionState::Vector(vector_state) => {
|
||||
//assuming we are mapped to a vector lets fly
|
||||
let input_vector = vec3(
|
||||
vector_state.current_state[0],
|
||||
0.0,
|
||||
-vector_state.current_state[1],
|
||||
);
|
||||
//hard code speed for now
|
||||
let speed = 5.0;
|
||||
|
||||
let root = oxr_root.get_single_mut();
|
||||
match root {
|
||||
Ok(mut root_position) => {
|
||||
//lets assume HMD based direction for now
|
||||
let view = views.first();
|
||||
match view {
|
||||
Some(v) => {
|
||||
let reference_quat = v.pose.orientation.to_quat();
|
||||
let locomotion_vector = reference_quat.mul_vec3(input_vector);
|
||||
|
||||
root_position.translation +=
|
||||
locomotion_vector * speed * time.delta_seconds();
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
info!("handle_flight_input: error getting root position for flight actions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/bevy_openxr/examples/android/.gitignore
vendored
Normal file
1
crates/bevy_openxr/examples/android/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/runtime_libs
|
||||
@@ -1,26 +1,21 @@
|
||||
[package]
|
||||
name = "bevy_openxr_android"
|
||||
version = "0.1.0"
|
||||
description = "Example for building an Android OpenXR app with Bevy"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy_mod_openxr.path = "../.."
|
||||
bevy.workspace = true
|
||||
bevy_xr_utils.path = "../../../bevy_xr_utils"
|
||||
|
||||
|
||||
[lib]
|
||||
name = "bevy_openxr_android"
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
bevy_oxr.path = "../.."
|
||||
openxr = { workspace = true, features = ["mint"] }
|
||||
|
||||
# [profile.release]
|
||||
# lto = "fat"
|
||||
# codegen-units = 1
|
||||
# panic = "abort"
|
||||
|
||||
# This metadata is used by `cargo-apk` - `xbuild` uses the `manifest.yaml` instead.
|
||||
[package.metadata.android]
|
||||
package = "org.bevyengine.example_openxr_android"
|
||||
@@ -47,11 +42,31 @@ launch_mode = "singleTask"
|
||||
orientation = "landscape"
|
||||
resizeable_activity = false
|
||||
|
||||
[[package.metadata.android.uses_feature]]
|
||||
name = "android.hardware.vr.headtracking"
|
||||
required = true
|
||||
|
||||
[[package.metadata.android.uses_feature]]
|
||||
name = "oculus.software.handtracking"
|
||||
required = true
|
||||
|
||||
[[package.metadata.android.uses_feature]]
|
||||
name = "com.oculus.feature.PASSTHROUGH"
|
||||
required = true
|
||||
|
||||
[[package.metadata.android.uses_feature]]
|
||||
name = "com.oculus.experimental.enabled"
|
||||
required = true
|
||||
|
||||
[[package.metadata.android.uses_permission]]
|
||||
name = "com.oculus.permission.HAND_TRACKING"
|
||||
|
||||
[[package.metadata.android.application.activity.intent_filter]]
|
||||
actions = ["android.intent.action.MAIN"]
|
||||
categories = [
|
||||
"com.oculus.intent.category.VR",
|
||||
"android.intent.category.LAUNCHER",
|
||||
"org.khronos.openxr.intent.category.IMMERSIVE_HMD",
|
||||
]
|
||||
|
||||
# !! IMPORTANT !!
|
||||
@@ -36,4 +36,4 @@ android:
|
||||
- "android.intent.category.LAUNCHER"
|
||||
- "org.khronos.openxr.intent.category.IMMERSIVE_HMD"
|
||||
sdk:
|
||||
target_sdk_version: 32
|
||||
target_sdk_version: 32
|
||||
58
crates/bevy_openxr/examples/android/src/lib.rs
Normal file
58
crates/bevy_openxr/examples/android/src/lib.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::{add_xr_plugins, init::OxrInitPlugin, types::OxrExtensions};
|
||||
|
||||
#[bevy_main]
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins).set(OxrInitPlugin {
|
||||
app_info: default(),
|
||||
exts: {
|
||||
let mut exts = OxrExtensions::default();
|
||||
exts.enable_fb_passthrough();
|
||||
exts.enable_hand_tracking();
|
||||
exts
|
||||
},
|
||||
blend_modes: default(),
|
||||
backends: default(),
|
||||
formats: default(),
|
||||
resolutions: default(),
|
||||
synchronous_pipeline_compilation: default(),
|
||||
}))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.insert_resource(Msaa::Off)
|
||||
.add_systems(Startup, setup)
|
||||
.insert_resource(AmbientLight {
|
||||
color: Default::default(),
|
||||
brightness: 500.0,
|
||||
})
|
||||
.insert_resource(ClearColor(Color::NONE))
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let mut white: StandardMaterial = Color::WHITE.into();
|
||||
white.unlit = true;
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(white),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
let mut cube_mat: StandardMaterial = Color::rgb_u8(124, 144, 255).into();
|
||||
cube_mat.unlit = true;
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(cube_mat),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
108
crates/bevy_openxr/examples/overlay.rs
Normal file
108
crates/bevy_openxr/examples/overlay.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::{add_xr_plugins, init::OxrInitPlugin, types::OxrExtensions};
|
||||
use bevy_mod_xr::session::XrState;
|
||||
// use openxr::EnvironmentBlendMode;
|
||||
// use wgpu::TextureFormat;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins).build().set(OxrInitPlugin {
|
||||
exts: {
|
||||
let mut exts = OxrExtensions::default();
|
||||
exts.enable_hand_tracking();
|
||||
exts.other.push("XR_EXTX_overlay\0".into());
|
||||
exts
|
||||
},
|
||||
// blend_modes: Some({
|
||||
// let mut v = Vec::new();
|
||||
// v.push(EnvironmentBlendMode::ALPHA_BLEND);
|
||||
// v.push(EnvironmentBlendMode::ADDITIVE);
|
||||
// v.push(EnvironmentBlendMode::OPAQUE);
|
||||
// v
|
||||
// }),
|
||||
// formats: Some({
|
||||
// let mut v = Vec::new();
|
||||
// // v.push(TextureFormat::Rgba8Uint);
|
||||
// v.push(TextureFormat::Rgba8Unorm);
|
||||
// v.push(TextureFormat::Rgba8UnormSrgb);
|
||||
// v
|
||||
// }),
|
||||
..OxrInitPlugin::default()
|
||||
}))
|
||||
.insert_resource(ClearColor(Color::NONE))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, handle_input)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn handle_input(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut end: EventWriter<bevy_mod_xr::session::XrEndSessionEvent>,
|
||||
mut destroy: EventWriter<bevy_mod_xr::session::XrDestroySessionEvent>,
|
||||
mut begin: EventWriter<bevy_mod_xr::session::XrBeginSessionEvent>,
|
||||
mut create: EventWriter<bevy_mod_xr::session::XrCreateSessionEvent>,
|
||||
mut request_exit: EventWriter<bevy_mod_xr::session::XrRequestExitEvent>,
|
||||
state: Res<XrState>,
|
||||
) {
|
||||
if keys.just_pressed(KeyCode::KeyE) {
|
||||
info!("sending end");
|
||||
end.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyC) {
|
||||
info!("sending create");
|
||||
create.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyD) {
|
||||
info!("sending destroy");
|
||||
destroy.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyB) {
|
||||
info!("sending begin");
|
||||
begin.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyR) {
|
||||
info!("sending request exit");
|
||||
request_exit.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyI) {
|
||||
info!("current state: {:?}", *state);
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
// commands.spawn(PbrBundle {
|
||||
// mesh: meshes.add(Circle::new(4.0)),
|
||||
// material: materials.add(Color::WHITE),
|
||||
// transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
// ..default()
|
||||
// });
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 2.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
160
crates/bevy_openxr/examples/raw_actions.rs
Normal file
160
crates/bevy_openxr/examples/raw_actions.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::{
|
||||
action_binding::{OxrSendActionBindings, OxrSuggestActionBinding},
|
||||
action_set_attaching::OxrAttachActionSet,
|
||||
action_set_syncing::{OxrActionSetSyncSet, OxrSyncActionSet},
|
||||
add_xr_plugins,
|
||||
init::create_xr_session,
|
||||
resources::OxrInstance,
|
||||
session::OxrSession,
|
||||
spaces::OxrSpaceExt,
|
||||
};
|
||||
use bevy_mod_xr::{
|
||||
session::{session_available, XrCreateSession, XrTrackingRoot},
|
||||
spaces::XrSpace,
|
||||
types::XrPose,
|
||||
};
|
||||
use openxr::Posef;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(add_xr_plugins(DefaultPlugins));
|
||||
app.add_systems(XrCreateSession, spawn_hands.after(create_xr_session));
|
||||
app.add_systems(XrCreateSession, attach_set.after(create_xr_session));
|
||||
app.add_systems(PreUpdate, sync_actions.before(OxrActionSetSyncSet));
|
||||
app.add_systems(OxrSendActionBindings, suggest_action_bindings);
|
||||
app.add_systems(Startup, create_actions.run_if(session_available));
|
||||
app.add_systems(Startup, setup);
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
fn attach_set(actions: Res<ControllerActions>, mut attach: EventWriter<OxrAttachActionSet>) {
|
||||
attach.send(OxrAttachActionSet(actions.set.clone()));
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct ControllerActions {
|
||||
set: openxr::ActionSet,
|
||||
left: openxr::Action<Posef>,
|
||||
right: openxr::Action<Posef>,
|
||||
}
|
||||
fn sync_actions(actions: Res<ControllerActions>, mut sync: EventWriter<OxrSyncActionSet>) {
|
||||
sync.send(OxrSyncActionSet(actions.set.clone()));
|
||||
}
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
fn suggest_action_bindings(
|
||||
actions: Res<ControllerActions>,
|
||||
mut bindings: EventWriter<OxrSuggestActionBinding>,
|
||||
) {
|
||||
bindings.send(OxrSuggestActionBinding {
|
||||
action: actions.left.as_raw(),
|
||||
interaction_profile: "/interaction_profiles/oculus/touch_controller".into(),
|
||||
bindings: vec!["/user/hand/left/input/grip/pose".into()],
|
||||
});
|
||||
bindings.send(OxrSuggestActionBinding {
|
||||
action: actions.right.as_raw(),
|
||||
interaction_profile: "/interaction_profiles/oculus/touch_controller".into(),
|
||||
bindings: vec!["/user/hand/right/input/grip/pose".into()],
|
||||
});
|
||||
}
|
||||
fn create_actions(instance: Res<OxrInstance>, mut cmds: Commands) {
|
||||
let set = instance.create_action_set("hands", "Hands", 0).unwrap();
|
||||
let left = set
|
||||
.create_action("left_pose", "Left Hand Grip Pose", &[])
|
||||
.unwrap();
|
||||
let right = set
|
||||
.create_action("right_pose", "Right Hand Grip Pose", &[])
|
||||
.unwrap();
|
||||
|
||||
cmds.insert_resource(ControllerActions { set, left, right })
|
||||
}
|
||||
|
||||
fn spawn_hands(
|
||||
actions: Res<ControllerActions>,
|
||||
mut cmds: Commands,
|
||||
root: Query<Entity, With<XrTrackingRoot>>,
|
||||
session: Res<OxrSession>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// This is a demonstation of how to integrate with the openxr crate, the right space is the
|
||||
// recommended way
|
||||
let left_space = XrSpace::from_openxr_space(
|
||||
actions
|
||||
.left
|
||||
.create_space(
|
||||
session.deref().deref().clone(),
|
||||
openxr::Path::NULL,
|
||||
Posef::IDENTITY,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let right_space = session
|
||||
.create_action_space(&actions.right, openxr::Path::NULL, XrPose::IDENTITY)
|
||||
.unwrap();
|
||||
let left = cmds
|
||||
.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(0.1, 0.1, 0.05)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
},
|
||||
left_space,
|
||||
Controller,
|
||||
))
|
||||
.id();
|
||||
let right = cmds
|
||||
.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(0.1, 0.1, 0.05)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
},
|
||||
right_space,
|
||||
Controller,
|
||||
))
|
||||
.id();
|
||||
|
||||
cmds.entity(root.single()).push_children(&[left, right]);
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Controller;
|
||||
75
crates/bevy_openxr/examples/sessions.rs
Normal file
75
crates/bevy_openxr/examples/sessions.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::add_xr_plugins;
|
||||
use bevy_mod_xr::session::{XrSessionPlugin, XrState};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins).set(XrSessionPlugin { auto_handle: false }))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, handle_input)
|
||||
.insert_resource(AmbientLight::default())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn handle_input(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
mut end: EventWriter<bevy_mod_xr::session::XrEndSessionEvent>,
|
||||
mut destroy: EventWriter<bevy_mod_xr::session::XrDestroySessionEvent>,
|
||||
mut begin: EventWriter<bevy_mod_xr::session::XrBeginSessionEvent>,
|
||||
mut create: EventWriter<bevy_mod_xr::session::XrCreateSessionEvent>,
|
||||
mut request_exit: EventWriter<bevy_mod_xr::session::XrRequestExitEvent>,
|
||||
state: Res<XrState>,
|
||||
) {
|
||||
if keys.just_pressed(KeyCode::KeyE) {
|
||||
info!("sending end");
|
||||
end.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyC) {
|
||||
info!("sending create");
|
||||
create.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyD) {
|
||||
info!("sending destroy");
|
||||
destroy.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyB) {
|
||||
info!("sending begin");
|
||||
begin.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyR) {
|
||||
info!("sending request exit");
|
||||
request_exit.send_default();
|
||||
}
|
||||
if keys.just_pressed(KeyCode::KeyI) {
|
||||
info!("current state: {:?}", *state);
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(124, 144, 255)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
201
crates/bevy_openxr/examples/transform_utils.rs
Normal file
201
crates/bevy_openxr/examples/transform_utils.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
//! A simple example of how to use the transform utils to set the players position and orientation
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::add_xr_plugins;
|
||||
use bevy_xr_utils::transform_utils::{self, SnapToPosition, SnapToRotation};
|
||||
use bevy_xr_utils::xr_utils_actions::{
|
||||
ActiveSet, XRUtilsAction, XRUtilsActionSet, XRUtilsActionState, XRUtilsActionSystemSet,
|
||||
XRUtilsActionsPlugin, XRUtilsBinding,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(add_xr_plugins(DefaultPlugins))
|
||||
.add_plugins(bevy_xr_utils::hand_gizmos::HandGizmosPlugin)
|
||||
.add_plugins(transform_utils::TransformUtilitiesPlugin)
|
||||
.add_systems(Startup, setup)
|
||||
.add_plugins(XRUtilsActionsPlugin)
|
||||
.add_systems(
|
||||
Startup,
|
||||
create_action_entities.before(XRUtilsActionSystemSet::CreateEvents),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
send_look_at_red_cube_event.after(XRUtilsActionSystemSet::SyncActionStates),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
send_recenter.after(XRUtilsActionSystemSet::SyncActionStates),
|
||||
)
|
||||
.insert_resource(AmbientLight {
|
||||
color: Default::default(),
|
||||
brightness: 500.0,
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// circular base
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Circle::new(4.0)),
|
||||
material: materials.add(Color::WHITE),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
..default()
|
||||
});
|
||||
// red cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(252, 44, 3)),
|
||||
transform: Transform::from_xyz(4.0, 0.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
});
|
||||
// blue cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(3, 28, 252)),
|
||||
transform: Transform::from_xyz(-4.0, 0.5, 0.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
});
|
||||
// green cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(3, 252, 32)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 4.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
});
|
||||
// white cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(250, 250, 250)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, -4.0).with_scale(Vec3::splat(0.5)),
|
||||
..default()
|
||||
});
|
||||
// black cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)),
|
||||
material: materials.add(Color::rgb_u8(0, 0, 0)),
|
||||
transform: Transform::from_xyz(0.0, 0.1, 0.0).with_scale(Vec3::splat(0.2)),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct FaceRedAction;
|
||||
|
||||
fn create_action_entities(mut commands: Commands) {
|
||||
//create a set
|
||||
let set = commands
|
||||
.spawn((
|
||||
XRUtilsActionSet {
|
||||
name: "locomotion".into(),
|
||||
pretty_name: "locomotion set".into(),
|
||||
priority: u32::MIN,
|
||||
},
|
||||
ActiveSet, //marker to indicate we want this synced
|
||||
))
|
||||
.id();
|
||||
//create an action
|
||||
let action = commands
|
||||
.spawn((
|
||||
XRUtilsAction {
|
||||
action_name: "face_red".into(),
|
||||
localized_name: "face_red_localized".into(),
|
||||
action_type: bevy_mod_xr::actions::ActionType::Bool,
|
||||
},
|
||||
FaceRedAction, //lets try a marker component
|
||||
))
|
||||
.id();
|
||||
|
||||
//create a binding
|
||||
let binding = commands
|
||||
.spawn(XRUtilsBinding {
|
||||
profile: "/interaction_profiles/valve/index_controller".into(),
|
||||
binding: "/user/hand/left/input/a/click".into(),
|
||||
})
|
||||
.id();
|
||||
|
||||
//add action to set, this isnt the best
|
||||
//TODO look into a better system
|
||||
commands.entity(action).add_child(binding);
|
||||
commands.entity(set).add_child(action);
|
||||
|
||||
let action = commands
|
||||
.spawn((
|
||||
XRUtilsAction {
|
||||
action_name: "center".into(),
|
||||
localized_name: "center_localized".into(),
|
||||
action_type: bevy_mod_xr::actions::ActionType::Bool,
|
||||
},
|
||||
Center, //lets try a marker component
|
||||
))
|
||||
.id();
|
||||
|
||||
//create a binding
|
||||
let binding = commands
|
||||
.spawn(XRUtilsBinding {
|
||||
profile: "/interaction_profiles/valve/index_controller".into(),
|
||||
binding: "/user/hand/right/input/a/click".into(),
|
||||
})
|
||||
.id();
|
||||
|
||||
//add action to set, this isnt the best
|
||||
//TODO look into a better system
|
||||
commands.entity(action).add_child(binding);
|
||||
commands.entity(set).add_child(action);
|
||||
}
|
||||
|
||||
fn send_look_at_red_cube_event(
|
||||
mut action_query: Query<&XRUtilsActionState, With<FaceRedAction>>,
|
||||
mut event_writer: EventWriter<SnapToRotation>,
|
||||
) {
|
||||
//now for the actual checking
|
||||
for state in action_query.iter_mut() {
|
||||
match state {
|
||||
XRUtilsActionState::Bool(state) => {
|
||||
let send = state.current_state && state.changed_since_last_sync;
|
||||
if send {
|
||||
info!("send facing");
|
||||
let quat = Transform::default()
|
||||
.looking_at(Transform::from_xyz(4.0, 0.0, 0.0).translation, Vec3::Y); //this is a transform facing the red cube from the center of the scene, you should use the HMD posision but I was lazy.
|
||||
event_writer.send(SnapToRotation(quat.rotation));
|
||||
}
|
||||
}
|
||||
XRUtilsActionState::Float(_) => (),
|
||||
XRUtilsActionState::Vector(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Center;
|
||||
|
||||
fn send_recenter(
|
||||
mut action_query: Query<&XRUtilsActionState, With<Center>>,
|
||||
mut event_writer: EventWriter<SnapToPosition>,
|
||||
) {
|
||||
//now for the actual checking
|
||||
for state in action_query.iter_mut() {
|
||||
match state {
|
||||
XRUtilsActionState::Bool(state) => {
|
||||
let send = state.current_state && state.changed_since_last_sync;
|
||||
if send {
|
||||
let center = Transform::default().translation;
|
||||
|
||||
event_writer.send(SnapToPosition(center));
|
||||
}
|
||||
}
|
||||
XRUtilsActionState::Float(_) => (),
|
||||
XRUtilsActionState::Vector(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
4
crates/bevy_openxr/src/lib.rs
Normal file
4
crates/bevy_openxr/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod openxr;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use openxr::*;
|
||||
102
crates/bevy_openxr/src/openxr/action_binding.rs
Normal file
102
crates/bevy_openxr/src/openxr/action_binding.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ptr;
|
||||
|
||||
use bevy::ecs::schedule::ScheduleLabel;
|
||||
use bevy::ecs::system::RunSystemOnce;
|
||||
use bevy::prelude::*;
|
||||
use bevy::utils::HashMap;
|
||||
use bevy_mod_xr::session::XrSessionCreatedEvent;
|
||||
use openxr::sys::ActionSuggestedBinding;
|
||||
|
||||
use crate::resources::OxrInstance;
|
||||
|
||||
impl Plugin for OxrActionBindingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_schedule(Schedule::new(OxrSendActionBindings));
|
||||
app.add_event::<OxrSuggestActionBinding>();
|
||||
app.add_systems(
|
||||
Update,
|
||||
run_action_binding_sugestion.run_if(on_event::<XrSessionCreatedEvent>()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// This could for now be handled better with a SystemSet, but in the future we might want to add an
|
||||
// Event to allow requesting binding suggestion for new actions
|
||||
pub(crate) fn run_action_binding_sugestion(world: &mut World) {
|
||||
world.run_schedule(OxrSendActionBindings);
|
||||
world.run_system_once(bind_actions);
|
||||
}
|
||||
|
||||
fn bind_actions(instance: Res<OxrInstance>, mut actions: EventReader<OxrSuggestActionBinding>) {
|
||||
let mut bindings: HashMap<&str, Vec<ActionSuggestedBinding>> = HashMap::new();
|
||||
for e in actions.read() {
|
||||
bindings.entry(&e.interaction_profile).or_default().extend(
|
||||
e.bindings
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|b| match instance.string_to_path(&b) {
|
||||
Ok(p) => Some(p),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Unable to convert path: \"{}\"; error: {}",
|
||||
b,
|
||||
err.to_string()
|
||||
);
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|p| ActionSuggestedBinding {
|
||||
action: e.action,
|
||||
binding: p,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
use openxr::sys;
|
||||
for (profile, bindings) in bindings.iter() {
|
||||
let interaction_profile = match instance.string_to_path(profile) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Unable to convert interaction profile path: \"{}\"; error: \"{}\" Skipping all suggestions for this interaction profile",
|
||||
profile,
|
||||
err.to_string()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// Using the raw way since we want all actions through one event and we can't use the
|
||||
// Bindings from the openxr crate since they can't be created from raw actions
|
||||
let info = sys::InteractionProfileSuggestedBinding {
|
||||
ty: sys::InteractionProfileSuggestedBinding::TYPE,
|
||||
next: ptr::null(),
|
||||
interaction_profile,
|
||||
count_suggested_bindings: bindings.len() as u32,
|
||||
suggested_bindings: bindings.as_ptr() as *const _ as _,
|
||||
};
|
||||
match unsafe {
|
||||
(instance.fp().suggest_interaction_profile_bindings)(instance.as_raw(), &info)
|
||||
} {
|
||||
openxr::sys::Result::ERROR_ACTIONSETS_ALREADY_ATTACHED => error!(
|
||||
"Binding Suggested for an Action whith an ActionSet that was already attached!"
|
||||
),
|
||||
openxr::sys::Result::ERROR_PATH_INVALID => error!("Invalid Path Suggested!"),
|
||||
openxr::sys::Result::ERROR_PATH_UNSUPPORTED => error!("Suggested Path Unsupported!"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Clone)]
|
||||
/// Only Send this for Actions that were not attached yet!
|
||||
pub struct OxrSuggestActionBinding {
|
||||
pub action: openxr::sys::Action,
|
||||
pub interaction_profile: Cow<'static, str>,
|
||||
pub bindings: Vec<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
pub struct OxrActionBindingPlugin;
|
||||
// Maybe use a SystemSet in an XrStartup Schedule?
|
||||
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OxrSendActionBindings;
|
||||
44
crates/bevy_openxr/src/openxr/action_set_attaching.rs
Normal file
44
crates/bevy_openxr/src/openxr/action_set_attaching.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use crate::{action_binding::run_action_binding_sugestion, session::OxrSession};
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_xr::session::XrSessionCreatedEvent;
|
||||
|
||||
impl Plugin for OxrActionAttachingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<OxrAttachActionSet>();
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
attach_sets
|
||||
.run_if(on_event::<XrSessionCreatedEvent>())
|
||||
.after(run_action_binding_sugestion),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_sets(session: Res<OxrSession>, mut events: EventReader<OxrAttachActionSet>) {
|
||||
let sets = events.read().map(|v| &v.0).collect::<Vec<_>>();
|
||||
if sets.is_empty() {
|
||||
return;
|
||||
}
|
||||
info!("attaching {} sessions", sets.len());
|
||||
match session.attach_action_sets(&sets) {
|
||||
Ok(_) => {
|
||||
info!("attached sessions!")
|
||||
}
|
||||
Err(openxr::sys::Result::ERROR_ACTIONSETS_ALREADY_ATTACHED) => {
|
||||
error!("Action Sets Already attached!");
|
||||
}
|
||||
|
||||
Err(openxr::sys::Result::ERROR_HANDLE_INVALID) => error!("Invalid ActionSet Handle!"),
|
||||
Err(e) => error!(
|
||||
"Unhandled Error while attaching action sets: {}",
|
||||
e.to_string()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Event, Clone)]
|
||||
/// Send this event for every ActionSet you want to attach to the [`OxrSession`] once the Session Status changed to Ready. all requests will
|
||||
/// be applied in [`PostUpdate`]
|
||||
pub struct OxrAttachActionSet(pub openxr::ActionSet);
|
||||
|
||||
pub struct OxrActionAttachingPlugin;
|
||||
38
crates/bevy_openxr/src/openxr/action_set_syncing.rs
Normal file
38
crates/bevy_openxr/src/openxr/action_set_syncing.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::session::OxrSession;
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_xr::session::session_running;
|
||||
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct OxrActionSetSyncSet;
|
||||
|
||||
impl Plugin for OxrActionSyncingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<OxrSyncActionSet>();
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
sync_sets.in_set(OxrActionSetSyncSet).run_if(session_running), // .in_set(OxrPreUpdateSet::SyncActions),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_sets(session: Res<OxrSession>, mut events: EventReader<OxrSyncActionSet>) {
|
||||
let sets = events
|
||||
.read()
|
||||
.map(|v| &v.0)
|
||||
.map(openxr::ActiveActionSet::new)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if sets.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = session.sync_actions(&sets) {
|
||||
warn!("error while syncing actionsets: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Clone)]
|
||||
/// Send this event for every ActionSet you want to attach to the [`OxrSession`] once the Session Status changed to Ready. all requests will
|
||||
pub struct OxrSyncActionSet(pub openxr::ActionSet);
|
||||
|
||||
pub struct OxrActionSyncingPlugin;
|
||||
117
crates/bevy_openxr/src/openxr/error.rs
Normal file
117
crates/bevy_openxr/src/openxr/error.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
||||
use super::graphics::GraphicsBackend;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum OxrError {
|
||||
#[error("OpenXR error: {0}")]
|
||||
OpenXrError(#[from] openxr::sys::Result),
|
||||
#[error("OpenXR loading error: {0}")]
|
||||
OpenXrLoadingError(#[from] openxr::LoadError),
|
||||
#[error("WGPU instance error: {0}")]
|
||||
WgpuInstanceError(#[from] wgpu_hal::InstanceError),
|
||||
#[error("WGPU device error: {0}")]
|
||||
WgpuDeviceError(#[from] wgpu_hal::DeviceError),
|
||||
#[error("WGPU request device error: {0}")]
|
||||
WgpuRequestDeviceError(#[from] wgpu::RequestDeviceError),
|
||||
#[error("Unsupported texture format: {0:?}")]
|
||||
UnsupportedTextureFormat(wgpu::TextureFormat),
|
||||
#[error("Graphics backend '{0:?}' is not available")]
|
||||
UnavailableBackend(GraphicsBackend),
|
||||
#[error("No compatible backend available")]
|
||||
NoAvailableBackend,
|
||||
#[error("No compatible view configuration available")]
|
||||
NoAvailableViewConfiguration,
|
||||
#[error("No compatible blend mode available")]
|
||||
NoAvailableBlendMode,
|
||||
#[error("No compatible format available")]
|
||||
NoAvailableFormat,
|
||||
#[error("OpenXR runtime does not support these extensions: {0}")]
|
||||
UnavailableExtensions(UnavailableExts),
|
||||
#[error("Could not meet graphics requirements for platform. See console for details")]
|
||||
FailedGraphicsRequirements,
|
||||
#[error(
|
||||
"Tried to use item {item} with backend {backend}. Expected backend {expected_backend}"
|
||||
)]
|
||||
GraphicsBackendMismatch {
|
||||
item: &'static str,
|
||||
backend: &'static str,
|
||||
expected_backend: &'static str,
|
||||
},
|
||||
#[error("Failed to create CString: {0}")]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error("Graphics init error: {0}")]
|
||||
InitError(InitError),
|
||||
}
|
||||
|
||||
pub use init_error::InitError;
|
||||
|
||||
/// This module is needed because thiserror does not allow conditional compilation within enums for some reason,
|
||||
/// so graphics api specific errors are implemented here.
|
||||
mod init_error {
|
||||
use super::OxrError;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InitError {
|
||||
#[cfg(feature = "vulkan")]
|
||||
VulkanError(ash::vk::Result),
|
||||
#[cfg(feature = "vulkan")]
|
||||
VulkanLoadingError(ash::LoadingError),
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
FailedToFindD3D12Adapter,
|
||||
}
|
||||
|
||||
impl fmt::Display for InitError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
#[cfg(feature = "vulkan")]
|
||||
InitError::VulkanError(error) => write!(f, "Vulkan error: {}", error),
|
||||
#[cfg(feature = "vulkan")]
|
||||
InitError::VulkanLoadingError(error) => {
|
||||
write!(f, "Vulkan loading error: {}", error)
|
||||
}
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
InitError::FailedToFindD3D12Adapter => write!(
|
||||
f,
|
||||
"Failed to find D3D12 adapter matching LUID provided by the OpenXR runtime"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
impl From<ash::vk::Result> for OxrError {
|
||||
fn from(value: ash::vk::Result) -> Self {
|
||||
Self::InitError(InitError::VulkanError(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vulkan")]
|
||||
impl From<ash::LoadingError> for OxrError {
|
||||
fn from(value: ash::LoadingError) -> Self {
|
||||
Self::InitError(InitError::VulkanLoadingError(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Cow<'static, str>>> for OxrError {
|
||||
fn from(value: Vec<Cow<'static, str>>) -> Self {
|
||||
Self::UnavailableExtensions(UnavailableExts(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnavailableExts(Vec<Cow<'static, str>>);
|
||||
|
||||
impl fmt::Display for UnavailableExts {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for s in &self.0 {
|
||||
write!(f, "\t{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
325
crates/bevy_openxr/src/openxr/exts.rs
Normal file
325
crates/bevy_openxr/src/openxr/exts.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
use bevy::{ecs::system::Resource, prelude::{Deref, DerefMut}};
|
||||
use openxr::ExtensionSet;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut, Resource)]
|
||||
pub struct OxrEnabledExtensions(pub OxrExtensions);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deref, DerefMut)]
|
||||
pub struct OxrExtensions(ExtensionSet);
|
||||
impl OxrExtensions {
|
||||
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
|
||||
}
|
||||
/// returns true if all of the extensions enabled are also available in `available_exts`
|
||||
pub fn is_available(&self, available_exts: &OxrExtensions) -> bool {
|
||||
self.clone() & available_exts.clone() == *self
|
||||
}
|
||||
}
|
||||
impl From<ExtensionSet> for OxrExtensions {
|
||||
fn from(value: ExtensionSet) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl From<OxrExtensions> for ExtensionSet {
|
||||
fn from(val: OxrExtensions) -> Self {
|
||||
val.0
|
||||
}
|
||||
}
|
||||
impl Default for OxrExtensions {
|
||||
fn default() -> Self {
|
||||
let exts = ExtensionSet::default();
|
||||
//exts.ext_hand_tracking = true;
|
||||
Self(exts)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! unavailable_exts {
|
||||
(
|
||||
$exts:ty;
|
||||
$(
|
||||
$(
|
||||
#[$meta:meta]
|
||||
)*
|
||||
$ident:ident
|
||||
),*
|
||||
$(,)?
|
||||
) => {
|
||||
impl $exts {
|
||||
/// Returns any extensions needed by `required_exts` that aren't available in `self`
|
||||
pub fn unavailable_exts(&self, required_exts: &Self) -> Vec<std::borrow::Cow<'static, str>> {
|
||||
let mut exts = vec![];
|
||||
$(
|
||||
$(
|
||||
#[$meta]
|
||||
)*
|
||||
if required_exts.0.$ident && !self.0.$ident {
|
||||
exts.push(std::borrow::Cow::Borrowed(stringify!($ident)))
|
||||
}
|
||||
)*
|
||||
for ext in required_exts.0.other.iter() {
|
||||
if !self.0.other.contains(ext) {
|
||||
exts.push(std::borrow::Cow::Owned(ext.clone()))
|
||||
}
|
||||
}
|
||||
exts
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bitor {
|
||||
(
|
||||
$exts:ty;
|
||||
$(
|
||||
$(
|
||||
#[$meta:meta]
|
||||
)*
|
||||
$ident:ident
|
||||
),*
|
||||
$(,)?
|
||||
) => {
|
||||
impl std::ops::BitOr for $exts {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
let mut out = ExtensionSet::default();
|
||||
$(
|
||||
$(
|
||||
#[$meta]
|
||||
)*
|
||||
{
|
||||
out.$ident = self.0.$ident || rhs.0.$ident;
|
||||
}
|
||||
|
||||
)*
|
||||
out.other = self.0.other;
|
||||
for ext in rhs.0.other {
|
||||
if !out.other.contains(&ext) {
|
||||
out.other.push(ext);
|
||||
}
|
||||
}
|
||||
Self(out)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bitand {
|
||||
(
|
||||
$exts:ty;
|
||||
$(
|
||||
$(
|
||||
#[$meta:meta]
|
||||
)*
|
||||
$ident:ident
|
||||
),*
|
||||
$(,)?
|
||||
) => {
|
||||
impl std::ops::BitAnd for $exts {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
let mut out = ExtensionSet::default();
|
||||
$(
|
||||
$(
|
||||
#[$meta]
|
||||
)*
|
||||
{
|
||||
out.$ident = self.0.$ident && rhs.0.$ident;
|
||||
}
|
||||
|
||||
)*
|
||||
for ext in self.0.other {
|
||||
if rhs.0.other.contains(&ext) {
|
||||
out.other.push(ext);
|
||||
}
|
||||
}
|
||||
Self(out)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_ext {
|
||||
(
|
||||
$(
|
||||
$macro:ident
|
||||
),*
|
||||
|
||||
) => {
|
||||
$(
|
||||
$macro! {
|
||||
OxrExtensions;
|
||||
almalence_digital_lens_control,
|
||||
bd_controller_interaction,
|
||||
epic_view_configuration_fov,
|
||||
ext_performance_settings,
|
||||
ext_thermal_query,
|
||||
ext_debug_utils,
|
||||
ext_eye_gaze_interaction,
|
||||
ext_view_configuration_depth_range,
|
||||
ext_conformance_automation,
|
||||
ext_hand_tracking,
|
||||
#[cfg(windows)]
|
||||
ext_win32_appcontainer_compatible,
|
||||
ext_dpad_binding,
|
||||
ext_hand_joints_motion_range,
|
||||
ext_samsung_odyssey_controller,
|
||||
ext_hp_mixed_reality_controller,
|
||||
ext_palm_pose,
|
||||
ext_uuid,
|
||||
ext_hand_interaction,
|
||||
ext_active_action_set_priority,
|
||||
ext_local_floor,
|
||||
ext_hand_tracking_data_source,
|
||||
ext_plane_detection,
|
||||
fb_composition_layer_image_layout,
|
||||
fb_composition_layer_alpha_blend,
|
||||
#[cfg(target_os = "android")]
|
||||
fb_android_surface_swapchain_create,
|
||||
fb_swapchain_update_state,
|
||||
fb_composition_layer_secure_content,
|
||||
fb_body_tracking,
|
||||
fb_display_refresh_rate,
|
||||
fb_color_space,
|
||||
fb_hand_tracking_mesh,
|
||||
fb_hand_tracking_aim,
|
||||
fb_hand_tracking_capsules,
|
||||
fb_spatial_entity,
|
||||
fb_foveation,
|
||||
fb_foveation_configuration,
|
||||
fb_keyboard_tracking,
|
||||
fb_triangle_mesh,
|
||||
fb_passthrough,
|
||||
fb_render_model,
|
||||
fb_spatial_entity_query,
|
||||
fb_spatial_entity_storage,
|
||||
fb_foveation_vulkan,
|
||||
#[cfg(target_os = "android")]
|
||||
fb_swapchain_update_state_android_surface,
|
||||
fb_swapchain_update_state_opengl_es,
|
||||
fb_swapchain_update_state_vulkan,
|
||||
fb_touch_controller_pro,
|
||||
fb_spatial_entity_sharing,
|
||||
fb_space_warp,
|
||||
fb_haptic_amplitude_envelope,
|
||||
fb_scene,
|
||||
fb_scene_capture,
|
||||
fb_spatial_entity_container,
|
||||
fb_face_tracking,
|
||||
fb_eye_tracking_social,
|
||||
fb_passthrough_keyboard_hands,
|
||||
fb_composition_layer_settings,
|
||||
fb_touch_controller_proximity,
|
||||
fb_haptic_pcm,
|
||||
fb_composition_layer_depth_test,
|
||||
fb_spatial_entity_storage_batch,
|
||||
fb_spatial_entity_user,
|
||||
htc_vive_cosmos_controller_interaction,
|
||||
htc_facial_tracking,
|
||||
htc_vive_focus3_controller_interaction,
|
||||
htc_hand_interaction,
|
||||
htc_vive_wrist_tracker_interaction,
|
||||
htc_passthrough,
|
||||
htc_foveation,
|
||||
huawei_controller_interaction,
|
||||
#[cfg(target_os = "android")]
|
||||
khr_android_thread_settings,
|
||||
#[cfg(target_os = "android")]
|
||||
khr_android_surface_swapchain,
|
||||
khr_composition_layer_cube,
|
||||
#[cfg(target_os = "android")]
|
||||
khr_android_create_instance,
|
||||
khr_composition_layer_depth,
|
||||
khr_vulkan_swapchain_format_list,
|
||||
khr_composition_layer_cylinder,
|
||||
khr_composition_layer_equirect,
|
||||
khr_opengl_enable,
|
||||
khr_opengl_es_enable,
|
||||
khr_vulkan_enable,
|
||||
#[cfg(windows)]
|
||||
khr_d3d11_enable,
|
||||
#[cfg(windows)]
|
||||
khr_d3d12_enable,
|
||||
khr_visibility_mask,
|
||||
khr_composition_layer_color_scale_bias,
|
||||
#[cfg(windows)]
|
||||
khr_win32_convert_performance_counter_time,
|
||||
khr_convert_timespec_time,
|
||||
khr_loader_init,
|
||||
#[cfg(target_os = "android")]
|
||||
khr_loader_init_android,
|
||||
khr_vulkan_enable2,
|
||||
khr_composition_layer_equirect2,
|
||||
khr_binding_modification,
|
||||
khr_swapchain_usage_input_attachment_bit,
|
||||
meta_foveation_eye_tracked,
|
||||
meta_local_dimming,
|
||||
meta_passthrough_preferences,
|
||||
meta_virtual_keyboard,
|
||||
meta_vulkan_swapchain_create_info,
|
||||
meta_performance_metrics,
|
||||
meta_headset_id,
|
||||
meta_passthrough_color_lut,
|
||||
ml_ml2_controller_interaction,
|
||||
ml_frame_end_info,
|
||||
ml_global_dimmer,
|
||||
ml_compat,
|
||||
ml_user_calibration,
|
||||
mnd_headless,
|
||||
mnd_swapchain_usage_input_attachment_bit,
|
||||
msft_unbounded_reference_space,
|
||||
msft_spatial_anchor,
|
||||
msft_spatial_graph_bridge,
|
||||
msft_hand_interaction,
|
||||
msft_hand_tracking_mesh,
|
||||
msft_secondary_view_configuration,
|
||||
msft_first_person_observer,
|
||||
msft_controller_model,
|
||||
#[cfg(windows)]
|
||||
msft_perception_anchor_interop,
|
||||
#[cfg(windows)]
|
||||
msft_holographic_window_attachment,
|
||||
msft_composition_layer_reprojection,
|
||||
msft_spatial_anchor_persistence,
|
||||
#[cfg(target_os = "android")]
|
||||
oculus_android_session_state_enable,
|
||||
oculus_audio_device_guid,
|
||||
oculus_external_camera,
|
||||
oppo_controller_interaction,
|
||||
qcom_tracking_optimization_settings,
|
||||
ultraleap_hand_tracking_forearm,
|
||||
valve_analog_threshold,
|
||||
varjo_quad_views,
|
||||
varjo_foveated_rendering,
|
||||
varjo_composition_layer_depth_test,
|
||||
varjo_environment_depth_estimation,
|
||||
varjo_marker_tracking,
|
||||
varjo_view_offset,
|
||||
yvr_controller_interaction,
|
||||
}
|
||||
)*
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
impl_ext!(bitor, bitand, unavailable_exts);
|
||||
196
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
196
crates/bevy_openxr/src/openxr/features/handtracking.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_xr::hands::{LeftHand, RightHand, XrHandBoneEntities, HAND_JOINT_COUNT};
|
||||
use bevy_mod_xr::session::{XrCreateSession, XrDestroySession, XrTrackingRoot};
|
||||
use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrReferenceSpace};
|
||||
use bevy_mod_xr::{
|
||||
hands::{HandBone, HandBoneRadius},
|
||||
session::session_running,
|
||||
};
|
||||
use openxr::SpaceLocationFlags;
|
||||
|
||||
use crate::init::create_xr_session;
|
||||
use crate::resources::OxrFrameState;
|
||||
use crate::resources::Pipelined;
|
||||
use crate::session::OxrSession;
|
||||
|
||||
pub struct HandTrackingPlugin {
|
||||
default_hands: bool,
|
||||
}
|
||||
impl Default for HandTrackingPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default_hands: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for HandTrackingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PreUpdate, locate_hands.run_if(session_running));
|
||||
if self.default_hands {
|
||||
app.add_systems(XrDestroySession, clean_up_default_hands)
|
||||
.add_systems(
|
||||
XrCreateSession,
|
||||
(spawn_default_hands, apply_deferred)
|
||||
.chain()
|
||||
.after(create_xr_session),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_hand_bones<T: Bundle + Clone>(
|
||||
cmds: &mut Commands,
|
||||
bundle: T,
|
||||
) -> [Entity; HAND_JOINT_COUNT] {
|
||||
let mut bones: [Entity; HAND_JOINT_COUNT] = [Entity::PLACEHOLDER; HAND_JOINT_COUNT];
|
||||
// screw you clippy, i don't see a better way to init this array
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for bone in HandBone::get_all_bones().into_iter() {
|
||||
bones[bone as usize] = cmds
|
||||
.spawn((SpatialBundle::default(), bone, HandBoneRadius(0.0)))
|
||||
.insert(bundle.clone())
|
||||
.id();
|
||||
}
|
||||
bones
|
||||
}
|
||||
|
||||
fn spawn_default_hands(
|
||||
mut cmds: Commands,
|
||||
session: Res<OxrSession>,
|
||||
root: Query<Entity, With<XrTrackingRoot>>,
|
||||
) {
|
||||
debug!("spawning default hands");
|
||||
let Ok(root) = root.get_single() else {
|
||||
error!("unable to get tracking root, skipping hand creation");
|
||||
return;
|
||||
};
|
||||
let tracker_left = match session.create_hand_tracker(openxr::HandEXT::LEFT) {
|
||||
Ok(t) => t,
|
||||
Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => {
|
||||
warn!("Handtracking Extension not loaded, Unable to create Handtracker!");
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Error while creating Handtracker: {}", err.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let tracker_right = match session.create_hand_tracker(openxr::HandEXT::RIGHT) {
|
||||
Ok(t) => t,
|
||||
Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => {
|
||||
warn!("Handtracking Extension not loaded, Unable to create Handtracker!");
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Error while creating Handtracker: {}", err.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
let left_bones = spawn_hand_bones(&mut cmds, (DefaultHandBone, LeftHand));
|
||||
let right_bones = spawn_hand_bones(&mut cmds, (DefaultHandBone, RightHand));
|
||||
cmds.entity(root).push_children(&left_bones);
|
||||
cmds.entity(root).push_children(&right_bones);
|
||||
cmds.spawn((
|
||||
DefaultHandTracker,
|
||||
OxrHandTracker(tracker_left),
|
||||
XrHandBoneEntities(left_bones),
|
||||
LeftHand,
|
||||
));
|
||||
cmds.spawn((
|
||||
DefaultHandTracker,
|
||||
OxrHandTracker(tracker_right),
|
||||
XrHandBoneEntities(right_bones),
|
||||
RightHand,
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
struct DefaultHandTracker;
|
||||
#[derive(Component, Clone, Copy)]
|
||||
struct DefaultHandBone;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn clean_up_default_hands(
|
||||
mut cmds: Commands,
|
||||
query: Query<Entity, Or<(With<DefaultHandTracker>, With<DefaultHandBone>)>>,
|
||||
) {
|
||||
for e in &query {
|
||||
debug!("removing default hand entity");
|
||||
cmds.entity(e).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut, Component)]
|
||||
pub struct OxrHandTracker(pub openxr::HandTracker);
|
||||
|
||||
fn locate_hands(
|
||||
default_ref_space: Res<XrPrimaryReferenceSpace>,
|
||||
frame_state: Res<OxrFrameState>,
|
||||
tracker_query: Query<(
|
||||
&OxrHandTracker,
|
||||
Option<&XrReferenceSpace>,
|
||||
&XrHandBoneEntities,
|
||||
)>,
|
||||
session: Res<OxrSession>,
|
||||
mut bone_query: Query<(&HandBone, &mut HandBoneRadius, &mut Transform)>,
|
||||
pipelined: Option<Res<Pipelined>>,
|
||||
) {
|
||||
for (tracker, ref_space, hand_entities) in &tracker_query {
|
||||
let ref_space = ref_space.map(|v| &v.0).unwrap_or(&default_ref_space.0);
|
||||
// relate_hand_joints also provides velocities
|
||||
let joints = match session.locate_hand_joints(
|
||||
tracker,
|
||||
ref_space,
|
||||
if pipelined.is_some() {
|
||||
openxr::Time::from_nanos(
|
||||
frame_state.predicted_display_time.as_nanos()
|
||||
+ frame_state.predicted_display_period.as_nanos(),
|
||||
)
|
||||
} else {
|
||||
frame_state.predicted_display_time
|
||||
},
|
||||
) {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => continue,
|
||||
Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => {
|
||||
error!("HandTracking Extension not loaded");
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Error while locating hand joints: {}", err.to_string());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let bone_entities = match bone_query.get_many_mut(hand_entities.0) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
warn!("unable to get entities, {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
for (bone, mut bone_radius, mut transform) in bone_entities {
|
||||
let joint = joints[*bone as usize];
|
||||
**bone_radius = joint.radius;
|
||||
|
||||
if joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::POSITION_VALID)
|
||||
{
|
||||
transform.translation.x = joint.pose.position.x;
|
||||
transform.translation.y = joint.pose.position.y;
|
||||
transform.translation.z = joint.pose.position.z;
|
||||
}
|
||||
|
||||
if joint
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::ORIENTATION_VALID)
|
||||
{
|
||||
transform.rotation.x = joint.pose.orientation.x;
|
||||
transform.rotation.y = joint.pose.orientation.y;
|
||||
transform.rotation.z = joint.pose.orientation.z;
|
||||
transform.rotation.w = joint.pose.orientation.w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
crates/bevy_openxr/src/openxr/features/mod.rs
Normal file
4
crates/bevy_openxr/src/openxr/features/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod handtracking;
|
||||
#[cfg(feature = "passthrough")]
|
||||
pub mod passthrough;
|
||||
pub mod overlay;
|
||||
96
crates/bevy_openxr/src/openxr/features/overlay.rs
Normal file
96
crates/bevy_openxr/src/openxr/features/overlay.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{mem, ptr};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_xr::session::session_available;
|
||||
use openxr::sys;
|
||||
|
||||
use crate::{
|
||||
next_chain::{OxrNextChainStructBase, OxrNextChainStructProvider},
|
||||
openxr::exts::OxrEnabledExtensions,
|
||||
session::{OxrSessionCreateNextChain, OxrSessionCreateNextProvider},
|
||||
};
|
||||
|
||||
pub struct OxrOverlayPlugin;
|
||||
|
||||
impl Plugin for OxrOverlayPlugin {
|
||||
fn build(&self, app: &mut bevy::prelude::App) {
|
||||
app.add_event::<OxrOverlaySessionEvent>();
|
||||
app.init_resource::<OxrOverlaySettings>();
|
||||
app.add_systems(First, add_overlay_info_to_chain.run_if(session_available));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct OxrOverlaySettings {
|
||||
pub session_layer_placement: u32,
|
||||
pub flags: openxr::OverlaySessionCreateFlagsEXTX,
|
||||
}
|
||||
|
||||
impl Default for OxrOverlaySettings {
|
||||
fn default() -> Self {
|
||||
OxrOverlaySettings {
|
||||
session_layer_placement: 0,
|
||||
flags: openxr::OverlaySessionCreateFlagsEXTX::EMPTY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_overlay_info_to_chain(
|
||||
mut chain: NonSendMut<OxrSessionCreateNextChain>,
|
||||
exts: Res<OxrEnabledExtensions>,
|
||||
settings: Res<OxrOverlaySettings>,
|
||||
) {
|
||||
if exts.other.contains(&"XR_EXTX_overlay\0".to_string()) {
|
||||
chain.push(OxrSessionCreateInfoOverlay::new(
|
||||
settings.flags,
|
||||
settings.session_layer_placement,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Copy, Debug)]
|
||||
pub enum OxrOverlaySessionEvent {
|
||||
MainSessionVisibilityChanged {
|
||||
visible: bool,
|
||||
flags: openxr::OverlayMainSessionFlagsEXTX,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct OxrSessionCreateInfoOverlay {
|
||||
inner: sys::SessionCreateInfoOverlayEXTX,
|
||||
}
|
||||
impl OxrSessionCreateInfoOverlay {
|
||||
pub const fn new(
|
||||
flags: openxr::OverlaySessionCreateFlagsEXTX,
|
||||
session_layers_placement: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: sys::SessionCreateInfoOverlayEXTX {
|
||||
ty: sys::SessionCreateInfoOverlayEXTX::TYPE,
|
||||
next: ptr::null(),
|
||||
create_flags: flags,
|
||||
session_layers_placement,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Default for OxrSessionCreateInfoOverlay {
|
||||
fn default() -> Self {
|
||||
Self::new(openxr::OverlaySessionCreateFlagsEXTX::EMPTY, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl OxrNextChainStructProvider for OxrSessionCreateInfoOverlay {
|
||||
fn header(&self) -> &OxrNextChainStructBase {
|
||||
unsafe { mem::transmute(&self.inner) }
|
||||
}
|
||||
|
||||
fn set_next(&mut self, next: &OxrNextChainStructBase) {
|
||||
self.inner.next = next as *const _ as *const _;
|
||||
}
|
||||
fn clear_next(&mut self) {
|
||||
self.inner.next = ptr::null();
|
||||
}
|
||||
}
|
||||
|
||||
impl OxrSessionCreateNextProvider for OxrSessionCreateInfoOverlay {}
|
||||
119
crates/bevy_openxr/src/openxr/features/passthrough.rs
Normal file
119
crates/bevy_openxr/src/openxr/features/passthrough.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::Render;
|
||||
use bevy::render::RenderApp;
|
||||
use bevy::render::RenderSet;
|
||||
use openxr::sys::SystemPassthroughProperties2FB;
|
||||
use openxr::PassthroughCapabilityFlagsFB;
|
||||
|
||||
use crate::layer_builder::PassthroughLayer;
|
||||
use crate::resources::*;
|
||||
use crate::session::OxrSession;
|
||||
use crate::types::*;
|
||||
|
||||
pub struct OxrPassthroughPlugin;
|
||||
|
||||
impl Plugin for OxrPassthroughPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let resources = app
|
||||
.world
|
||||
.get_resource::<OxrInstance>()
|
||||
.and_then(|instance| {
|
||||
app.world
|
||||
.get_resource::<OxrSystemId>()
|
||||
.map(|system_id| (instance, system_id))
|
||||
});
|
||||
if resources.is_some_and(|(instance, system)| {
|
||||
supports_passthrough(instance, *system).is_ok_and(|s| s)
|
||||
}) {
|
||||
app.sub_app_mut(RenderApp).add_systems(
|
||||
Render,
|
||||
insert_passthrough
|
||||
.in_set(RenderSet::PrepareAssets)
|
||||
.run_if(resource_added::<OxrSession>),
|
||||
);
|
||||
} else {
|
||||
error!("Passthrough is not supported with this runtime")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_passthrough(world: &mut World) {
|
||||
let session = world.resource::<OxrSession>();
|
||||
|
||||
let (passthrough, passthrough_layer) = create_passthrough(
|
||||
session,
|
||||
openxr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION,
|
||||
openxr::PassthroughLayerPurposeFB::RECONSTRUCTION,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
world
|
||||
.resource_mut::<OxrRenderLayers>()
|
||||
.insert(0, Box::new(PassthroughLayer));
|
||||
world.insert_resource(passthrough);
|
||||
world.insert_resource(passthrough_layer);
|
||||
}
|
||||
|
||||
pub fn resume_passthrough(
|
||||
passthrough: Res<OxrPassthrough>,
|
||||
passthrough_layer: Res<OxrPassthroughLayer>,
|
||||
) {
|
||||
passthrough.start().unwrap();
|
||||
passthrough_layer.resume().unwrap();
|
||||
}
|
||||
|
||||
pub fn pause_passthrough(
|
||||
passthrough: Res<OxrPassthrough>,
|
||||
passthrough_layer: Res<OxrPassthroughLayer>,
|
||||
) {
|
||||
passthrough_layer.pause().unwrap();
|
||||
passthrough.pause().unwrap();
|
||||
}
|
||||
|
||||
pub fn create_passthrough(
|
||||
session: &OxrSession,
|
||||
flags: openxr::PassthroughFlagsFB,
|
||||
purpose: openxr::PassthroughLayerPurposeFB,
|
||||
) -> Result<(OxrPassthrough, OxrPassthroughLayer)> {
|
||||
let passthrough = session.create_passthrough(flags)?;
|
||||
|
||||
let passthrough_layer = session.create_passthrough_layer(&passthrough, purpose)?;
|
||||
|
||||
Ok((passthrough, passthrough_layer))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn supports_passthrough(instance: &OxrInstance, system: OxrSystemId) -> Result<bool> {
|
||||
if instance.exts().fb_passthrough.is_none() {
|
||||
return Ok(false);
|
||||
}
|
||||
unsafe {
|
||||
let mut hand = openxr::sys::SystemPassthroughProperties2FB {
|
||||
ty: SystemPassthroughProperties2FB::TYPE,
|
||||
next: std::ptr::null(),
|
||||
capabilities: PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY,
|
||||
};
|
||||
let mut p = openxr::sys::SystemProperties::out(&mut hand as *mut _ as _);
|
||||
cvt((instance.fp().get_system_properties)(
|
||||
instance.as_raw(),
|
||||
system.0,
|
||||
p.as_mut_ptr(),
|
||||
))?;
|
||||
bevy::log::info!(
|
||||
"From supports_passthrough: Passthrough capabilities: {:?}",
|
||||
hand.capabilities
|
||||
);
|
||||
Ok(
|
||||
(hand.capabilities & PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY)
|
||||
== PassthroughCapabilityFlagsFB::PASSTHROUGH_CAPABILITY,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(x: openxr::sys::Result) -> openxr::Result<openxr::sys::Result> {
|
||||
if x.into_raw() >= 0 {
|
||||
Ok(x)
|
||||
} else {
|
||||
Err(x)
|
||||
}
|
||||
}
|
||||
202
crates/bevy_openxr/src/openxr/graphics.rs
Normal file
202
crates/bevy_openxr/src/openxr/graphics.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
mod d3d12;
|
||||
#[cfg(feature = "vulkan")]
|
||||
pub mod vulkan;
|
||||
|
||||
use std::any::TypeId;
|
||||
|
||||
use bevy::math::UVec2;
|
||||
use openxr::{FrameStream, FrameWaiter, Session};
|
||||
|
||||
use crate::{session::OxrSessionCreateNextChain, types::{AppInfo, OxrExtensions, Result, WgpuGraphics}};
|
||||
|
||||
/// This is an extension trait to the [`Graphics`](openxr::Graphics) trait and is how the graphics API should be interacted with.
|
||||
pub unsafe trait GraphicsExt: openxr::Graphics {
|
||||
/// Wrap the graphics specific type into the [GraphicsWrap] enum
|
||||
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T>;
|
||||
/// Returns all of the required openxr extensions to use this graphics API.
|
||||
fn required_exts() -> OxrExtensions;
|
||||
/// Convert from wgpu format to the graphics format
|
||||
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format>;
|
||||
/// Convert from the graphics format to wgpu format
|
||||
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat>;
|
||||
/// Convert an API specific swapchain image to a [`Texture`](wgpu::Texture).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The `image` argument must be a valid handle.
|
||||
unsafe fn to_wgpu_img(
|
||||
image: Self::SwapchainImage,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
resolution: UVec2,
|
||||
) -> Result<wgpu::Texture>;
|
||||
/// Initialize graphics for this backend and return a [`WgpuGraphics`] for bevy and an API specific [Self::SessionCreateInfo] for openxr
|
||||
fn init_graphics(
|
||||
app_info: &AppInfo,
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)>;
|
||||
unsafe fn create_session(
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
info: &Self::SessionCreateInfo,
|
||||
session_create_info_chain: &mut OxrSessionCreateNextChain,
|
||||
) -> openxr::Result<(Session<Self>, FrameWaiter, FrameStream<Self>)>;
|
||||
}
|
||||
|
||||
/// A type that can be used in [`GraphicsWrap`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// pub struct XrSession(GraphicsWrap<XrSession>);
|
||||
///
|
||||
/// impl GraphicsType for XrSession {
|
||||
/// type Inner<G: GraphicsExt> = openxr::Session<G>;
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// In this example, `GraphicsWrap<XrSession>` is now an enum with variants for each graphics API. The enum can be matched to get the graphics specific inner type.
|
||||
pub trait GraphicsType {
|
||||
type Inner<G: GraphicsExt>;
|
||||
}
|
||||
|
||||
impl GraphicsType for () {
|
||||
type Inner<G: GraphicsExt> = ();
|
||||
}
|
||||
|
||||
/// This is a special variant of [GraphicsWrap] using the unit struct as the inner type. This is to simply represent a graphics backend without storing data.
|
||||
pub type GraphicsBackend = GraphicsWrap<()>;
|
||||
|
||||
impl GraphicsBackend {
|
||||
const ALL: &'static [Self] = &[
|
||||
#[cfg(feature = "vulkan")]
|
||||
Self::Vulkan(()),
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
Self::D3D12(()),
|
||||
];
|
||||
|
||||
pub fn available_backends(exts: &OxrExtensions) -> Vec<Self> {
|
||||
Self::ALL
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|backend| backend.is_available(exts))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn is_available(&self, exts: &OxrExtensions) -> bool {
|
||||
self.required_exts().is_available(exts)
|
||||
}
|
||||
|
||||
pub fn required_exts(&self) -> OxrExtensions {
|
||||
graphics_match!(
|
||||
self;
|
||||
_ => Api::required_exts()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is for creating agnostic objects for OpenXR graphics API specific structs.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum GraphicsWrap<T: GraphicsType> {
|
||||
#[cfg(feature = "vulkan")]
|
||||
Vulkan(T::Inner<openxr::Vulkan>),
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
D3D12(T::Inner<openxr::D3D12>),
|
||||
}
|
||||
|
||||
impl<T: GraphicsType> GraphicsWrap<T> {
|
||||
/// Returns the name of the graphics api this struct is using.
|
||||
pub fn graphics_name(&self) -> &'static str {
|
||||
graphics_match!(
|
||||
self;
|
||||
_ => std::any::type_name::<Api>()
|
||||
)
|
||||
}
|
||||
|
||||
fn graphics_type(&self) -> TypeId {
|
||||
graphics_match!(
|
||||
self;
|
||||
_ => TypeId::of::<Api>()
|
||||
)
|
||||
}
|
||||
|
||||
/// Checks if this struct is using the wanted graphics api.
|
||||
pub fn using_graphics<G: GraphicsExt + 'static>(&self) -> bool {
|
||||
self.graphics_type() == TypeId::of::<G>()
|
||||
}
|
||||
|
||||
/// Checks if the two values are both using the same graphics backend
|
||||
pub fn using_graphics_of_val<V: GraphicsType>(&self, other: &GraphicsWrap<V>) -> bool {
|
||||
self.graphics_type() == other.graphics_type()
|
||||
}
|
||||
}
|
||||
|
||||
/// This macro can be used to quickly run the same code for every variant of [GraphicsWrap].
|
||||
/// The first argument should be an expression that returns either a reference or owned value of [GraphicsWrap].
|
||||
/// The second argument should be a match arm with the pattern for the inner type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// pub struct OxrFrameStream(GraphicsWrap<OxrFrameStream>);
|
||||
///
|
||||
/// impl GraphicsType for OxrFrameStream {
|
||||
/// // Here is the inner type
|
||||
/// type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
|
||||
/// }
|
||||
///
|
||||
/// fn begin(frame_stream: &mut OxrFrameStream) {
|
||||
/// graphics_match! {
|
||||
/// // get the inner 'GraphicsWrap' struct
|
||||
/// &mut frame_stream.0;
|
||||
/// // now we can handle every match arm with a single arm
|
||||
/// // important: the first pattern here represents the inner type of `GraphicsWrap`
|
||||
/// // it is already destructured for you.
|
||||
/// stream => stream.begin();
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, if you want the type that implements `GraphicsExt` in the scope of the match body, you can access that type through the type alias `Api`.
|
||||
macro_rules! graphics_match {
|
||||
(
|
||||
$field:expr;
|
||||
$var:pat => $expr:expr $(=> $($return:tt)*)?
|
||||
) => {
|
||||
match $field {
|
||||
#[cfg(feature = "vulkan")]
|
||||
$crate::graphics::GraphicsWrap::Vulkan($var) => {
|
||||
#[allow(unused)]
|
||||
type Api = openxr::Vulkan;
|
||||
graphics_match!(@arm_impl Vulkan; $expr $(=> $($return)*)?)
|
||||
},
|
||||
#[cfg(all(feature = "d3d12", windows))]
|
||||
$crate::graphics::GraphicsWrap::D3D12($var) => {
|
||||
#[allow(unused)]
|
||||
type Api = openxr::D3D12;
|
||||
graphics_match!(@arm_impl D3D12; $expr $(=> $($return)*)?)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
@arm_impl
|
||||
$variant:ident;
|
||||
$expr:expr => $wrap_ty:ty
|
||||
) => {
|
||||
$crate::graphics::GraphicsWrap::<$wrap_ty>::$variant($expr)
|
||||
};
|
||||
|
||||
(
|
||||
@arm_impl
|
||||
$variant:ident;
|
||||
$expr:expr
|
||||
) => {
|
||||
$expr
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use graphics_match;
|
||||
393
crates/bevy_openxr/src/openxr/graphics/d3d12.rs
Normal file
393
crates/bevy_openxr/src/openxr/graphics/d3d12.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
use bevy::log::error;
|
||||
use openxr::sys;
|
||||
use wgpu_hal::{Adapter, Instance};
|
||||
use winapi::shared::dxgiformat::DXGI_FORMAT;
|
||||
use winapi::um::d3d12 as winapi_d3d12;
|
||||
|
||||
use super::{GraphicsExt, GraphicsType, GraphicsWrap};
|
||||
use crate::error::OxrError;
|
||||
use crate::session::OxrSessionCreateNextChain;
|
||||
use crate::types::{AppInfo, OxrExtensions, Result, WgpuGraphics};
|
||||
|
||||
unsafe impl GraphicsExt for openxr::D3D12 {
|
||||
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T> {
|
||||
GraphicsWrap::D3D12(item)
|
||||
}
|
||||
|
||||
fn required_exts() -> OxrExtensions {
|
||||
let mut extensions = openxr::ExtensionSet::default();
|
||||
extensions.khr_d3d12_enable = true;
|
||||
extensions.into()
|
||||
}
|
||||
|
||||
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> {
|
||||
wgpu_to_d3d12(format)
|
||||
}
|
||||
|
||||
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
|
||||
d3d12_to_wgpu(format)
|
||||
}
|
||||
|
||||
unsafe fn to_wgpu_img(
|
||||
image: Self::SwapchainImage,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
resolution: bevy::prelude::UVec2,
|
||||
) -> Result<wgpu::Texture> {
|
||||
let wgpu_hal_texture = <wgpu_hal::dx12::Api as wgpu_hal::Api>::Device::texture_from_raw(
|
||||
d3d12::ComPtr::from_raw(image as *mut _),
|
||||
format,
|
||||
wgpu::TextureDimension::D2,
|
||||
wgpu::Extent3d {
|
||||
width: resolution.x,
|
||||
height: resolution.y,
|
||||
depth_or_array_layers: 2,
|
||||
},
|
||||
1,
|
||||
1,
|
||||
);
|
||||
let texture = device.create_texture_from_hal::<wgpu_hal::dx12::Api>(
|
||||
wgpu_hal_texture,
|
||||
&wgpu::TextureDescriptor {
|
||||
label: Some("VR Swapchain"),
|
||||
size: wgpu::Extent3d {
|
||||
width: resolution.x,
|
||||
height: resolution.y,
|
||||
depth_or_array_layers: 2,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
},
|
||||
);
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
fn init_graphics(
|
||||
app_info: &AppInfo,
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)> {
|
||||
let reqs = instance.graphics_requirements::<openxr::D3D12>(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
|
||||
})
|
||||
.ok_or(OxrError::InitError(
|
||||
crate::error::InitError::FailedToFindD3D12Adapter,
|
||||
))?;
|
||||
|
||||
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_exposed_adapter.capabilities.limits.clone();
|
||||
|
||||
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) {
|
||||
error!(
|
||||
"OpenXR runtime requires D3D12 feature level >= {}",
|
||||
reqs.min_feature_level
|
||||
);
|
||||
return Err(OxrError::FailedGraphicsRequirements);
|
||||
}
|
||||
|
||||
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((
|
||||
WgpuGraphics(
|
||||
wgpu_device,
|
||||
wgpu_queue,
|
||||
wgpu_adapter.get_info(),
|
||||
wgpu_adapter,
|
||||
wgpu_instance,
|
||||
),
|
||||
Self::SessionCreateInfo {
|
||||
device: raw_device.cast(),
|
||||
queue: raw_queue.cast(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
unsafe fn create_session(
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
info: &Self::SessionCreateInfo,
|
||||
session_create_info_chain: &mut OxrSessionCreateNextChain,
|
||||
) -> openxr::Result<(
|
||||
openxr::Session<Self>,
|
||||
openxr::FrameWaiter,
|
||||
openxr::FrameStream<Self>,
|
||||
)> {
|
||||
let binding = sys::GraphicsBindingD3D12KHR {
|
||||
ty: sys::GraphicsBindingD3D12KHR::TYPE,
|
||||
next: session_create_info_chain.chain_pointer(),
|
||||
device: info.device,
|
||||
queue: info.queue,
|
||||
};
|
||||
let info = sys::SessionCreateInfo {
|
||||
ty: sys::SessionCreateInfo::TYPE,
|
||||
next: &binding as *const _ as *const _,
|
||||
create_flags: Default::default(),
|
||||
system_id: system_id,
|
||||
};
|
||||
let mut out = sys::Session::NULL;
|
||||
cvt((instance.fp().create_session)(
|
||||
instance.as_raw(),
|
||||
&info,
|
||||
&mut out,
|
||||
))?;
|
||||
Ok(openxr::Session::from_raw(
|
||||
instance.clone(),
|
||||
out,
|
||||
Box::new(()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(x: sys::Result) -> openxr::Result<sys::Result> {
|
||||
if x.into_raw() >= 0 {
|
||||
Ok(x)
|
||||
} else {
|
||||
Err(x)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 d3d12_to_wgpu(format: DXGI_FORMAT) -> Option<wgpu::TextureFormat> {
|
||||
use wgpu::TextureFormat as Tf;
|
||||
use winapi::shared::dxgiformat::*;
|
||||
|
||||
Some(match format {
|
||||
DXGI_FORMAT_R8_UNORM => Tf::R8Unorm,
|
||||
DXGI_FORMAT_R8_SNORM => Tf::R8Snorm,
|
||||
DXGI_FORMAT_R8_UINT => Tf::R8Uint,
|
||||
DXGI_FORMAT_R8_SINT => Tf::R8Sint,
|
||||
DXGI_FORMAT_R16_UINT => Tf::R16Uint,
|
||||
DXGI_FORMAT_R16_SINT => Tf::R16Sint,
|
||||
DXGI_FORMAT_R16_UNORM => Tf::R16Unorm,
|
||||
DXGI_FORMAT_R16_SNORM => Tf::R16Snorm,
|
||||
DXGI_FORMAT_R16_FLOAT => Tf::R16Float,
|
||||
DXGI_FORMAT_R8G8_UNORM => Tf::Rg8Unorm,
|
||||
DXGI_FORMAT_R8G8_SNORM => Tf::Rg8Snorm,
|
||||
DXGI_FORMAT_R8G8_UINT => Tf::Rg8Uint,
|
||||
DXGI_FORMAT_R8G8_SINT => Tf::Rg8Sint,
|
||||
DXGI_FORMAT_R16G16_UNORM => Tf::Rg16Unorm,
|
||||
DXGI_FORMAT_R16G16_SNORM => Tf::Rg16Snorm,
|
||||
DXGI_FORMAT_R32_UINT => Tf::R32Uint,
|
||||
DXGI_FORMAT_R32_SINT => Tf::R32Sint,
|
||||
DXGI_FORMAT_R32_FLOAT => Tf::R32Float,
|
||||
DXGI_FORMAT_R16G16_UINT => Tf::Rg16Uint,
|
||||
DXGI_FORMAT_R16G16_SINT => Tf::Rg16Sint,
|
||||
DXGI_FORMAT_R16G16_FLOAT => Tf::Rg16Float,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM => Tf::Rgba8Unorm,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => Tf::Rgba8UnormSrgb,
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => Tf::Bgra8UnormSrgb,
|
||||
DXGI_FORMAT_R8G8B8A8_SNORM => Tf::Rgba8Snorm,
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM => Tf::Bgra8Unorm,
|
||||
DXGI_FORMAT_R8G8B8A8_UINT => Tf::Rgba8Uint,
|
||||
DXGI_FORMAT_R8G8B8A8_SINT => Tf::Rgba8Sint,
|
||||
DXGI_FORMAT_R9G9B9E5_SHAREDEXP => Tf::Rgb9e5Ufloat,
|
||||
DXGI_FORMAT_R10G10B10A2_UINT => Tf::Rgb10a2Uint,
|
||||
DXGI_FORMAT_R10G10B10A2_UNORM => Tf::Rgb10a2Unorm,
|
||||
DXGI_FORMAT_R11G11B10_FLOAT => Tf::Rg11b10Float,
|
||||
DXGI_FORMAT_R32G32_UINT => Tf::Rg32Uint,
|
||||
DXGI_FORMAT_R32G32_SINT => Tf::Rg32Sint,
|
||||
DXGI_FORMAT_R32G32_FLOAT => Tf::Rg32Float,
|
||||
DXGI_FORMAT_R16G16B16A16_UINT => Tf::Rgba16Uint,
|
||||
DXGI_FORMAT_R16G16B16A16_SINT => Tf::Rgba16Sint,
|
||||
DXGI_FORMAT_R16G16B16A16_UNORM => Tf::Rgba16Unorm,
|
||||
DXGI_FORMAT_R16G16B16A16_SNORM => Tf::Rgba16Snorm,
|
||||
DXGI_FORMAT_R16G16B16A16_FLOAT => Tf::Rgba16Float,
|
||||
DXGI_FORMAT_R32G32B32A32_UINT => Tf::Rgba32Uint,
|
||||
DXGI_FORMAT_R32G32B32A32_SINT => Tf::Rgba32Sint,
|
||||
DXGI_FORMAT_R32G32B32A32_FLOAT => Tf::Rgba32Float,
|
||||
DXGI_FORMAT_D24_UNORM_S8_UINT => Tf::Stencil8,
|
||||
DXGI_FORMAT_D16_UNORM => Tf::Depth16Unorm,
|
||||
DXGI_FORMAT_D32_FLOAT => Tf::Depth32Float,
|
||||
DXGI_FORMAT_D32_FLOAT_S8X24_UINT => Tf::Depth32FloatStencil8,
|
||||
DXGI_FORMAT_NV12 => Tf::NV12,
|
||||
DXGI_FORMAT_BC1_UNORM => Tf::Bc1RgbaUnorm,
|
||||
DXGI_FORMAT_BC1_UNORM_SRGB => Tf::Bc1RgbaUnormSrgb,
|
||||
DXGI_FORMAT_BC2_UNORM => Tf::Bc2RgbaUnorm,
|
||||
DXGI_FORMAT_BC2_UNORM_SRGB => Tf::Bc2RgbaUnormSrgb,
|
||||
DXGI_FORMAT_BC3_UNORM => Tf::Bc3RgbaUnorm,
|
||||
DXGI_FORMAT_BC3_UNORM_SRGB => Tf::Bc3RgbaUnormSrgb,
|
||||
DXGI_FORMAT_BC4_UNORM => Tf::Bc4RUnorm,
|
||||
DXGI_FORMAT_BC4_SNORM => Tf::Bc4RSnorm,
|
||||
DXGI_FORMAT_BC5_UNORM => Tf::Bc5RgUnorm,
|
||||
DXGI_FORMAT_BC5_SNORM => Tf::Bc5RgSnorm,
|
||||
DXGI_FORMAT_BC6H_UF16 => Tf::Bc6hRgbUfloat,
|
||||
DXGI_FORMAT_BC6H_SF16 => Tf::Bc6hRgbFloat,
|
||||
DXGI_FORMAT_BC7_UNORM => Tf::Bc7RgbaUnorm,
|
||||
DXGI_FORMAT_BC7_UNORM_SRGB => Tf::Bc7RgbaUnormSrgb,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
727
crates/bevy_openxr/src/openxr/graphics/vulkan.rs
Normal file
727
crates/bevy_openxr/src/openxr/graphics/vulkan.rs
Normal file
@@ -0,0 +1,727 @@
|
||||
use std::ffi::{c_void, CString};
|
||||
|
||||
use ash::vk::Handle;
|
||||
use bevy::log::error;
|
||||
use bevy::math::UVec2;
|
||||
use openxr::{sys, Version};
|
||||
use wgpu_hal::api::Vulkan;
|
||||
use wgpu_hal::Api;
|
||||
|
||||
use super::{GraphicsExt, GraphicsType, GraphicsWrap};
|
||||
use crate::error::OxrError;
|
||||
use crate::session::OxrSessionCreateNextChain;
|
||||
use crate::types::{AppInfo, OxrExtensions, Result, WgpuGraphics};
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
const VK_TARGET_VERSION: Version = Version::new(1, 2, 0);
|
||||
#[cfg(target_os = "android")]
|
||||
const VK_TARGET_VERSION: Version = Version::new(1, 1, 0);
|
||||
|
||||
const VK_TARGET_VERSION_ASH: u32 = ash::vk::make_api_version(
|
||||
0,
|
||||
VK_TARGET_VERSION.major() as u32,
|
||||
VK_TARGET_VERSION.minor() as u32,
|
||||
VK_TARGET_VERSION.patch() as u32,
|
||||
);
|
||||
|
||||
unsafe impl GraphicsExt for openxr::Vulkan {
|
||||
fn wrap<T: GraphicsType>(item: T::Inner<Self>) -> GraphicsWrap<T> {
|
||||
GraphicsWrap::Vulkan(item)
|
||||
}
|
||||
|
||||
fn required_exts() -> OxrExtensions {
|
||||
let mut extensions = openxr::ExtensionSet::default();
|
||||
extensions.khr_vulkan_enable2 = true;
|
||||
extensions.into()
|
||||
}
|
||||
|
||||
fn from_wgpu_format(format: wgpu::TextureFormat) -> Option<Self::Format> {
|
||||
wgpu_to_vulkan(format).map(|f| f.as_raw() as _)
|
||||
}
|
||||
|
||||
fn into_wgpu_format(format: Self::Format) -> Option<wgpu::TextureFormat> {
|
||||
vulkan_to_wgpu(ash::vk::Format::from_raw(format as _))
|
||||
}
|
||||
|
||||
unsafe fn to_wgpu_img(
|
||||
color_image: Self::SwapchainImage,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
resolution: UVec2,
|
||||
) -> Result<wgpu::Texture> {
|
||||
let color_image = ash::vk::Image::from_raw(color_image);
|
||||
let wgpu_hal_texture = unsafe {
|
||||
<wgpu_hal::vulkan::Api as wgpu_hal::Api>::Device::texture_from_raw(
|
||||
color_image,
|
||||
&wgpu_hal::TextureDescriptor {
|
||||
label: Some("VR Swapchain"),
|
||||
size: wgpu::Extent3d {
|
||||
width: resolution.x,
|
||||
height: resolution.y,
|
||||
depth_or_array_layers: 2,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: 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 {
|
||||
device.create_texture_from_hal::<wgpu_hal::vulkan::Api>(
|
||||
wgpu_hal_texture,
|
||||
&wgpu::TextureDescriptor {
|
||||
label: Some("VR Swapchain"),
|
||||
size: wgpu::Extent3d {
|
||||
width: resolution.x,
|
||||
height: resolution.y,
|
||||
depth_or_array_layers: 2,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
},
|
||||
)
|
||||
};
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
fn init_graphics(
|
||||
app_info: &AppInfo,
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
) -> Result<(WgpuGraphics, Self::SessionCreateInfo)> {
|
||||
let reqs = instance.graphics_requirements::<openxr::Vulkan>(system_id)?;
|
||||
if VK_TARGET_VERSION < reqs.min_api_version_supported
|
||||
|| VK_TARGET_VERSION.major() > reqs.max_api_version_supported.major()
|
||||
{
|
||||
error!(
|
||||
"OpenXR runtime requires Vulkan version > {}, < {}.0.0",
|
||||
reqs.min_api_version_supported,
|
||||
reqs.max_api_version_supported.major() + 1
|
||||
);
|
||||
return Err(OxrError::FailedGraphicsRequirements);
|
||||
};
|
||||
let vk_entry = unsafe { ash::Entry::load() }?;
|
||||
let flags = wgpu::InstanceFlags::empty();
|
||||
let extensions =
|
||||
<Vulkan as Api>::Instance::desired_extensions(&vk_entry, VK_TARGET_VERSION_ASH, flags)?;
|
||||
let device_extensions = vec![
|
||||
ash::extensions::khr::Swapchain::name(),
|
||||
ash::extensions::khr::DrawIndirectCount::name(),
|
||||
#[cfg(target_os = "android")]
|
||||
ash::extensions::khr::TimelineSemaphore::name(),
|
||||
ash::vk::KhrImagelessFramebufferFn::name(),
|
||||
ash::vk::KhrImageFormatListFn::name(),
|
||||
];
|
||||
|
||||
let vk_instance = unsafe {
|
||||
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
|
||||
|
||||
let app_name = CString::new(app_info.name.clone().into_owned())?;
|
||||
let vk_app_info = ash::vk::ApplicationInfo::builder()
|
||||
.application_name(&app_name)
|
||||
.application_version(1)
|
||||
.engine_name(&app_name)
|
||||
.engine_version(1)
|
||||
.api_version(VK_TARGET_VERSION_ASH);
|
||||
|
||||
let vk_instance = instance
|
||||
.create_vulkan_instance(
|
||||
system_id,
|
||||
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
|
||||
&ash::vk::InstanceCreateInfo::builder()
|
||||
.application_info(&vk_app_info)
|
||||
.enabled_extension_names(&extensions_cchar) as *const _
|
||||
as *const _,
|
||||
)?
|
||||
.map_err(ash::vk::Result::from_raw)?;
|
||||
|
||||
ash::Instance::load(
|
||||
vk_entry.static_fn(),
|
||||
ash::vk::Instance::from_raw(vk_instance as _),
|
||||
)
|
||||
};
|
||||
|
||||
let vk_instance_ptr = vk_instance.handle().as_raw() as *const c_void;
|
||||
|
||||
let vk_physical_device = ash::vk::PhysicalDevice::from_raw(unsafe {
|
||||
instance.vulkan_graphics_device(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_ASH {
|
||||
unsafe { vk_instance.destroy_instance(None) }
|
||||
error!(
|
||||
"Vulkan physical device doesn't support version {}.{}.{}",
|
||||
VK_TARGET_VERSION.major(),
|
||||
VK_TARGET_VERSION.minor(),
|
||||
VK_TARGET_VERSION.patch()
|
||||
);
|
||||
return Err(OxrError::FailedGraphicsRequirements);
|
||||
}
|
||||
|
||||
let wgpu_vk_instance = unsafe {
|
||||
<Vulkan as Api>::Instance::from_raw(
|
||||
vk_entry.clone(),
|
||||
vk_instance.clone(),
|
||||
VK_TARGET_VERSION_ASH,
|
||||
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 Some(wgpu_exposed_adapter) = wgpu_vk_instance.expose_adapter(vk_physical_device) else {
|
||||
error!("WGPU failed to provide an adapter");
|
||||
return Err(OxrError::FailedGraphicsRequirements);
|
||||
};
|
||||
|
||||
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 = ash::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(
|
||||
ash::vk::DeviceCreateInfo::builder()
|
||||
.queue_create_infos(&family_infos)
|
||||
.push_next(&mut ash::vk::PhysicalDeviceMultiviewFeatures {
|
||||
multiview: ash::vk::TRUE,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.enabled_extension_names(&extensions_cchar)
|
||||
.build();
|
||||
let vk_device = unsafe {
|
||||
let vk_device = instance
|
||||
.create_vulkan_device(
|
||||
system_id,
|
||||
std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr),
|
||||
vk_physical_device.as_raw() as _,
|
||||
&info as *const _ as *const _,
|
||||
)?
|
||||
.map_err(ash::vk::Result::from_raw)?;
|
||||
|
||||
ash::Device::load(
|
||||
vk_instance.fp_v1_0(),
|
||||
ash::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((
|
||||
WgpuGraphics(
|
||||
wgpu_device,
|
||||
wgpu_queue,
|
||||
wgpu_adapter.get_info(),
|
||||
wgpu_adapter,
|
||||
wgpu_instance,
|
||||
),
|
||||
openxr::vulkan::SessionCreateInfo {
|
||||
instance: vk_instance_ptr,
|
||||
physical_device: vk_physical_device_ptr,
|
||||
device: vk_device_ptr,
|
||||
queue_family_index,
|
||||
queue_index: 0,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
unsafe fn create_session(
|
||||
instance: &openxr::Instance,
|
||||
system_id: openxr::SystemId,
|
||||
info: &Self::SessionCreateInfo,
|
||||
session_create_info_chain: &mut OxrSessionCreateNextChain,
|
||||
) -> openxr::Result<(
|
||||
openxr::Session<Self>,
|
||||
openxr::FrameWaiter,
|
||||
openxr::FrameStream<Self>,
|
||||
)> {
|
||||
let next_ptr = session_create_info_chain.chain_pointer();
|
||||
let binding = sys::GraphicsBindingVulkanKHR {
|
||||
ty: sys::GraphicsBindingVulkanKHR::TYPE,
|
||||
next: next_ptr,
|
||||
instance: info.instance,
|
||||
physical_device: info.physical_device,
|
||||
device: info.device,
|
||||
queue_family_index: info.queue_family_index,
|
||||
queue_index: info.queue_index,
|
||||
};
|
||||
let info = sys::SessionCreateInfo {
|
||||
ty: sys::SessionCreateInfo::TYPE,
|
||||
next: &binding as *const _ as *const _,
|
||||
create_flags: Default::default(),
|
||||
system_id: system_id,
|
||||
};
|
||||
let mut out = sys::Session::NULL;
|
||||
cvt((instance.fp().create_session)(
|
||||
instance.as_raw(),
|
||||
&info,
|
||||
&mut out,
|
||||
))?;
|
||||
Ok(openxr::Session::from_raw(
|
||||
instance.clone(),
|
||||
out,
|
||||
Box::new(()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(x: sys::Result) -> openxr::Result<sys::Result> {
|
||||
if x.into_raw() >= 0 {
|
||||
Ok(x)
|
||||
} else {
|
||||
Err(x)
|
||||
}
|
||||
}
|
||||
|
||||
fn vulkan_to_wgpu(format: ash::vk::Format) -> Option<wgpu::TextureFormat> {
|
||||
use ash::vk::Format as F;
|
||||
use wgpu::TextureFormat as Tf;
|
||||
use wgpu::{AstcBlock, AstcChannel};
|
||||
Some(match format {
|
||||
F::R8_UNORM => Tf::R8Unorm,
|
||||
F::R8_SNORM => Tf::R8Snorm,
|
||||
F::R8_UINT => Tf::R8Uint,
|
||||
F::R8_SINT => Tf::R8Sint,
|
||||
F::R16_UINT => Tf::R16Uint,
|
||||
F::R16_SINT => Tf::R16Sint,
|
||||
F::R16_UNORM => Tf::R16Unorm,
|
||||
F::R16_SNORM => Tf::R16Snorm,
|
||||
F::R16_SFLOAT => Tf::R16Float,
|
||||
F::R8G8_UNORM => Tf::Rg8Unorm,
|
||||
F::R8G8_SNORM => Tf::Rg8Snorm,
|
||||
F::R8G8_UINT => Tf::Rg8Uint,
|
||||
F::R8G8_SINT => Tf::Rg8Sint,
|
||||
F::R16G16_UNORM => Tf::Rg16Unorm,
|
||||
F::R16G16_SNORM => Tf::Rg16Snorm,
|
||||
F::R32_UINT => Tf::R32Uint,
|
||||
F::R32_SINT => Tf::R32Sint,
|
||||
F::R32_SFLOAT => Tf::R32Float,
|
||||
F::R16G16_UINT => Tf::Rg16Uint,
|
||||
F::R16G16_SINT => Tf::Rg16Sint,
|
||||
F::R16G16_SFLOAT => Tf::Rg16Float,
|
||||
F::R8G8B8A8_UNORM => Tf::Rgba8Unorm,
|
||||
F::R8G8B8A8_SRGB => Tf::Rgba8UnormSrgb,
|
||||
F::B8G8R8A8_SRGB => Tf::Bgra8UnormSrgb,
|
||||
F::R8G8B8A8_SNORM => Tf::Rgba8Snorm,
|
||||
F::B8G8R8A8_UNORM => Tf::Bgra8Unorm,
|
||||
F::R8G8B8A8_UINT => Tf::Rgba8Uint,
|
||||
F::R8G8B8A8_SINT => Tf::Rgba8Sint,
|
||||
F::A2B10G10R10_UINT_PACK32 => Tf::Rgb10a2Uint,
|
||||
F::A2B10G10R10_UNORM_PACK32 => Tf::Rgb10a2Unorm,
|
||||
F::B10G11R11_UFLOAT_PACK32 => Tf::Rg11b10Float,
|
||||
F::R32G32_UINT => Tf::Rg32Uint,
|
||||
F::R32G32_SINT => Tf::Rg32Sint,
|
||||
F::R32G32_SFLOAT => Tf::Rg32Float,
|
||||
F::R16G16B16A16_UINT => Tf::Rgba16Uint,
|
||||
F::R16G16B16A16_SINT => Tf::Rgba16Sint,
|
||||
F::R16G16B16A16_UNORM => Tf::Rgba16Unorm,
|
||||
F::R16G16B16A16_SNORM => Tf::Rgba16Snorm,
|
||||
F::R16G16B16A16_SFLOAT => Tf::Rgba16Float,
|
||||
F::R32G32B32A32_UINT => Tf::Rgba32Uint,
|
||||
F::R32G32B32A32_SINT => Tf::Rgba32Sint,
|
||||
F::R32G32B32A32_SFLOAT => Tf::Rgba32Float,
|
||||
F::D32_SFLOAT => Tf::Depth32Float,
|
||||
F::D32_SFLOAT_S8_UINT => Tf::Depth32FloatStencil8,
|
||||
F::D16_UNORM => Tf::Depth16Unorm,
|
||||
F::G8_B8R8_2PLANE_420_UNORM => Tf::NV12,
|
||||
F::E5B9G9R9_UFLOAT_PACK32 => Tf::Rgb9e5Ufloat,
|
||||
F::BC1_RGBA_UNORM_BLOCK => Tf::Bc1RgbaUnorm,
|
||||
F::BC1_RGBA_SRGB_BLOCK => Tf::Bc1RgbaUnormSrgb,
|
||||
F::BC2_UNORM_BLOCK => Tf::Bc2RgbaUnorm,
|
||||
F::BC2_SRGB_BLOCK => Tf::Bc2RgbaUnormSrgb,
|
||||
F::BC3_UNORM_BLOCK => Tf::Bc3RgbaUnorm,
|
||||
F::BC3_SRGB_BLOCK => Tf::Bc3RgbaUnormSrgb,
|
||||
F::BC4_UNORM_BLOCK => Tf::Bc4RUnorm,
|
||||
F::BC4_SNORM_BLOCK => Tf::Bc4RSnorm,
|
||||
F::BC5_UNORM_BLOCK => Tf::Bc5RgUnorm,
|
||||
F::BC5_SNORM_BLOCK => Tf::Bc5RgSnorm,
|
||||
F::BC6H_UFLOAT_BLOCK => Tf::Bc6hRgbUfloat,
|
||||
F::BC6H_SFLOAT_BLOCK => Tf::Bc6hRgbFloat,
|
||||
F::BC7_UNORM_BLOCK => Tf::Bc7RgbaUnorm,
|
||||
F::BC7_SRGB_BLOCK => Tf::Bc7RgbaUnormSrgb,
|
||||
F::ETC2_R8G8B8_UNORM_BLOCK => Tf::Etc2Rgb8Unorm,
|
||||
F::ETC2_R8G8B8_SRGB_BLOCK => Tf::Etc2Rgb8UnormSrgb,
|
||||
F::ETC2_R8G8B8A1_UNORM_BLOCK => Tf::Etc2Rgb8A1Unorm,
|
||||
F::ETC2_R8G8B8A1_SRGB_BLOCK => Tf::Etc2Rgb8A1UnormSrgb,
|
||||
F::ETC2_R8G8B8A8_UNORM_BLOCK => Tf::Etc2Rgba8Unorm,
|
||||
F::ETC2_R8G8B8A8_SRGB_BLOCK => Tf::Etc2Rgba8UnormSrgb,
|
||||
F::EAC_R11_UNORM_BLOCK => Tf::EacR11Unorm,
|
||||
F::EAC_R11_SNORM_BLOCK => Tf::EacR11Snorm,
|
||||
F::EAC_R11G11_UNORM_BLOCK => Tf::EacRg11Unorm,
|
||||
F::EAC_R11G11_SNORM_BLOCK => Tf::EacRg11Snorm,
|
||||
F::ASTC_4X4_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B4x4,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_5X4_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B5x4,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_5X5_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B5x5,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_6X5_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B6x5,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_6X6_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B6x6,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_8X5_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x5,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_8X6_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x6,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_8X8_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x8,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_10X5_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x5,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_10X6_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x6,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_10X8_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x8,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_10X10_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x10,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_12X10_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B12x10,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_12X12_UNORM_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B12x12,
|
||||
channel: AstcChannel::Unorm,
|
||||
},
|
||||
F::ASTC_4X4_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B4x4,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_5X4_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B5x4,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_5X5_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B5x5,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_6X5_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B6x5,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_6X6_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B6x6,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_8X5_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x5,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_8X6_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x6,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_8X8_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B8x8,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_10X5_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x5,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_10X6_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x6,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_10X8_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x8,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_10X10_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B10x10,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_12X10_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B12x10,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_12X12_SRGB_BLOCK => Tf::Astc {
|
||||
block: AstcBlock::B12x12,
|
||||
channel: AstcChannel::UnormSrgb,
|
||||
},
|
||||
F::ASTC_4X4_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B4x4,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_5X4_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B5x4,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_5X5_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B5x5,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_6X5_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B6x5,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_6X6_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B6x6,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_8X5_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B8x5,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_8X6_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B8x6,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_8X8_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B8x8,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_10X5_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B10x5,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_10X6_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B10x6,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_10X8_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B10x8,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_10X10_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B10x10,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_12X10_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B12x10,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
F::ASTC_12X12_SFLOAT_BLOCK_EXT => Tf::Astc {
|
||||
block: AstcBlock::B12x12,
|
||||
channel: AstcChannel::Hdr,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn wgpu_to_vulkan(format: wgpu::TextureFormat) -> Option<ash::vk::Format> {
|
||||
// Copied with minor modification from:
|
||||
// https://github.com/gfx-rs/wgpu/blob/a7defb723f856d946d6d220e9897d20dbb7b8f61/wgpu-hal/src/vulkan/conv.rs#L5-L151
|
||||
// license: MIT OR Apache-2.0
|
||||
use ash::vk::Format as F;
|
||||
use wgpu::TextureFormat as Tf;
|
||||
use wgpu::{AstcBlock, AstcChannel};
|
||||
Some(match format {
|
||||
Tf::R8Unorm => F::R8_UNORM,
|
||||
Tf::R8Snorm => F::R8_SNORM,
|
||||
Tf::R8Uint => F::R8_UINT,
|
||||
Tf::R8Sint => F::R8_SINT,
|
||||
Tf::R16Uint => F::R16_UINT,
|
||||
Tf::R16Sint => F::R16_SINT,
|
||||
Tf::R16Unorm => F::R16_UNORM,
|
||||
Tf::R16Snorm => F::R16_SNORM,
|
||||
Tf::R16Float => F::R16_SFLOAT,
|
||||
Tf::Rg8Unorm => F::R8G8_UNORM,
|
||||
Tf::Rg8Snorm => F::R8G8_SNORM,
|
||||
Tf::Rg8Uint => F::R8G8_UINT,
|
||||
Tf::Rg8Sint => F::R8G8_SINT,
|
||||
Tf::Rg16Unorm => F::R16G16_UNORM,
|
||||
Tf::Rg16Snorm => F::R16G16_SNORM,
|
||||
Tf::R32Uint => F::R32_UINT,
|
||||
Tf::R32Sint => F::R32_SINT,
|
||||
Tf::R32Float => F::R32_SFLOAT,
|
||||
Tf::Rg16Uint => F::R16G16_UINT,
|
||||
Tf::Rg16Sint => F::R16G16_SINT,
|
||||
Tf::Rg16Float => F::R16G16_SFLOAT,
|
||||
Tf::Rgba8Unorm => F::R8G8B8A8_UNORM,
|
||||
Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB,
|
||||
Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB,
|
||||
Tf::Rgba8Snorm => F::R8G8B8A8_SNORM,
|
||||
Tf::Bgra8Unorm => F::B8G8R8A8_UNORM,
|
||||
Tf::Rgba8Uint => F::R8G8B8A8_UINT,
|
||||
Tf::Rgba8Sint => F::R8G8B8A8_SINT,
|
||||
Tf::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 => return None, // 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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
117
crates/bevy_openxr/src/openxr/helper_traits.rs
Normal file
117
crates/bevy_openxr/src/openxr/helper_traits.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_xr::types::XrPose;
|
||||
|
||||
pub trait ToPosef {
|
||||
fn to_posef(&self) -> openxr::Posef;
|
||||
}
|
||||
pub trait ToTransform {
|
||||
fn to_transform(&self) -> Transform;
|
||||
}
|
||||
pub trait ToXrPose {
|
||||
fn to_xr_pose(&self) -> XrPose;
|
||||
}
|
||||
pub trait ToQuaternionf {
|
||||
fn to_quaternionf(&self) -> openxr::Quaternionf;
|
||||
}
|
||||
pub trait ToQuat {
|
||||
fn to_quat(&self) -> Quat;
|
||||
}
|
||||
pub trait ToVector3f {
|
||||
fn to_vector3f(&self) -> openxr::Vector3f;
|
||||
}
|
||||
pub trait ToVec3 {
|
||||
fn to_vec3(&self) -> Vec3;
|
||||
}
|
||||
pub trait ToVector2f {
|
||||
fn to_vector2f(&self) -> openxr::Vector2f;
|
||||
}
|
||||
pub trait ToVec2 {
|
||||
fn to_vec2(&self) -> Vec2;
|
||||
}
|
||||
impl ToPosef for Transform {
|
||||
fn to_posef(&self) -> openxr::Posef {
|
||||
openxr::Posef {
|
||||
orientation: self.rotation.to_quaternionf(),
|
||||
position: self.translation.to_vector3f(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToTransform for openxr::Posef {
|
||||
fn to_transform(&self) -> Transform {
|
||||
Transform::from_translation(self.position.to_vec3())
|
||||
.with_rotation(self.orientation.to_quat())
|
||||
}
|
||||
}
|
||||
impl ToXrPose for openxr::Posef {
|
||||
fn to_xr_pose(&self) -> XrPose {
|
||||
XrPose {
|
||||
translation: self.position.to_vec3(),
|
||||
rotation: self.orientation.to_quat(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToPosef for XrPose {
|
||||
fn to_posef(&self) -> openxr::Posef {
|
||||
openxr::Posef {
|
||||
orientation: self.rotation.to_quaternionf(),
|
||||
position: self.translation.to_vector3f(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToQuaternionf for Quat {
|
||||
fn to_quaternionf(&self) -> openxr::Quaternionf {
|
||||
openxr::Quaternionf {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
w: self.w,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToQuat for openxr::Quaternionf {
|
||||
fn to_quat(&self) -> Quat {
|
||||
let mut quat = Quat::from_xyzw(self.x, self.y, self.z, self.w);
|
||||
if quat.length() == 0.0 {
|
||||
quat = Quat::IDENTITY;
|
||||
}
|
||||
if !quat.is_normalized() {
|
||||
quat = quat.normalize();
|
||||
}
|
||||
quat
|
||||
}
|
||||
}
|
||||
impl ToVector3f for Vec3 {
|
||||
fn to_vector3f(&self) -> openxr::Vector3f {
|
||||
openxr::Vector3f {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToVec3 for openxr::Vector3f {
|
||||
fn to_vec3(&self) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToVector2f for Vec2 {
|
||||
fn to_vector2f(&self) -> openxr::Vector2f {
|
||||
openxr::Vector2f {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToVec2 for openxr::Vector2f {
|
||||
fn to_vec2(&self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
526
crates/bevy_openxr/src/openxr/init.rs
Normal file
526
crates/bevy_openxr/src/openxr/init.rs
Normal file
@@ -0,0 +1,526 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_resource::ExtractResourcePlugin;
|
||||
use bevy::render::renderer::RenderAdapter;
|
||||
use bevy::render::renderer::RenderAdapterInfo;
|
||||
use bevy::render::renderer::RenderDevice;
|
||||
use bevy::render::renderer::RenderInstance;
|
||||
use bevy::render::renderer::RenderQueue;
|
||||
use bevy::render::settings::RenderCreation;
|
||||
use bevy::render::MainWorld;
|
||||
use bevy::render::RenderApp;
|
||||
use bevy::render::RenderPlugin;
|
||||
use bevy::winit::UpdateMode;
|
||||
use bevy::winit::WinitSettings;
|
||||
use bevy_mod_xr::session::*;
|
||||
|
||||
use crate::error::OxrError;
|
||||
use crate::features::overlay::OxrOverlaySessionEvent;
|
||||
use crate::graphics::*;
|
||||
use crate::resources::*;
|
||||
use crate::session::OxrSession;
|
||||
use crate::session::OxrSessionCreateNextChain;
|
||||
use crate::types::*;
|
||||
|
||||
use super::exts::OxrEnabledExtensions;
|
||||
|
||||
pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool {
|
||||
started.is_some_and(|started| started.0)
|
||||
}
|
||||
|
||||
pub fn should_run_frame_loop(
|
||||
started: Option<Res<OxrSessionStarted>>,
|
||||
state: Option<Res<XrState>>,
|
||||
) -> bool {
|
||||
started.is_some_and(|started| started.0)
|
||||
&& state.is_some_and(|state| *state != XrState::Stopping)
|
||||
}
|
||||
|
||||
pub fn should_render(frame_state: Option<Res<OxrFrameState>>) -> bool {
|
||||
frame_state.is_some_and(|frame_state| frame_state.should_render)
|
||||
}
|
||||
|
||||
pub struct OxrInitPlugin {
|
||||
/// Information about the app this is being used to build.
|
||||
pub app_info: AppInfo,
|
||||
/// Extensions wanted for this session.
|
||||
// TODO!() This should be changed to take a simpler list of features wanted that this crate supports. i.e. hand tracking
|
||||
pub exts: OxrExtensions,
|
||||
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
|
||||
pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
|
||||
/// List of backends the openxr session can use. If [None], pick the first available backend.
|
||||
pub backends: Option<Vec<GraphicsBackend>>,
|
||||
/// List of formats the openxr session can use. If [None], pick the first available format
|
||||
pub formats: Option<Vec<wgpu::TextureFormat>>,
|
||||
/// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution.
|
||||
pub resolutions: Option<Vec<UVec2>>,
|
||||
/// Passed into the render plugin when added to the app.
|
||||
pub synchronous_pipeline_compilation: bool,
|
||||
}
|
||||
impl Default for OxrInitPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
app_info: default(),
|
||||
exts: {
|
||||
let mut exts = OxrExtensions::default();
|
||||
exts.enable_fb_passthrough();
|
||||
exts.enable_hand_tracking();
|
||||
exts
|
||||
},
|
||||
blend_modes: default(),
|
||||
backends: default(),
|
||||
formats: Some(vec![wgpu::TextureFormat::Rgba8UnormSrgb]),
|
||||
resolutions: default(),
|
||||
synchronous_pipeline_compilation: default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for OxrInitPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
match self.init_xr() {
|
||||
Ok((
|
||||
instance,
|
||||
system_id,
|
||||
WgpuGraphics(device, queue, adapter_info, adapter, wgpu_instance),
|
||||
session_create_info,
|
||||
enabled_exts,
|
||||
)) => {
|
||||
app.insert_resource(enabled_exts)
|
||||
.add_plugins((
|
||||
RenderPlugin {
|
||||
render_creation: RenderCreation::manual(
|
||||
device.into(),
|
||||
RenderQueue(queue.into()),
|
||||
RenderAdapterInfo(adapter_info),
|
||||
RenderAdapter(adapter.into()),
|
||||
RenderInstance(wgpu_instance.into()),
|
||||
),
|
||||
synchronous_pipeline_compilation: self.synchronous_pipeline_compilation,
|
||||
},
|
||||
ExtractResourcePlugin::<OxrSessionStarted>::default(),
|
||||
))
|
||||
.add_systems(
|
||||
XrFirst,
|
||||
poll_events
|
||||
.before(XrHandleEvents)
|
||||
.run_if(not(state_equals(XrState::Unavailable))),
|
||||
)
|
||||
.add_systems(XrCreateSession, create_xr_session)
|
||||
.add_systems(XrDestroySession, destroy_xr_session)
|
||||
.add_systems(XrBeginSession, begin_xr_session)
|
||||
.add_systems(XrEndSession, end_xr_session)
|
||||
.add_systems(XrRequestExit, request_exit_xr_session)
|
||||
.insert_resource(instance.clone())
|
||||
.insert_resource(system_id)
|
||||
.insert_resource(XrState::Available)
|
||||
.insert_resource(WinitSettings {
|
||||
focused_mode: UpdateMode::Continuous,
|
||||
unfocused_mode: UpdateMode::Continuous,
|
||||
})
|
||||
.insert_resource(OxrSessionStarted(false))
|
||||
.insert_non_send_resource(session_create_info)
|
||||
.init_non_send_resource::<OxrSessionCreateNextChain>();
|
||||
|
||||
app.world
|
||||
.spawn((TransformBundle::default(), XrTrackingRoot));
|
||||
|
||||
app.world
|
||||
.resource_mut::<Events<XrStateChanged>>()
|
||||
.send(XrStateChanged(XrState::Available));
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app
|
||||
.add_systems(ExtractSchedule, transfer_xr_resources)
|
||||
.insert_resource(instance)
|
||||
.insert_resource(system_id)
|
||||
.insert_resource(XrState::Available)
|
||||
.insert_resource(OxrSessionStarted(false));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to initialize openxr: {e}");
|
||||
app.add_plugins(RenderPlugin::default())
|
||||
.insert_resource(XrState::Unavailable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
app.sub_app_mut(RenderApp)
|
||||
.add_systems(XrDestroySession, destroy_xr_session);
|
||||
}
|
||||
}
|
||||
|
||||
impl OxrInitPlugin {
|
||||
fn init_xr(
|
||||
&self,
|
||||
) -> Result<(
|
||||
OxrInstance,
|
||||
OxrSystemId,
|
||||
WgpuGraphics,
|
||||
SessionConfigInfo,
|
||||
OxrEnabledExtensions,
|
||||
)> {
|
||||
#[cfg(windows)]
|
||||
let entry = OxrEntry(openxr::Entry::linked());
|
||||
#[cfg(not(windows))]
|
||||
let entry = OxrEntry(unsafe { openxr::Entry::load()? });
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
entry.initialize_android_loader()?;
|
||||
|
||||
let available_exts = entry.enumerate_extensions()?;
|
||||
|
||||
// check available extensions and send a warning for any wanted extensions that aren't available.
|
||||
for ext in available_exts.unavailable_exts(&self.exts) {
|
||||
error!(
|
||||
"Extension \"{ext}\" not available in the current OpenXR runtime. Disabling extension."
|
||||
);
|
||||
}
|
||||
|
||||
let available_backends = GraphicsBackend::available_backends(&available_exts);
|
||||
|
||||
// Backend selection
|
||||
let backend = if let Some(wanted_backends) = &self.backends {
|
||||
let mut backend = None;
|
||||
for wanted_backend in wanted_backends {
|
||||
if available_backends.contains(wanted_backend) {
|
||||
backend = Some(*wanted_backend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
backend
|
||||
} else {
|
||||
available_backends.first().copied()
|
||||
}
|
||||
.ok_or(OxrError::NoAvailableBackend)?;
|
||||
|
||||
let exts = self.exts.clone() & available_exts;
|
||||
|
||||
let instance = entry.create_instance(
|
||||
self.app_info.clone(),
|
||||
exts.clone(),
|
||||
// &["XR_APILAYER_LUNARG_api_dump"],
|
||||
&[],
|
||||
backend,
|
||||
)?;
|
||||
let instance_props = instance.properties()?;
|
||||
|
||||
info!(
|
||||
"Loaded OpenXR runtime: {} {}",
|
||||
instance_props.runtime_name, instance_props.runtime_version
|
||||
);
|
||||
|
||||
let system_id = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?;
|
||||
let system_props = instance.system_properties(system_id)?;
|
||||
|
||||
info!(
|
||||
"Using system: {}",
|
||||
if system_props.system_name.is_empty() {
|
||||
"<unnamed>"
|
||||
} else {
|
||||
&system_props.system_name
|
||||
}
|
||||
);
|
||||
|
||||
let (graphics, graphics_info) = instance.init_graphics(system_id)?;
|
||||
|
||||
let session_create_info = SessionConfigInfo {
|
||||
blend_modes: self.blend_modes.clone(),
|
||||
formats: self.formats.clone(),
|
||||
resolutions: self.resolutions.clone(),
|
||||
graphics_info,
|
||||
};
|
||||
|
||||
Ok((
|
||||
instance,
|
||||
OxrSystemId(system_id),
|
||||
graphics,
|
||||
session_create_info,
|
||||
OxrEnabledExtensions(exts),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls any OpenXR events and handles them accordingly
|
||||
pub fn poll_events(
|
||||
instance: Res<OxrInstance>,
|
||||
mut status: ResMut<XrState>,
|
||||
mut changed_event: EventWriter<XrStateChanged>,
|
||||
mut overlay_writer: Option<ResMut<Events<OxrOverlaySessionEvent>>>,
|
||||
) {
|
||||
let _span = info_span!("xr_poll_events");
|
||||
let mut buffer = Default::default();
|
||||
while let Some(event) = instance
|
||||
.poll_event(&mut buffer)
|
||||
.expect("Failed to poll event")
|
||||
{
|
||||
use openxr::Event::*;
|
||||
match event {
|
||||
SessionStateChanged(state) => {
|
||||
use openxr::SessionState;
|
||||
|
||||
let state = state.state();
|
||||
|
||||
info!("entered XR state {:?}", state);
|
||||
|
||||
let new_status = match state {
|
||||
SessionState::IDLE => XrState::Idle,
|
||||
SessionState::READY => XrState::Ready,
|
||||
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
|
||||
XrState::Running
|
||||
}
|
||||
SessionState::STOPPING => XrState::Stopping,
|
||||
SessionState::EXITING => XrState::Exiting {
|
||||
should_restart: false,
|
||||
},
|
||||
SessionState::LOSS_PENDING => XrState::Exiting {
|
||||
should_restart: true,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
changed_event.send(XrStateChanged(new_status));
|
||||
*status = new_status;
|
||||
}
|
||||
InstanceLossPending(_) => {}
|
||||
EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()),
|
||||
MainSessionVisibilityChangedEXTX(d) => {
|
||||
if let Some(writer) = overlay_writer.as_mut() {
|
||||
writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged {
|
||||
visible: d.visible(),
|
||||
flags: d.flags(),
|
||||
});
|
||||
} else {
|
||||
warn!("Overlay Event Recieved without the OverlayPlugin being added!");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_xr_session(
|
||||
device: &wgpu::Device,
|
||||
instance: &OxrInstance,
|
||||
system_id: openxr::SystemId,
|
||||
chain: &mut OxrSessionCreateNextChain,
|
||||
SessionConfigInfo {
|
||||
blend_modes,
|
||||
formats,
|
||||
resolutions,
|
||||
graphics_info,
|
||||
}: SessionConfigInfo,
|
||||
) -> Result<(
|
||||
OxrSession,
|
||||
OxrFrameWaiter,
|
||||
OxrFrameStream,
|
||||
OxrSwapchain,
|
||||
OxrSwapchainImages,
|
||||
OxrGraphicsInfo,
|
||||
)> {
|
||||
let (session, frame_waiter, frame_stream) =
|
||||
unsafe { instance.create_session(system_id, graphics_info, chain)? };
|
||||
|
||||
// TODO!() support other view configurations
|
||||
let available_view_configurations = instance.enumerate_view_configurations(system_id)?;
|
||||
if !available_view_configurations.contains(&openxr::ViewConfigurationType::PRIMARY_STEREO) {
|
||||
return Err(OxrError::NoAvailableViewConfiguration);
|
||||
}
|
||||
|
||||
let view_configuration_type = openxr::ViewConfigurationType::PRIMARY_STEREO;
|
||||
|
||||
let view_configuration_views =
|
||||
instance.enumerate_view_configuration_views(system_id, view_configuration_type)?;
|
||||
|
||||
let (resolution, _view) = if let Some(resolutions) = &resolutions {
|
||||
let mut preferred = None;
|
||||
for resolution in resolutions {
|
||||
for view_config in view_configuration_views.iter() {
|
||||
if view_config.recommended_image_rect_height == resolution.y
|
||||
&& view_config.recommended_image_rect_width == resolution.x
|
||||
{
|
||||
preferred = Some((*resolution, *view_config));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if preferred.is_none() {
|
||||
for resolution in resolutions {
|
||||
for view_config in view_configuration_views.iter() {
|
||||
if view_config.max_image_rect_height >= resolution.y
|
||||
&& view_config.max_image_rect_width >= resolution.x
|
||||
{
|
||||
preferred = Some((*resolution, *view_config));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preferred
|
||||
} else {
|
||||
view_configuration_views.first().map(|config| {
|
||||
(
|
||||
UVec2::new(
|
||||
config.recommended_image_rect_width,
|
||||
config.recommended_image_rect_height,
|
||||
),
|
||||
*config,
|
||||
)
|
||||
})
|
||||
}
|
||||
.ok_or(OxrError::NoAvailableViewConfiguration)?;
|
||||
|
||||
let available_formats = session.enumerate_swapchain_formats()?;
|
||||
|
||||
let format = if let Some(formats) = &formats {
|
||||
let mut format = None;
|
||||
for wanted_format in formats {
|
||||
if available_formats.contains(wanted_format) {
|
||||
format = Some(*wanted_format);
|
||||
}
|
||||
}
|
||||
format
|
||||
} else {
|
||||
available_formats.first().copied()
|
||||
}
|
||||
.ok_or(OxrError::NoAvailableFormat)?;
|
||||
|
||||
let swapchain = session.create_swapchain(SwapchainCreateInfo {
|
||||
create_flags: SwapchainCreateFlags::EMPTY,
|
||||
usage_flags: SwapchainUsageFlags::COLOR_ATTACHMENT | SwapchainUsageFlags::SAMPLED,
|
||||
format,
|
||||
// TODO() add support for multisampling
|
||||
sample_count: 1,
|
||||
width: resolution.x,
|
||||
height: resolution.y,
|
||||
face_count: 1,
|
||||
array_size: 2,
|
||||
mip_count: 1,
|
||||
})?;
|
||||
|
||||
let images = swapchain.enumerate_images(device, format, resolution)?;
|
||||
|
||||
let available_blend_modes =
|
||||
instance.enumerate_environment_blend_modes(system_id, view_configuration_type)?;
|
||||
|
||||
// blend mode selection
|
||||
let blend_mode = if let Some(wanted_blend_modes) = &blend_modes {
|
||||
let mut blend_mode = None;
|
||||
for wanted_blend_mode in wanted_blend_modes {
|
||||
if available_blend_modes.contains(wanted_blend_mode) {
|
||||
blend_mode = Some(*wanted_blend_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
blend_mode
|
||||
} else {
|
||||
available_blend_modes.first().copied()
|
||||
}
|
||||
.ok_or(OxrError::NoAvailableBackend)?;
|
||||
|
||||
let graphics_info = OxrGraphicsInfo {
|
||||
blend_mode,
|
||||
resolution,
|
||||
format,
|
||||
};
|
||||
|
||||
Ok((
|
||||
session,
|
||||
frame_waiter,
|
||||
frame_stream,
|
||||
swapchain,
|
||||
images,
|
||||
graphics_info,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn create_xr_session(world: &mut World) {
|
||||
let mut chain = world
|
||||
.remove_non_send_resource::<OxrSessionCreateNextChain>()
|
||||
.unwrap();
|
||||
let device = world.resource::<RenderDevice>();
|
||||
let instance = world.resource::<OxrInstance>();
|
||||
let create_info = world.non_send_resource::<SessionConfigInfo>();
|
||||
let system_id = world.resource::<OxrSystemId>();
|
||||
match init_xr_session(
|
||||
device.wgpu_device(),
|
||||
&instance,
|
||||
**system_id,
|
||||
&mut chain,
|
||||
create_info.clone(),
|
||||
) {
|
||||
Ok((session, frame_waiter, frame_stream, swapchain, images, graphics_info)) => {
|
||||
world.insert_resource(session.clone());
|
||||
world.insert_resource(frame_waiter);
|
||||
world.insert_resource(images.clone());
|
||||
world.insert_resource(graphics_info.clone());
|
||||
world.insert_resource(OxrRenderResources {
|
||||
session,
|
||||
frame_stream,
|
||||
swapchain,
|
||||
images,
|
||||
graphics_info,
|
||||
});
|
||||
}
|
||||
Err(e) => error!("Failed to initialize XrSession: {e}"),
|
||||
}
|
||||
world.insert_non_send_resource(chain);
|
||||
}
|
||||
|
||||
pub fn destroy_xr_session(world: &mut World) {
|
||||
world.remove_resource::<OxrSession>();
|
||||
world.remove_resource::<OxrFrameWaiter>();
|
||||
world.remove_resource::<OxrFrameStream>();
|
||||
world.remove_resource::<OxrSwapchain>();
|
||||
world.remove_resource::<OxrSwapchainImages>();
|
||||
world.remove_resource::<OxrGraphicsInfo>();
|
||||
world.insert_resource(XrState::Available);
|
||||
}
|
||||
|
||||
pub fn begin_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
|
||||
let _span = info_span!("xr_begin_session");
|
||||
session
|
||||
.begin(openxr::ViewConfigurationType::PRIMARY_STEREO)
|
||||
.expect("Failed to begin session");
|
||||
session_started.0 = true;
|
||||
}
|
||||
|
||||
pub fn end_xr_session(session: Res<OxrSession>, mut session_started: ResMut<OxrSessionStarted>) {
|
||||
let _span = info_span!("xr_end_session");
|
||||
session.end().expect("Failed to end session");
|
||||
session_started.0 = false;
|
||||
}
|
||||
|
||||
pub fn request_exit_xr_session(session: Res<OxrSession>) {
|
||||
session.request_exit().expect("Failed to request exit");
|
||||
}
|
||||
|
||||
/// This is used solely to transport resources from the main world to the render world.
|
||||
#[derive(Resource)]
|
||||
struct OxrRenderResources {
|
||||
session: OxrSession,
|
||||
frame_stream: OxrFrameStream,
|
||||
swapchain: OxrSwapchain,
|
||||
images: OxrSwapchainImages,
|
||||
graphics_info: OxrGraphicsInfo,
|
||||
}
|
||||
|
||||
/// This system transfers important render resources from the main world to the render world when a session is created.
|
||||
pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld>) {
|
||||
let Some(OxrRenderResources {
|
||||
session,
|
||||
frame_stream,
|
||||
swapchain,
|
||||
images,
|
||||
graphics_info,
|
||||
}) = world.remove_resource()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
commands.insert_resource(session);
|
||||
commands.insert_resource(frame_stream);
|
||||
commands.insert_resource(swapchain);
|
||||
commands.insert_resource(images);
|
||||
commands.insert_resource(graphics_info);
|
||||
}
|
||||
267
crates/bevy_openxr/src/openxr/layer_builder.rs
Normal file
267
crates/bevy_openxr/src/openxr/layer_builder.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
use std::mem;
|
||||
|
||||
use bevy::ecs::world::World;
|
||||
use bevy_mod_xr::spaces::{XrPrimaryReferenceSpace, XrSpace};
|
||||
use openxr::{sys, CompositionLayerFlags, Fovf, Posef, Rect2Di};
|
||||
|
||||
use crate::graphics::graphics_match;
|
||||
use crate::resources::*;
|
||||
use crate::spaces::OxrSpaceExt as _;
|
||||
|
||||
pub trait LayerProvider {
|
||||
fn get<'a>(&'a self, world: &'a World) -> Option<Box<dyn CompositionLayer + '_>>;
|
||||
}
|
||||
|
||||
pub struct ProjectionLayer;
|
||||
|
||||
pub struct PassthroughLayer;
|
||||
|
||||
impl LayerProvider for ProjectionLayer {
|
||||
fn get<'a>(&self, world: &'a World) -> Option<Box<dyn CompositionLayer<'a> + 'a>> {
|
||||
let stage = world.get_resource::<XrPrimaryReferenceSpace>()?;
|
||||
let openxr_views = world.get_resource::<OxrViews>()?;
|
||||
let swapchain = world.get_resource::<OxrSwapchain>()?;
|
||||
let graphics_info = world.get_resource::<OxrGraphicsInfo>()?;
|
||||
let rect = openxr::Rect2Di {
|
||||
offset: openxr::Offset2Di { x: 0, y: 0 },
|
||||
extent: openxr::Extent2Di {
|
||||
width: graphics_info.resolution.x as _,
|
||||
height: graphics_info.resolution.y as _,
|
||||
},
|
||||
};
|
||||
|
||||
if openxr_views.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Box::new(
|
||||
CompositionLayerProjection::new()
|
||||
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
||||
.space(&stage)
|
||||
.views(&[
|
||||
CompositionLayerProjectionView::new()
|
||||
.pose(openxr_views.0[0].pose)
|
||||
.fov(openxr_views.0[0].fov)
|
||||
.sub_image(
|
||||
SwapchainSubImage::new()
|
||||
.swapchain(&swapchain)
|
||||
.image_array_index(0)
|
||||
.image_rect(rect),
|
||||
),
|
||||
CompositionLayerProjectionView::new()
|
||||
.pose(openxr_views.0[1].pose)
|
||||
.fov(openxr_views.0[1].fov)
|
||||
.sub_image(
|
||||
SwapchainSubImage::new()
|
||||
.swapchain(&swapchain)
|
||||
.image_array_index(1)
|
||||
.image_rect(rect),
|
||||
),
|
||||
]),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerProvider for PassthroughLayer {
|
||||
fn get<'a>(&'a self, world: &'a World) -> Option<Box<dyn CompositionLayer + '_>> {
|
||||
Some(Box::new(
|
||||
CompositionLayerPassthrough::new()
|
||||
.layer_handle(world.get_resource::<OxrPassthroughLayer>()?)
|
||||
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SwapchainSubImage<'a> {
|
||||
inner: sys::SwapchainSubImage,
|
||||
swapchain: Option<&'a OxrSwapchain>,
|
||||
}
|
||||
|
||||
impl<'a> SwapchainSubImage<'a> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: sys::SwapchainSubImage {
|
||||
..unsafe { mem::zeroed() }
|
||||
},
|
||||
swapchain: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_raw(self) -> sys::SwapchainSubImage {
|
||||
self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_raw(&self) -> &sys::SwapchainSubImage {
|
||||
&self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn swapchain(mut self, value: &'a OxrSwapchain) -> Self {
|
||||
graphics_match!(
|
||||
&value.0;
|
||||
swap => self.inner.swapchain = swap.as_raw()
|
||||
);
|
||||
self.swapchain = Some(value);
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn image_rect(mut self, value: Rect2Di) -> Self {
|
||||
self.inner.image_rect = value;
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn image_array_index(mut self, value: u32) -> Self {
|
||||
self.inner.image_array_index = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for SwapchainSubImage<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CompositionLayerProjectionView<'a> {
|
||||
inner: sys::CompositionLayerProjectionView,
|
||||
swapchain: Option<&'a OxrSwapchain>,
|
||||
}
|
||||
|
||||
impl<'a> CompositionLayerProjectionView<'a> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: sys::CompositionLayerProjectionView {
|
||||
ty: sys::StructureType::COMPOSITION_LAYER_PROJECTION_VIEW,
|
||||
..unsafe { mem::zeroed() }
|
||||
},
|
||||
swapchain: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_raw(self) -> sys::CompositionLayerProjectionView {
|
||||
self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_raw(&self) -> &sys::CompositionLayerProjectionView {
|
||||
&self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn pose(mut self, value: Posef) -> Self {
|
||||
self.inner.pose = value;
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn fov(mut self, value: Fovf) -> Self {
|
||||
self.inner.fov = value;
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn sub_image(mut self, value: SwapchainSubImage<'a>) -> Self {
|
||||
self.inner.sub_image = value.inner;
|
||||
self.swapchain = value.swapchain;
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'a> Default for CompositionLayerProjectionView<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub unsafe trait CompositionLayer<'a> {
|
||||
fn swapchain(&self) -> Option<&'a OxrSwapchain>;
|
||||
fn header(&self) -> &sys::CompositionLayerBaseHeader;
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct CompositionLayerProjection<'a> {
|
||||
inner: sys::CompositionLayerProjection,
|
||||
swapchain: Option<&'a OxrSwapchain>,
|
||||
views: Vec<sys::CompositionLayerProjectionView>,
|
||||
}
|
||||
impl<'a> CompositionLayerProjection<'a> {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: sys::CompositionLayerProjection {
|
||||
ty: sys::StructureType::COMPOSITION_LAYER_PROJECTION,
|
||||
..unsafe { mem::zeroed() }
|
||||
},
|
||||
swapchain: None,
|
||||
views: Vec::new(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn into_raw(self) -> sys::CompositionLayerProjection {
|
||||
self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_raw(&self) -> &sys::CompositionLayerProjection {
|
||||
&self.inner
|
||||
}
|
||||
#[inline]
|
||||
pub fn layer_flags(mut self, value: CompositionLayerFlags) -> Self {
|
||||
self.inner.layer_flags = value;
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn space(mut self, value: &XrSpace) -> Self {
|
||||
self.inner.space = value.as_raw_openxr_space();
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn views(mut self, value: &[CompositionLayerProjectionView<'a>]) -> Self {
|
||||
self.views = value.iter().map(|view| view.inner).collect();
|
||||
self.inner.views = self.views.as_slice().as_ptr() as *const _ as _;
|
||||
self.inner.view_count = self.views.len() as u32;
|
||||
self
|
||||
}
|
||||
}
|
||||
unsafe impl<'a> CompositionLayer<'a> for CompositionLayerProjection<'a> {
|
||||
fn swapchain(&self) -> Option<&'a OxrSwapchain> {
|
||||
self.swapchain
|
||||
}
|
||||
|
||||
fn header(&self) -> &sys::CompositionLayerBaseHeader {
|
||||
unsafe { mem::transmute(&self.inner) }
|
||||
}
|
||||
}
|
||||
impl<'a> Default for CompositionLayerProjection<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub struct CompositionLayerPassthrough {
|
||||
inner: sys::CompositionLayerPassthroughFB,
|
||||
}
|
||||
impl CompositionLayerPassthrough {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: openxr::sys::CompositionLayerPassthroughFB {
|
||||
ty: openxr::sys::CompositionLayerPassthroughFB::TYPE,
|
||||
..unsafe { mem::zeroed() }
|
||||
},
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn layer_handle(mut self, layer_handle: &OxrPassthroughLayer) -> Self {
|
||||
self.inner.layer_handle = *layer_handle.inner();
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
pub fn layer_flags(mut self, value: CompositionLayerFlags) -> Self {
|
||||
self.inner.flags = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
unsafe impl<'a> CompositionLayer<'a> for CompositionLayerPassthrough {
|
||||
fn swapchain(&self) -> Option<&'a OxrSwapchain> {
|
||||
None
|
||||
}
|
||||
|
||||
fn header(&self) -> &sys::CompositionLayerBaseHeader {
|
||||
unsafe { mem::transmute(&self.inner) }
|
||||
}
|
||||
}
|
||||
70
crates/bevy_openxr/src/openxr/mod.rs
Normal file
70
crates/bevy_openxr/src/openxr/mod.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
// use actions::XrActionPlugin;
|
||||
use bevy::{
|
||||
app::{PluginGroup, PluginGroupBuilder},
|
||||
render::RenderPlugin,
|
||||
utils::default,
|
||||
window::{PresentMode, Window, WindowPlugin},
|
||||
};
|
||||
use bevy_mod_xr::camera::XrCameraPlugin;
|
||||
use bevy_mod_xr::session::XrSessionPlugin;
|
||||
use init::OxrInitPlugin;
|
||||
use render::OxrRenderPlugin;
|
||||
|
||||
use self::{
|
||||
features::{handtracking::HandTrackingPlugin, passthrough::OxrPassthroughPlugin},
|
||||
reference_space::OxrReferenceSpacePlugin,
|
||||
};
|
||||
|
||||
pub mod action_binding;
|
||||
pub mod action_set_attaching;
|
||||
pub mod action_set_syncing;
|
||||
pub mod error;
|
||||
mod exts;
|
||||
pub mod features;
|
||||
pub mod graphics;
|
||||
pub mod helper_traits;
|
||||
pub mod init;
|
||||
pub mod layer_builder;
|
||||
pub mod next_chain;
|
||||
pub mod reference_space;
|
||||
pub mod render;
|
||||
pub mod resources;
|
||||
pub mod session;
|
||||
pub mod spaces;
|
||||
pub mod types;
|
||||
|
||||
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
||||
plugins
|
||||
.build()
|
||||
.disable::<RenderPlugin>()
|
||||
// .disable::<PipelinedRenderingPlugin>()
|
||||
.add_before::<RenderPlugin, _>(XrSessionPlugin { auto_handle: true })
|
||||
.add_before::<RenderPlugin, _>(OxrInitPlugin::default())
|
||||
.add(OxrReferenceSpacePlugin::default())
|
||||
.add(OxrRenderPlugin)
|
||||
.add(OxrPassthroughPlugin)
|
||||
.add(HandTrackingPlugin::default())
|
||||
.add(XrCameraPlugin)
|
||||
.add(action_set_attaching::OxrActionAttachingPlugin)
|
||||
.add(action_binding::OxrActionBindingPlugin)
|
||||
.add(action_set_syncing::OxrActionSyncingPlugin)
|
||||
.add(features::overlay::OxrOverlayPlugin)
|
||||
.add(spaces::OxrSpatialPlugin)
|
||||
.add(spaces::OxrSpacePatchingPlugin)
|
||||
// .add(XrActionPlugin)
|
||||
// we should probably handle the exiting ourselfs so that we can correctly end the
|
||||
// session and instance
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
transparent: true,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
// title: self.app_info.name.clone(),
|
||||
..default()
|
||||
}),
|
||||
// #[cfg(target_os = "android")]
|
||||
// exit_condition: bevy::window::ExitCondition::DontExit,
|
||||
#[cfg(target_os = "android")]
|
||||
close_when_requested: true,
|
||||
..default()
|
||||
})
|
||||
}
|
||||
44
crates/bevy_openxr/src/openxr/next_chain.rs
Normal file
44
crates/bevy_openxr/src/openxr/next_chain.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use openxr::sys;
|
||||
use std::{ffi::c_void, ptr};
|
||||
|
||||
/// An abstraction for the next pointer fields for openxr calls
|
||||
#[derive(Default)]
|
||||
pub struct OxrNextChain {
|
||||
structs: Vec<Box<dyn OxrNextChainStructProvider>>,
|
||||
}
|
||||
|
||||
impl OxrNextChain {
|
||||
pub fn push<T: OxrNextChainStructProvider>(&mut self, info_struct: T) {
|
||||
if let Some(last) = self.structs.last_mut() {
|
||||
let mut info = Box::new(info_struct);
|
||||
info.as_mut().clear_next();
|
||||
last.as_mut().set_next(info.as_ref().header());
|
||||
self.structs.push(info);
|
||||
} else {
|
||||
let mut info_struct = Box::new(info_struct);
|
||||
info_struct.as_mut().clear_next();
|
||||
self.structs.push(info_struct);
|
||||
}
|
||||
}
|
||||
pub fn chain(&self) -> Option<&OxrNextChainStructBase> {
|
||||
self.structs.first().map(|v| v.as_ref().header())
|
||||
}
|
||||
pub fn chain_pointer(&self) -> *const c_void {
|
||||
self.chain()
|
||||
.map(|v| v as *const _ as *const c_void)
|
||||
.unwrap_or(ptr::null())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OxrNextChainStructProvider: 'static {
|
||||
fn header(&self) -> &OxrNextChainStructBase;
|
||||
fn set_next(&mut self, next: &OxrNextChainStructBase);
|
||||
fn clear_next(&mut self);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct OxrNextChainStructBase {
|
||||
pub ty: sys::StructureType,
|
||||
pub next: *const OxrNextChainStructBase,
|
||||
}
|
||||
74
crates/bevy_openxr/src/openxr/reference_space.rs
Normal file
74
crates/bevy_openxr/src/openxr/reference_space.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{extract_resource::ExtractResourcePlugin, RenderApp},
|
||||
};
|
||||
use bevy_mod_xr::{
|
||||
session::{XrCreateSession, XrDestroySession},
|
||||
spaces::{XrPrimaryReferenceSpace, XrReferenceSpace},
|
||||
};
|
||||
|
||||
use crate::{init::create_xr_session, session::OxrSession};
|
||||
|
||||
pub struct OxrReferenceSpacePlugin {
|
||||
pub default_primary_ref_space: openxr::ReferenceSpaceType,
|
||||
}
|
||||
impl Default for OxrReferenceSpacePlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default_primary_ref_space: openxr::ReferenceSpaceType::STAGE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resource specifying what the type should be for [`OxrPrimaryReferenceSpace`]. Set through [`OxrReferenceSpacePlugin`].
|
||||
#[derive(Resource)]
|
||||
struct OxrDefaultPrimaryReferenceSpaceType(openxr::ReferenceSpaceType);
|
||||
|
||||
/// The Default Reference space used for locating things
|
||||
// #[derive(Resource, Deref, ExtrctResource, Clone)]
|
||||
// pub struct OxrPrimaryReferenceSpace(pub Arc<openxr::Space>);
|
||||
|
||||
/// The Reference space used for locating spaces on this entity
|
||||
#[derive(Component)]
|
||||
pub struct OxrReferenceSpace(pub openxr::Space);
|
||||
|
||||
impl Plugin for OxrReferenceSpacePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(ExtractResourcePlugin::<XrPrimaryReferenceSpace>::default())
|
||||
.insert_resource(OxrDefaultPrimaryReferenceSpaceType(
|
||||
self.default_primary_ref_space,
|
||||
))
|
||||
.add_systems(
|
||||
XrCreateSession,
|
||||
set_primary_ref_space.after(create_xr_session),
|
||||
)
|
||||
.add_systems(XrDestroySession, cleanup);
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app.add_systems(XrDestroySession, cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(query: Query<Entity, With<XrReferenceSpace>>, mut cmds: Commands) {
|
||||
cmds.remove_resource::<XrPrimaryReferenceSpace>();
|
||||
for e in &query {
|
||||
cmds.entity(e).remove::<XrReferenceSpace>();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_primary_ref_space(
|
||||
session: Res<OxrSession>,
|
||||
space_type: Res<OxrDefaultPrimaryReferenceSpaceType>,
|
||||
mut cmds: Commands,
|
||||
) {
|
||||
match session.create_reference_space(space_type.0, Transform::IDENTITY) {
|
||||
Ok(space) => {
|
||||
cmds.insert_resource(XrPrimaryReferenceSpace(space));
|
||||
}
|
||||
Err(openxr::sys::Result::ERROR_EXTENSION_NOT_PRESENT) => {
|
||||
error!("Required Extension for Reference Space not loaded");
|
||||
}
|
||||
Err(err) => error!("Error while creating reference space: {}", err.to_string()),
|
||||
};
|
||||
}
|
||||
468
crates/bevy_openxr/src/openxr/render.rs
Normal file
468
crates/bevy_openxr/src/openxr/render.rs
Normal file
@@ -0,0 +1,468 @@
|
||||
use bevy::{
|
||||
ecs::query::QuerySingleError,
|
||||
prelude::*,
|
||||
render::{
|
||||
camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews, RenderTarget},
|
||||
extract_resource::ExtractResourcePlugin,
|
||||
pipelined_rendering::PipelinedRenderingPlugin,
|
||||
view::ExtractedView,
|
||||
Render, RenderApp,
|
||||
},
|
||||
transform::TransformSystem,
|
||||
};
|
||||
use bevy_mod_xr::{
|
||||
camera::{XrCamera, XrCameraBundle, XrProjection},
|
||||
session::{
|
||||
XrDestroySession, XrFirst, XrHandleEvents, XrRenderSet, XrRootTransform, XrTrackingRoot,
|
||||
},
|
||||
spaces::XrPrimaryReferenceSpace,
|
||||
};
|
||||
use openxr::ViewStateFlags;
|
||||
|
||||
use crate::{init::should_run_frame_loop, resources::*};
|
||||
use crate::{layer_builder::ProjectionLayer, session::OxrSession};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub struct OxrRenderBegin;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub struct OxrRenderEnd;
|
||||
|
||||
pub struct OxrRenderPlugin;
|
||||
|
||||
impl Plugin for OxrRenderPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
if app.is_plugin_added::<PipelinedRenderingPlugin>() {
|
||||
app.init_resource::<Pipelined>();
|
||||
|
||||
// if let Some(sub_app) = app.remove_sub_app(RenderExtractApp) {
|
||||
// app.insert_sub_app(RenderExtractApp, SubApp::new(sub_app.app, update_rendering));
|
||||
// }
|
||||
}
|
||||
|
||||
app.add_plugins((
|
||||
ExtractResourcePlugin::<OxrFrameState>::default(),
|
||||
ExtractResourcePlugin::<OxrGraphicsInfo>::default(),
|
||||
ExtractResourcePlugin::<OxrSwapchainImages>::default(),
|
||||
ExtractResourcePlugin::<OxrViews>::default(),
|
||||
))
|
||||
.add_systems(XrDestroySession, clean_views)
|
||||
.add_systems(
|
||||
XrFirst,
|
||||
(
|
||||
wait_frame.run_if(should_run_frame_loop),
|
||||
update_cameras.run_if(should_run_frame_loop),
|
||||
init_views.run_if(resource_added::<OxrSession>),
|
||||
)
|
||||
.chain()
|
||||
.after(XrHandleEvents),
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(locate_views, update_views)
|
||||
.before(TransformSystem::TransformPropagate)
|
||||
.chain()
|
||||
// .run_if(should_render)
|
||||
.run_if(should_run_frame_loop),
|
||||
)
|
||||
.init_resource::<OxrViews>();
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app
|
||||
.add_systems(XrDestroySession, clean_views)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
begin_frame,
|
||||
insert_texture_views,
|
||||
locate_views,
|
||||
update_views_render_world,
|
||||
wait_image,
|
||||
)
|
||||
.chain()
|
||||
.in_set(XrRenderSet::PreRender)
|
||||
.run_if(should_run_frame_loop),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
(release_image, end_frame)
|
||||
.chain()
|
||||
.run_if(should_run_frame_loop)
|
||||
.in_set(XrRenderSet::PostRender),
|
||||
)
|
||||
.insert_resource(OxrRenderLayers(vec![Box::new(ProjectionLayer)]));
|
||||
}
|
||||
}
|
||||
|
||||
// fn update_rendering(app_world: &mut World, _sub_app: &mut App) {
|
||||
// app_world.resource_scope(|world, main_thread_executor: Mut<MainThreadExecutor>| {
|
||||
// world.resource_scope(|world, mut render_channels: Mut<RenderAppChannels>| {
|
||||
// // we use a scope here to run any main thread tasks that the render world still needs to run
|
||||
// // while we wait for the render world to be received.
|
||||
// let mut render_app = ComputeTaskPool::get()
|
||||
// .scope_with_executor(true, Some(&*main_thread_executor.0), |s| {
|
||||
// s.spawn(async { render_channels.recv().await });
|
||||
// })
|
||||
// .pop()
|
||||
// .unwrap();
|
||||
|
||||
// if matches!(world.resource::<XrState>(), XrState::Stopping) {
|
||||
// world.run_schedule(XrEndSession);
|
||||
// }
|
||||
|
||||
// if matches!(world.resource::<XrState>(), XrState::Exiting { .. }) {
|
||||
// world.run_schedule(XrDestroySession);
|
||||
// render_app.app.world.run_schedule(XrDestroySession);
|
||||
// }
|
||||
|
||||
// render_app.extract(world);
|
||||
|
||||
// render_channels.send_blocking(render_app);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
pub const XR_TEXTURE_INDEX: u32 = 3383858418;
|
||||
|
||||
pub fn clean_views(
|
||||
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||
mut commands: Commands,
|
||||
cam_query: Query<(Entity, &XrCamera)>,
|
||||
) {
|
||||
for (e, cam) in &cam_query {
|
||||
manual_texture_views.remove(&ManualTextureViewHandle(XR_TEXTURE_INDEX + cam.0));
|
||||
commands.entity(e).despawn_recursive();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_views(
|
||||
graphics_info: Res<OxrGraphicsInfo>,
|
||||
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||
swapchain_images: Res<OxrSwapchainImages>,
|
||||
root: Query<Entity, With<XrTrackingRoot>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let _span = info_span!("xr_init_views");
|
||||
let temp_tex = swapchain_images.first().unwrap();
|
||||
// this for loop is to easily add support for quad or mono views in the future.
|
||||
for index in 0..2 {
|
||||
info!("{}", graphics_info.resolution);
|
||||
let view_handle =
|
||||
add_texture_view(&mut manual_texture_views, temp_tex, &graphics_info, index);
|
||||
|
||||
let cam = commands
|
||||
.spawn((XrCameraBundle {
|
||||
camera: Camera {
|
||||
target: RenderTarget::TextureView(view_handle),
|
||||
..Default::default()
|
||||
},
|
||||
view: XrCamera(index),
|
||||
..Default::default()
|
||||
},))
|
||||
.id();
|
||||
match root.get_single() {
|
||||
Ok(root) => {
|
||||
commands.entity(root).add_child(cam);
|
||||
}
|
||||
Err(QuerySingleError::NoEntities(_)) => {
|
||||
warn!("No XrTrackingRoot!");
|
||||
}
|
||||
Err(QuerySingleError::MultipleEntities(_)) => {
|
||||
warn!("Multiple XrTrackingRoots! this is not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_frame(mut frame_waiter: ResMut<OxrFrameWaiter>, mut commands: Commands) {
|
||||
let _span = info_span!("xr_wait_frame");
|
||||
let state = frame_waiter.wait().expect("Failed to wait frame");
|
||||
commands.insert_resource(OxrFrameState(state));
|
||||
}
|
||||
|
||||
pub fn update_cameras(
|
||||
frame_state: Res<OxrFrameState>,
|
||||
mut cameras: Query<&mut Camera, With<XrCamera>>,
|
||||
) {
|
||||
if frame_state.is_changed() {
|
||||
for mut camera in &mut cameras {
|
||||
camera.is_active = frame_state.should_render
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn locate_views(
|
||||
session: Res<OxrSession>,
|
||||
ref_space: Res<XrPrimaryReferenceSpace>,
|
||||
frame_state: Res<OxrFrameState>,
|
||||
mut openxr_views: ResMut<OxrViews>,
|
||||
pipelined: Option<Res<Pipelined>>,
|
||||
) {
|
||||
let time = if pipelined.is_some() {
|
||||
openxr::Time::from_nanos(
|
||||
frame_state.predicted_display_time.as_nanos()
|
||||
+ frame_state.predicted_display_period.as_nanos(),
|
||||
)
|
||||
} else {
|
||||
frame_state.predicted_display_time
|
||||
};
|
||||
let (flags, xr_views) = session
|
||||
.locate_views(
|
||||
openxr::ViewConfigurationType::PRIMARY_STEREO,
|
||||
time,
|
||||
&ref_space,
|
||||
)
|
||||
.expect("Failed to locate views");
|
||||
|
||||
match (
|
||||
flags & ViewStateFlags::ORIENTATION_VALID == ViewStateFlags::ORIENTATION_VALID,
|
||||
flags & ViewStateFlags::POSITION_VALID == ViewStateFlags::POSITION_VALID,
|
||||
) {
|
||||
(true, true) => *openxr_views = OxrViews(xr_views),
|
||||
(true, false) => {
|
||||
for (i, view) in openxr_views.iter_mut().enumerate() {
|
||||
let Some(xr_view) = xr_views.get(i) else {
|
||||
break;
|
||||
};
|
||||
view.pose.orientation = xr_view.pose.orientation;
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
for (i, view) in openxr_views.iter_mut().enumerate() {
|
||||
let Some(xr_view) = xr_views.get(i) else {
|
||||
break;
|
||||
};
|
||||
view.pose.position = xr_view.pose.position;
|
||||
}
|
||||
}
|
||||
(false, false) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_views(
|
||||
mut query: Query<(&mut Transform, &mut XrProjection, &XrCamera)>,
|
||||
views: ResMut<OxrViews>,
|
||||
) {
|
||||
for (mut transform, mut projection, camera) in query.iter_mut() {
|
||||
let Some(view) = views.get(camera.0 as usize) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let projection_matrix = calculate_projection(projection.near, view.fov);
|
||||
projection.projection_matrix = projection_matrix;
|
||||
|
||||
let openxr::Quaternionf { x, y, z, w } = view.pose.orientation;
|
||||
let rotation = Quat::from_xyzw(x, y, z, w);
|
||||
transform.rotation = rotation;
|
||||
let openxr::Vector3f { x, y, z } = view.pose.position;
|
||||
let translation = Vec3::new(x, y, z);
|
||||
transform.translation = translation;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_views_render_world(
|
||||
views: Res<OxrViews>,
|
||||
root: Res<XrRootTransform>,
|
||||
mut query: Query<(&mut ExtractedView, &XrCamera)>,
|
||||
) {
|
||||
for (mut extracted_view, camera) in query.iter_mut() {
|
||||
let Some(view) = views.get(camera.0 as usize) else {
|
||||
continue;
|
||||
};
|
||||
let mut transform = Transform::IDENTITY;
|
||||
let openxr::Quaternionf { x, y, z, w } = view.pose.orientation;
|
||||
let rotation = Quat::from_xyzw(x, y, z, w);
|
||||
transform.rotation = rotation;
|
||||
let openxr::Vector3f { x, y, z } = view.pose.position;
|
||||
let translation = Vec3::new(x, y, z);
|
||||
transform.translation = translation;
|
||||
extracted_view.transform = root.0.mul_transform(transform);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_projection(near_z: f32, fov: openxr::Fovf) -> 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 is_vulkan_api = false; // FIXME wgpu probably abstracts this
|
||||
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)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Images inserted into texture views here should not be written to until [`wait_image`] is ran
|
||||
pub fn insert_texture_views(
|
||||
swapchain_images: Res<OxrSwapchainImages>,
|
||||
mut swapchain: ResMut<OxrSwapchain>,
|
||||
mut manual_texture_views: ResMut<ManualTextureViews>,
|
||||
graphics_info: Res<OxrGraphicsInfo>,
|
||||
) {
|
||||
let _span = info_span!("xr_insert_texture_views");
|
||||
let index = swapchain.acquire_image().expect("Failed to acquire image");
|
||||
let image = &swapchain_images[index as usize];
|
||||
|
||||
for i in 0..2 {
|
||||
add_texture_view(&mut manual_texture_views, image, &graphics_info, i);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_image(mut swapchain: ResMut<OxrSwapchain>) {
|
||||
swapchain
|
||||
.wait_image(openxr::Duration::INFINITE)
|
||||
.expect("Failed to wait image");
|
||||
}
|
||||
|
||||
pub fn add_texture_view(
|
||||
manual_texture_views: &mut ManualTextureViews,
|
||||
texture: &wgpu::Texture,
|
||||
info: &OxrGraphicsInfo,
|
||||
index: u32,
|
||||
) -> ManualTextureViewHandle {
|
||||
let view = texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
array_layer_count: Some(1),
|
||||
base_array_layer: index,
|
||||
..default()
|
||||
});
|
||||
let view = ManualTextureView {
|
||||
texture_view: view.into(),
|
||||
size: info.resolution,
|
||||
format: info.format,
|
||||
};
|
||||
let handle = ManualTextureViewHandle(XR_TEXTURE_INDEX + index);
|
||||
manual_texture_views.insert(handle, view);
|
||||
handle
|
||||
}
|
||||
|
||||
pub fn begin_frame(mut frame_stream: ResMut<OxrFrameStream>) {
|
||||
let _span = info_span!("xr_begin_frame");
|
||||
frame_stream.begin().expect("Failed to begin frame");
|
||||
}
|
||||
|
||||
pub fn release_image(mut swapchain: ResMut<OxrSwapchain>) {
|
||||
let _span = info_span!("xr_release_image");
|
||||
#[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();
|
||||
}
|
||||
swapchain.release_image().unwrap();
|
||||
}
|
||||
|
||||
pub fn end_frame(world: &mut World) {
|
||||
let _span = info_span!("xr_end_frame");
|
||||
#[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();
|
||||
}
|
||||
world.resource_scope::<OxrFrameStream, ()>(|world, mut frame_stream| {
|
||||
let mut layers = vec![];
|
||||
let frame_state = world.resource::<OxrFrameState>();
|
||||
if frame_state.should_render {
|
||||
for layer in world.resource::<OxrRenderLayers>().iter() {
|
||||
if let Some(layer) = layer.get(world) {
|
||||
layers.push(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
let layers: Vec<_> = layers.iter().map(Box::as_ref).collect();
|
||||
if let Err(e) = frame_stream.end(
|
||||
frame_state.predicted_display_time,
|
||||
world.resource::<OxrGraphicsInfo>().blend_mode,
|
||||
&layers,
|
||||
) {
|
||||
error!("Failed to end frame stream: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
360
crates/bevy_openxr/src/openxr/resources.rs
Normal file
360
crates/bevy_openxr/src/openxr/resources.rs
Normal file
@@ -0,0 +1,360 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_resource::ExtractResource;
|
||||
|
||||
use crate::error::OxrError;
|
||||
use crate::graphics::*;
|
||||
use crate::layer_builder::{CompositionLayer, LayerProvider};
|
||||
use crate::session::{OxrSession, OxrSessionCreateNextChain};
|
||||
use crate::types::*;
|
||||
|
||||
/// Wrapper around an [`Entry`](openxr::Entry) with some methods overridden to use bevy types.
|
||||
///
|
||||
/// See [`openxr::Entry`] for other available methods.
|
||||
#[derive(Deref, Clone)]
|
||||
pub struct OxrEntry(pub openxr::Entry);
|
||||
|
||||
impl OxrEntry {
|
||||
/// Enumerate available extensions for this OpenXR runtime.
|
||||
pub fn enumerate_extensions(&self) -> Result<OxrExtensions> {
|
||||
Ok(self.0.enumerate_extensions().map(Into::into)?)
|
||||
}
|
||||
|
||||
/// Creates an [`OxrInstance`].
|
||||
///
|
||||
/// Calls [`create_instance`](openxr::Entry::create_instance) internally.
|
||||
pub fn create_instance(
|
||||
&self,
|
||||
app_info: AppInfo,
|
||||
exts: OxrExtensions,
|
||||
layers: &[&str],
|
||||
backend: GraphicsBackend,
|
||||
) -> Result<OxrInstance> {
|
||||
let available_exts = self.enumerate_extensions()?;
|
||||
|
||||
if !backend.is_available(&available_exts) {
|
||||
return Err(OxrError::UnavailableBackend(backend));
|
||||
}
|
||||
|
||||
let required_exts = exts | backend.required_exts();
|
||||
|
||||
let instance = self.0.create_instance(
|
||||
&openxr::ApplicationInfo {
|
||||
application_name: &app_info.name,
|
||||
application_version: app_info.version.to_u32(),
|
||||
engine_name: "Bevy",
|
||||
engine_version: Version::BEVY.to_u32(),
|
||||
},
|
||||
&required_exts.into(),
|
||||
layers,
|
||||
)?;
|
||||
|
||||
Ok(OxrInstance(instance, backend, app_info))
|
||||
}
|
||||
|
||||
/// Returns a list of all of the backends the OpenXR runtime supports.
|
||||
pub fn available_backends(&self) -> Result<Vec<GraphicsBackend>> {
|
||||
Ok(GraphicsBackend::available_backends(
|
||||
&self.enumerate_extensions()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around [`openxr::Instance`] with additional data for safety and some methods overriden to use bevy types.
|
||||
///
|
||||
/// See [`openxr::Instance`] for other available methods.
|
||||
#[derive(Resource, Deref, Clone)]
|
||||
pub struct OxrInstance(
|
||||
#[deref] pub(crate) openxr::Instance,
|
||||
/// [`GraphicsBackend`] is stored here to let us know what graphics API the current instance wants to target.
|
||||
pub(crate) GraphicsBackend,
|
||||
pub(crate) AppInfo,
|
||||
);
|
||||
|
||||
impl OxrInstance {
|
||||
/// Creates an [`OxrInstance`] from an [`openxr::Instance`] if needed.
|
||||
/// In the majority of cases, you should use [`create_instance`](OxrEntry::create_instance) instead.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The OpenXR instance passed in *must* have support for the backend specified.
|
||||
pub unsafe fn from_inner(
|
||||
instance: openxr::Instance,
|
||||
backend: GraphicsBackend,
|
||||
info: AppInfo,
|
||||
) -> Self {
|
||||
Self(instance, backend, info)
|
||||
}
|
||||
|
||||
/// Consumes self and returns the inner [`openxr::Instance`]
|
||||
pub fn into_inner(self) -> openxr::Instance {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the current backend being used by this instance.
|
||||
pub fn backend(&self) -> GraphicsBackend {
|
||||
self.1
|
||||
}
|
||||
|
||||
/// Returns the [`AppInfo`] being used by this instance.
|
||||
pub fn app_info(&self) -> &AppInfo {
|
||||
&self.2
|
||||
}
|
||||
|
||||
/// Initialize graphics. This is used to create [WgpuGraphics] for the bevy app and to get the [SessionCreateInfo] needed to make an XR session.
|
||||
pub fn init_graphics(
|
||||
&self,
|
||||
system_id: openxr::SystemId,
|
||||
) -> Result<(WgpuGraphics, SessionCreateInfo)> {
|
||||
graphics_match!(
|
||||
self.1;
|
||||
_ => {
|
||||
let (graphics, session_info) = Api::init_graphics(&self.2, &self, system_id)?;
|
||||
|
||||
Ok((graphics, SessionCreateInfo(Api::wrap(session_info))))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates an [OxrSession]
|
||||
///
|
||||
/// Calls [`create_session`](openxr::Instance::create_session) internally.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `info` must contain valid handles for the graphics api
|
||||
pub unsafe fn create_session(
|
||||
&self,
|
||||
system_id: openxr::SystemId,
|
||||
info: SessionCreateInfo,
|
||||
chain: &mut OxrSessionCreateNextChain,
|
||||
) -> Result<(OxrSession, OxrFrameWaiter, OxrFrameStream)> {
|
||||
if !info.0.using_graphics_of_val(&self.1) {
|
||||
return Err(OxrError::GraphicsBackendMismatch {
|
||||
item: std::any::type_name::<SessionCreateInfo>(),
|
||||
backend: info.0.graphics_name(),
|
||||
expected_backend: self.1.graphics_name(),
|
||||
});
|
||||
}
|
||||
graphics_match!(
|
||||
info.0;
|
||||
info => {
|
||||
let (session, frame_waiter, frame_stream) = Api::create_session(self,system_id, &info,chain)?;
|
||||
Ok((session.into(), OxrFrameWaiter(frame_waiter), OxrFrameStream(Api::wrap(frame_stream))))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Graphics agnostic wrapper around [openxr::FrameStream]
|
||||
#[derive(Resource)]
|
||||
pub struct OxrFrameStream(pub GraphicsWrap<Self>);
|
||||
|
||||
impl GraphicsType for OxrFrameStream {
|
||||
type Inner<G: GraphicsExt> = openxr::FrameStream<G>;
|
||||
}
|
||||
|
||||
impl OxrFrameStream {
|
||||
/// Creates a new [`OxrFrameStream`] from an [`openxr::FrameStream`].
|
||||
/// In the majority of cases, you should use [`create_session`](OxrInstance::create_session) instead.
|
||||
pub fn from_inner<G: GraphicsExt>(frame_stream: openxr::FrameStream<G>) -> Self {
|
||||
Self(G::wrap(frame_stream))
|
||||
}
|
||||
|
||||
/// Indicate that graphics device work is beginning.
|
||||
///
|
||||
/// Calls [`begin`](openxr::FrameStream::begin) internally.
|
||||
pub fn begin(&mut self) -> openxr::Result<()> {
|
||||
graphics_match!(
|
||||
&mut self.0;
|
||||
stream => stream.begin()
|
||||
)
|
||||
}
|
||||
|
||||
/// Indicate that all graphics work for the frame has been submitted
|
||||
///
|
||||
/// `layers` is an array of references to any type of composition layer that implements [`CompositionLayer`],
|
||||
/// e.g. [`CompositionLayerProjection`](crate::layer_builder::CompositionLayerProjection)
|
||||
pub fn end(
|
||||
&mut self,
|
||||
display_time: openxr::Time,
|
||||
environment_blend_mode: openxr::EnvironmentBlendMode,
|
||||
layers: &[&dyn CompositionLayer],
|
||||
) -> Result<()> {
|
||||
graphics_match!(
|
||||
&mut self.0;
|
||||
stream => {
|
||||
let mut new_layers = vec![];
|
||||
|
||||
for (i, layer) in layers.into_iter().enumerate() {
|
||||
if let Some(swapchain) = layer.swapchain() {
|
||||
if !swapchain.0.using_graphics::<Api>() {
|
||||
error!(
|
||||
"Composition layer {i} is using graphics api '{}', expected graphics api '{}'. Excluding layer from frame submission.",
|
||||
swapchain.0.graphics_name(),
|
||||
std::any::type_name::<Api>(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
new_layers.push(unsafe { std::mem::transmute(layer.header()) });
|
||||
}
|
||||
|
||||
Ok(stream.end(display_time, environment_blend_mode, new_layers.as_slice())?)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle for waiting to render a frame.
|
||||
///
|
||||
/// See [`FrameWaiter`](openxr::FrameWaiter) for available methods.
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
pub struct OxrFrameWaiter(pub openxr::FrameWaiter);
|
||||
|
||||
/// Graphics agnostic wrapper around [openxr::Swapchain]
|
||||
#[derive(Resource)]
|
||||
pub struct OxrSwapchain(pub GraphicsWrap<Self>);
|
||||
|
||||
impl GraphicsType for OxrSwapchain {
|
||||
type Inner<G: GraphicsExt> = openxr::Swapchain<G>;
|
||||
}
|
||||
|
||||
impl OxrSwapchain {
|
||||
/// Creates a new [`OxrSwapchain`] from an [`openxr::Swapchain`].
|
||||
/// In the majority of cases, you should use [`create_swapchain`](OxrSession::create_swapchain) instead.
|
||||
pub fn from_inner<G: GraphicsExt>(swapchain: openxr::Swapchain<G>) -> Self {
|
||||
Self(G::wrap(swapchain))
|
||||
}
|
||||
|
||||
/// Determine the index of the next image to render to in the swapchain image array.
|
||||
///
|
||||
/// Calls [`acquire_image`](openxr::Swapchain::acquire_image) internally.
|
||||
pub fn acquire_image(&mut self) -> Result<u32> {
|
||||
graphics_match!(
|
||||
&mut self.0;
|
||||
swap => Ok(swap.acquire_image()?)
|
||||
)
|
||||
}
|
||||
|
||||
/// Wait for the compositor to finish reading from the oldest unwaited acquired image.
|
||||
///
|
||||
/// Calls [`wait_image`](openxr::Swapchain::wait_image) internally.
|
||||
pub fn wait_image(&mut self, timeout: openxr::Duration) -> Result<()> {
|
||||
graphics_match!(
|
||||
&mut self.0;
|
||||
swap => Ok(swap.wait_image(timeout)?)
|
||||
)
|
||||
}
|
||||
|
||||
/// Release the oldest acquired image.
|
||||
///
|
||||
/// Calls [`release_image`](openxr::Swapchain::release_image) internally.
|
||||
pub fn release_image(&mut self) -> Result<()> {
|
||||
graphics_match!(
|
||||
&mut self.0;
|
||||
swap => Ok(swap.release_image()?)
|
||||
)
|
||||
}
|
||||
|
||||
/// Enumerates swapchain images and converts them to wgpu [`Texture`](wgpu::Texture)s.
|
||||
///
|
||||
/// Calls [`enumerate_images`](openxr::Swapchain::enumerate_images) internally.
|
||||
pub fn enumerate_images(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
format: wgpu::TextureFormat,
|
||||
resolution: UVec2,
|
||||
) -> Result<OxrSwapchainImages> {
|
||||
graphics_match!(
|
||||
&self.0;
|
||||
swap => {
|
||||
let mut images = vec![];
|
||||
for image in swap.enumerate_images()? {
|
||||
unsafe {
|
||||
images.push(Api::to_wgpu_img(image, device, format, resolution)?);
|
||||
}
|
||||
}
|
||||
Ok(OxrSwapchainImages(images.leak()))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the generated swapchain images.
|
||||
#[derive(Debug, Deref, Resource, Clone, Copy, ExtractResource)]
|
||||
pub struct OxrSwapchainImages(pub &'static [wgpu::Texture]);
|
||||
|
||||
/// Thread safe wrapper around [openxr::Space] representing the stage.
|
||||
// #[derive(Deref, Clone, Resource)]
|
||||
// pub struct OxrStage(pub Arc<openxr::Space>);
|
||||
|
||||
/// Stores the latest generated [OxrViews]
|
||||
#[derive(Clone, Resource, ExtractResource, Deref, DerefMut, Default)]
|
||||
pub struct OxrViews(pub Vec<openxr::View>);
|
||||
|
||||
/// Wrapper around [openxr::SystemId] to allow it to be stored as a resource.
|
||||
#[derive(Debug, Copy, Clone, Deref, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Resource)]
|
||||
pub struct OxrSystemId(pub openxr::SystemId);
|
||||
|
||||
/// Wrapper around [`openxr::Passthrough`].
|
||||
///
|
||||
/// Used to [`start`](openxr::Passthrough::start) or [`pause`](openxr::Passthrough::pause) passthrough on the physical device.
|
||||
///
|
||||
/// See [`openxr::Passthrough`] for available methods.
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
pub struct OxrPassthrough(
|
||||
#[deref] pub openxr::Passthrough,
|
||||
/// The flags are stored here so that they don't need to be passed in again when creating an [`OxrPassthroughLayer`].
|
||||
pub openxr::PassthroughFlagsFB,
|
||||
);
|
||||
|
||||
impl OxrPassthrough {
|
||||
/// This function can create an [`OxrPassthrough`] from raw openxr types if needed.
|
||||
/// In the majority of cases, you should use [`create_passthrough`](OxrSession::create_passthrough) instead.
|
||||
pub fn from_inner(passthrough: openxr::Passthrough, flags: openxr::PassthroughFlagsFB) -> Self {
|
||||
Self(passthrough, flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around [`openxr::Passthrough`].
|
||||
///
|
||||
/// Used to create a [`CompositionLayerPassthrough`](crate::layer_builder::CompositionLayerPassthrough), and to [`pause`](openxr::PassthroughLayer::pause) or [`resume`](openxr::PassthroughLayer::resume) rendering of the passthrough layer.
|
||||
///
|
||||
/// See [`openxr::PassthroughLayer`] for available methods.
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
pub struct OxrPassthroughLayer(pub openxr::PassthroughLayer);
|
||||
|
||||
#[derive(Resource, Deref, DerefMut, Default)]
|
||||
pub struct OxrRenderLayers(pub Vec<Box<dyn LayerProvider + Send + Sync>>);
|
||||
|
||||
/// Resource storing graphics info for the currently running session.
|
||||
#[derive(Clone, Copy, Resource, ExtractResource)]
|
||||
pub struct OxrGraphicsInfo {
|
||||
pub blend_mode: EnvironmentBlendMode,
|
||||
pub resolution: UVec2,
|
||||
pub format: wgpu::TextureFormat,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// This is used to store information from startup that is needed to create the session after the instance has been created.
|
||||
pub struct SessionConfigInfo {
|
||||
/// List of blend modes the openxr session can use. If [None], pick the first available blend mode.
|
||||
pub blend_modes: Option<Vec<EnvironmentBlendMode>>,
|
||||
/// List of formats the openxr session can use. If [None], pick the first available format
|
||||
pub formats: Option<Vec<wgpu::TextureFormat>>,
|
||||
/// List of resolutions that the openxr swapchain can use. If [None] pick the first available resolution.
|
||||
pub resolutions: Option<Vec<UVec2>>,
|
||||
/// Graphics info used to create a session.
|
||||
pub graphics_info: SessionCreateInfo,
|
||||
}
|
||||
|
||||
#[derive(ExtractResource, Resource, Clone, Default)]
|
||||
pub struct OxrSessionStarted(pub bool);
|
||||
|
||||
/// The frame state returned from [FrameWaiter::wait_frame](openxr::FrameWaiter::wait)
|
||||
#[derive(Clone, Deref, DerefMut, Resource, ExtractResource)]
|
||||
pub struct OxrFrameState(pub openxr::FrameState);
|
||||
|
||||
/// Instructs systems to add display period
|
||||
#[derive(Clone, Copy, Default, Resource)]
|
||||
pub struct Pipelined;
|
||||
117
crates/bevy_openxr/src/openxr/session.rs
Normal file
117
crates/bevy_openxr/src/openxr/session.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::next_chain::{OxrNextChain, OxrNextChainStructBase, OxrNextChainStructProvider};
|
||||
use crate::resources::{OxrPassthrough, OxrPassthroughLayer, OxrSwapchain};
|
||||
use crate::types::{Result, SwapchainCreateInfo};
|
||||
use bevy::prelude::*;
|
||||
use openxr::AnyGraphics;
|
||||
|
||||
use crate::graphics::{graphics_match, GraphicsExt, GraphicsType, GraphicsWrap};
|
||||
|
||||
/// Graphics agnostic wrapper around [openxr::Session].
|
||||
///
|
||||
/// See [`openxr::Session`] for other available methods.
|
||||
#[derive(Resource, Deref, Clone)]
|
||||
pub struct OxrSession(
|
||||
/// A session handle with [`AnyGraphics`].
|
||||
/// Having this here allows the majority of [`Session`](openxr::Session)'s methods to work without having to rewrite them.
|
||||
#[deref]
|
||||
pub(crate) openxr::Session<AnyGraphics>,
|
||||
/// A [`GraphicsWrap`] with [`openxr::Session<G>`] as the inner type.
|
||||
/// This is so that we can still operate on functions that don't take [`AnyGraphics`] as the generic.
|
||||
pub(crate) GraphicsWrap<Self>,
|
||||
);
|
||||
|
||||
impl GraphicsType for OxrSession {
|
||||
type Inner<G: GraphicsExt> = openxr::Session<G>;
|
||||
}
|
||||
|
||||
impl<G: GraphicsExt> From<openxr::Session<G>> for OxrSession {
|
||||
fn from(session: openxr::Session<G>) -> Self {
|
||||
Self::from_inner(session)
|
||||
}
|
||||
}
|
||||
|
||||
impl OxrSession {
|
||||
/// Creates a new [`OxrSession`] from an [`openxr::Session`].
|
||||
/// In the majority of cases, you should use [`create_session`](OxrInstance::create_session) instead.
|
||||
pub fn from_inner<G: GraphicsExt>(session: openxr::Session<G>) -> Self {
|
||||
Self(session.clone().into_any_graphics(), G::wrap(session))
|
||||
}
|
||||
|
||||
/// Returns [`GraphicsWrap`] with [`openxr::Session<G>`] as the inner type.
|
||||
///
|
||||
/// This can be useful if you need access to the original [`openxr::Session`] with the graphics API still specified.
|
||||
pub fn typed_session(&self) -> &GraphicsWrap<Self> {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// Enumerates all available swapchain formats and converts them to wgpu's [`TextureFormat`](wgpu::TextureFormat).
|
||||
///
|
||||
/// Calls [`enumerate_swapchain_formats`](openxr::Session::enumerate_swapchain_formats) internally.
|
||||
pub fn enumerate_swapchain_formats(&self) -> Result<Vec<wgpu::TextureFormat>> {
|
||||
graphics_match!(
|
||||
&self.1;
|
||||
session => Ok(session.enumerate_swapchain_formats()?.into_iter().filter_map(Api::into_wgpu_format).collect())
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates an [OxrSwapchain].
|
||||
///
|
||||
/// Calls [`create_swapchain`](openxr::Session::create_swapchain) internally.
|
||||
pub fn create_swapchain(&self, info: SwapchainCreateInfo) -> Result<OxrSwapchain> {
|
||||
Ok(OxrSwapchain(graphics_match!(
|
||||
&self.1;
|
||||
session => session.create_swapchain(&info.try_into()?)? => OxrSwapchain
|
||||
)))
|
||||
}
|
||||
|
||||
/// Creates a passthrough.
|
||||
///
|
||||
/// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough).
|
||||
///
|
||||
/// Calls [`create_passthrough`](openxr::Session::create_passthrough) internally.
|
||||
pub fn create_passthrough(&self, flags: openxr::PassthroughFlagsFB) -> Result<OxrPassthrough> {
|
||||
Ok(OxrPassthrough(
|
||||
graphics_match! {
|
||||
&self.1;
|
||||
session => session.create_passthrough(flags)?
|
||||
},
|
||||
flags,
|
||||
))
|
||||
}
|
||||
|
||||
/// Creates a passthrough layer that can be used to make a [`CompositionLayerPassthrough`](crate::layer_builder::CompositionLayerPassthrough) for frame submission.
|
||||
///
|
||||
/// Requires [`XR_FB_passthrough`](https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#XR_FB_passthrough).
|
||||
///
|
||||
/// Calls [`create_passthrough_layer`](openxr::Session::create_passthrough_layer) internally.
|
||||
pub fn create_passthrough_layer(
|
||||
&self,
|
||||
passthrough: &OxrPassthrough,
|
||||
purpose: openxr::PassthroughLayerPurposeFB,
|
||||
) -> Result<OxrPassthroughLayer> {
|
||||
Ok(OxrPassthroughLayer(graphics_match! {
|
||||
&self.1;
|
||||
session => session.create_passthrough_layer(&passthrough.0, passthrough.1, purpose)?
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OxrSessionCreateNextProvider: OxrNextChainStructProvider {}
|
||||
|
||||
/// NonSend Resource
|
||||
#[derive(Default)]
|
||||
pub struct OxrSessionCreateNextChain(OxrNextChain);
|
||||
|
||||
impl OxrSessionCreateNextChain {
|
||||
pub fn push<T: OxrSessionCreateNextProvider>(&mut self, info_struct: T) {
|
||||
self.0.push(info_struct)
|
||||
}
|
||||
pub fn chain(&self) -> Option<&OxrNextChainStructBase> {
|
||||
self.0.chain()
|
||||
}
|
||||
pub fn chain_pointer(&self) -> *const c_void {
|
||||
self.0.chain_pointer()
|
||||
}
|
||||
}
|
||||
582
crates/bevy_openxr/src/openxr/spaces.rs
Normal file
582
crates/bevy_openxr/src/openxr/spaces.rs
Normal file
@@ -0,0 +1,582 @@
|
||||
use std::{mem::MaybeUninit, ptr, sync::Mutex};
|
||||
|
||||
use bevy::{prelude::*, utils::hashbrown::HashSet};
|
||||
use bevy_mod_xr::{
|
||||
session::{session_available, session_running, XrFirst, XrHandleEvents},
|
||||
spaces::{XrDestroySpace, XrPrimaryReferenceSpace, XrReferenceSpace, XrSpace, XrSpatialOffset},
|
||||
types::XrPose,
|
||||
};
|
||||
use openxr::{
|
||||
sys, HandJointLocation, HandJointLocations, HandJointVelocities, HandJointVelocity,
|
||||
ReferenceSpaceType, SpaceLocationFlags, HAND_JOINT_COUNT,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
helper_traits::{ToPosef, ToQuat, ToVec3},
|
||||
resources::{OxrFrameState, OxrInstance, Pipelined},
|
||||
session::OxrSession,
|
||||
};
|
||||
|
||||
#[derive(SystemSet, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct OxrSpaceSyncSet;
|
||||
|
||||
/// VERY IMPORTANT!! only disable when you know what you are doing
|
||||
pub struct OxrSpacePatchingPlugin;
|
||||
impl Plugin for OxrSpacePatchingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, patch_destroy_space.run_if(session_available));
|
||||
}
|
||||
}
|
||||
pub struct OxrSpatialPlugin;
|
||||
impl Plugin for OxrSpatialPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<XrDestroySpace>()
|
||||
.add_systems(XrFirst, destroy_space_event.before(XrHandleEvents))
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
update_space_transforms
|
||||
.in_set(OxrSpaceSyncSet)
|
||||
.run_if(session_running),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy_space_event(instance: Res<OxrInstance>, mut events: EventReader<XrDestroySpace>) {
|
||||
for space in events.read() {
|
||||
match instance.destroy_space(space.0) {
|
||||
Ok(_) => (),
|
||||
Err(err) => warn!("error while destroying space: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES: Mutex<Option<HashSet<u64>>> = Mutex::new(None);
|
||||
pub static OXR_ORIGINAL_DESTOY_SPACE: Mutex<Option<openxr::sys::pfn::DestroySpace>> =
|
||||
Mutex::new(None);
|
||||
|
||||
fn patch_destroy_space(instance: ResMut<OxrInstance>) {
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.replace(HashSet::new());
|
||||
let raw_instance_ptr = instance.fp() as *const _ as *mut openxr::raw::Instance;
|
||||
unsafe {
|
||||
OXR_ORIGINAL_DESTOY_SPACE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.replace((*raw_instance_ptr).destroy_space);
|
||||
|
||||
(*raw_instance_ptr).destroy_space = patched_destroy_space;
|
||||
}
|
||||
}
|
||||
unsafe extern "system" fn patched_destroy_space(space: openxr::sys::Space) -> openxr::sys::Result {
|
||||
if !OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&space.into_raw())
|
||||
{
|
||||
OXR_ORIGINAL_DESTOY_SPACE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.expect("has to be initialized")(space)
|
||||
} else {
|
||||
info!("Inject Worked, not destroying space");
|
||||
openxr::sys::Result::SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
fn update_space_transforms(
|
||||
session: Res<OxrSession>,
|
||||
default_ref_space: Res<XrPrimaryReferenceSpace>,
|
||||
pipelined: Option<Res<Pipelined>>,
|
||||
frame_state: Res<OxrFrameState>,
|
||||
mut query: Query<(
|
||||
&mut Transform,
|
||||
&XrSpace,
|
||||
Option<&XrSpatialOffset>,
|
||||
Option<&XrReferenceSpace>,
|
||||
)>,
|
||||
) {
|
||||
for (mut transform, space, offset, ref_space) in &mut query {
|
||||
let offset = offset.copied().unwrap_or_default();
|
||||
let ref_space = ref_space.unwrap_or(&default_ref_space);
|
||||
if let Ok(space_location) = session.locate_space(
|
||||
space,
|
||||
ref_space,
|
||||
if pipelined.is_some() {
|
||||
openxr::Time::from_nanos(
|
||||
frame_state.predicted_display_time.as_nanos()
|
||||
+ frame_state.predicted_display_period.as_nanos(),
|
||||
)
|
||||
} else {
|
||||
frame_state.predicted_display_time
|
||||
},
|
||||
) {
|
||||
if space_location
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::POSITION_VALID)
|
||||
{
|
||||
transform.translation = offset
|
||||
.to_transform()
|
||||
.transform_point(space_location.pose.position.to_vec3())
|
||||
}
|
||||
if space_location
|
||||
.location_flags
|
||||
.contains(SpaceLocationFlags::ORIENTATION_VALID)
|
||||
{
|
||||
transform.rotation = offset.rotation * space_location.pose.orientation.to_quat();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OxrSession {
|
||||
pub fn create_action_space<T: openxr::ActionTy>(
|
||||
&self,
|
||||
action: &openxr::Action<T>,
|
||||
subaction_path: openxr::Path,
|
||||
pose_in_space: XrPose,
|
||||
) -> openxr::Result<XrSpace> {
|
||||
let info = sys::ActionSpaceCreateInfo {
|
||||
ty: sys::ActionSpaceCreateInfo::TYPE,
|
||||
next: ptr::null(),
|
||||
action: action.as_raw(),
|
||||
subaction_path,
|
||||
pose_in_action_space: pose_in_space.to_posef(),
|
||||
};
|
||||
let mut out = sys::Space::NULL;
|
||||
unsafe {
|
||||
cvt((self.instance().fp().create_action_space)(
|
||||
self.as_raw(),
|
||||
&info,
|
||||
&mut out,
|
||||
))?;
|
||||
Ok(XrSpace::from_raw(out.into_raw()))
|
||||
}
|
||||
}
|
||||
pub fn create_reference_space(
|
||||
&self,
|
||||
ref_space_type: ReferenceSpaceType,
|
||||
pose_in_ref_space: Transform,
|
||||
) -> openxr::Result<XrReferenceSpace> {
|
||||
let info = sys::ReferenceSpaceCreateInfo {
|
||||
ty: sys::ReferenceSpaceCreateInfo::TYPE,
|
||||
next: ptr::null(),
|
||||
reference_space_type: ref_space_type,
|
||||
pose_in_reference_space: pose_in_ref_space.to_posef(),
|
||||
};
|
||||
let mut out = sys::Space::NULL;
|
||||
unsafe {
|
||||
cvt((self.instance().fp().create_reference_space)(
|
||||
self.as_raw(),
|
||||
&info,
|
||||
&mut out,
|
||||
))?;
|
||||
Ok(XrReferenceSpace(XrSpace::from_raw(out.into_raw())))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn locate_space(
|
||||
instance: &openxr::Instance,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<openxr::SpaceLocation> {
|
||||
unsafe {
|
||||
let mut x = sys::SpaceLocation::out(ptr::null_mut());
|
||||
cvt((instance.fp().locate_space)(
|
||||
space.as_raw_openxr_space(),
|
||||
base.as_raw_openxr_space(),
|
||||
time,
|
||||
x.as_mut_ptr(),
|
||||
))?;
|
||||
Ok(create_space_location(&x))
|
||||
}
|
||||
}
|
||||
fn locate_space_with_velocity(
|
||||
instance: &openxr::Instance,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> {
|
||||
unsafe {
|
||||
let mut velocity = sys::SpaceVelocity::out(ptr::null_mut());
|
||||
let mut location = sys::SpaceLocation::out(&mut velocity as *mut _ as _);
|
||||
cvt((instance.fp().locate_space)(
|
||||
space.as_raw_openxr_space(),
|
||||
base.as_raw_openxr_space(),
|
||||
time,
|
||||
location.as_mut_ptr(),
|
||||
))?;
|
||||
Ok((
|
||||
create_space_location(&location),
|
||||
create_space_velocity(&velocity),
|
||||
))
|
||||
}
|
||||
}
|
||||
pub fn locate_hand_joints(
|
||||
instance: &openxr::Instance,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<HandJointLocations>> {
|
||||
unsafe {
|
||||
let locate_info = sys::HandJointsLocateInfoEXT {
|
||||
ty: sys::HandJointsLocateInfoEXT::TYPE,
|
||||
next: ptr::null(),
|
||||
base_space: base.as_raw_openxr_space(),
|
||||
time,
|
||||
};
|
||||
let mut locations =
|
||||
MaybeUninit::<[openxr::HandJointLocation; openxr::HAND_JOINT_COUNT]>::uninit();
|
||||
let mut location_info = sys::HandJointLocationsEXT {
|
||||
ty: sys::HandJointLocationsEXT::TYPE,
|
||||
next: ptr::null_mut(),
|
||||
is_active: false.into(),
|
||||
joint_count: openxr::HAND_JOINT_COUNT as u32,
|
||||
joint_locations: locations.as_mut_ptr() as _,
|
||||
};
|
||||
cvt((instance
|
||||
.exts()
|
||||
.ext_hand_tracking
|
||||
.as_ref()
|
||||
.expect("Somehow created HandTracker without XR_EXT_hand_tracking being enabled")
|
||||
.locate_hand_joints)(
|
||||
tracker.as_raw(),
|
||||
&locate_info,
|
||||
&mut location_info,
|
||||
))?;
|
||||
Ok(if location_info.is_active.into() {
|
||||
Some(locations.assume_init())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn locate_hand_joints_with_velocities(
|
||||
instance: &openxr::Instance,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<(HandJointLocations, HandJointVelocities)>> {
|
||||
unsafe {
|
||||
let locate_info = sys::HandJointsLocateInfoEXT {
|
||||
ty: sys::HandJointsLocateInfoEXT::TYPE,
|
||||
next: ptr::null(),
|
||||
base_space: base.as_raw_openxr_space(),
|
||||
time,
|
||||
};
|
||||
let mut velocities = MaybeUninit::<[HandJointVelocity; HAND_JOINT_COUNT]>::uninit();
|
||||
let mut velocity_info = sys::HandJointVelocitiesEXT {
|
||||
ty: sys::HandJointVelocitiesEXT::TYPE,
|
||||
next: ptr::null_mut(),
|
||||
joint_count: HAND_JOINT_COUNT as u32,
|
||||
joint_velocities: velocities.as_mut_ptr() as _,
|
||||
};
|
||||
let mut locations = MaybeUninit::<[HandJointLocation; HAND_JOINT_COUNT]>::uninit();
|
||||
let mut location_info = sys::HandJointLocationsEXT {
|
||||
ty: sys::HandJointLocationsEXT::TYPE,
|
||||
next: &mut velocity_info as *mut _ as _,
|
||||
is_active: false.into(),
|
||||
joint_count: HAND_JOINT_COUNT as u32,
|
||||
joint_locations: locations.as_mut_ptr() as _,
|
||||
};
|
||||
cvt((instance
|
||||
.exts()
|
||||
.ext_hand_tracking
|
||||
.as_ref()
|
||||
.expect("Somehow created HandTracker without XR_EXT_hand_tracking being enabled")
|
||||
.locate_hand_joints)(
|
||||
tracker.as_raw(),
|
||||
&locate_info,
|
||||
&mut location_info,
|
||||
))?;
|
||||
Ok(if location_info.is_active.into() {
|
||||
Some((locations.assume_init(), velocities.assume_init()))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
pub fn destroy_space(
|
||||
instance: &openxr::Instance,
|
||||
space: sys::Space,
|
||||
) -> openxr::Result<sys::Result> {
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.remove(&space.into_raw());
|
||||
let result = unsafe { (instance.fp().destroy_space)(space) };
|
||||
cvt(result)
|
||||
}
|
||||
impl OxrSession {
|
||||
pub fn allow_auto_destruct_of_openxr_space(&self, space: &openxr::Space) {
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.remove(&space.as_raw().into_raw());
|
||||
}
|
||||
pub fn destroy_space(&self, space: XrSpace) -> openxr::Result<sys::Result> {
|
||||
destroy_space(self.instance(), space.as_raw_openxr_space())
|
||||
}
|
||||
pub fn destroy_openxr_space(&self, space: openxr::Space) -> openxr::Result<sys::Result> {
|
||||
destroy_space(self.instance(), space.as_raw())
|
||||
}
|
||||
pub fn locate_views(
|
||||
&self,
|
||||
view_configuration_type: openxr::ViewConfigurationType,
|
||||
display_time: openxr::Time,
|
||||
ref_space: &XrReferenceSpace,
|
||||
) -> openxr::Result<(openxr::ViewStateFlags, Vec<openxr::View>)> {
|
||||
let info = sys::ViewLocateInfo {
|
||||
ty: sys::ViewLocateInfo::TYPE,
|
||||
next: ptr::null(),
|
||||
view_configuration_type,
|
||||
display_time,
|
||||
space: ref_space.as_raw_openxr_space(),
|
||||
};
|
||||
let (flags, raw) = unsafe {
|
||||
let mut out = sys::ViewState::out(ptr::null_mut());
|
||||
let raw = get_arr_init(sys::View::out(ptr::null_mut()), |cap, count, buf| {
|
||||
(self.instance().fp().locate_views)(
|
||||
self.as_raw(),
|
||||
&info,
|
||||
out.as_mut_ptr(),
|
||||
cap,
|
||||
count,
|
||||
buf as _,
|
||||
)
|
||||
})?;
|
||||
(out.assume_init().view_state_flags, raw)
|
||||
};
|
||||
Ok((
|
||||
flags,
|
||||
raw.into_iter()
|
||||
.map(|x| unsafe { create_view(flags, &x) })
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
pub fn locate_space(
|
||||
&self,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<openxr::SpaceLocation> {
|
||||
locate_space(self.instance(), space, base, time)
|
||||
}
|
||||
pub fn locate_space_with_velocity(
|
||||
&self,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> {
|
||||
locate_space_with_velocity(self.instance(), space, base, time)
|
||||
}
|
||||
pub fn locate_hand_joints(
|
||||
&self,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<openxr::HandJointLocations>> {
|
||||
locate_hand_joints(self.instance(), tracker, base, time)
|
||||
}
|
||||
pub fn locate_hand_joints_with_velocities(
|
||||
&self,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<(HandJointLocations, HandJointVelocities)>> {
|
||||
locate_hand_joints_with_velocities(self.instance(), tracker, base, time)
|
||||
}
|
||||
}
|
||||
impl OxrInstance {
|
||||
pub fn allow_auto_destruct_of_openxr_space(&self, space: &openxr::Space) {
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.remove(&space.as_raw().into_raw());
|
||||
}
|
||||
pub fn destroy_space(&self, space: XrSpace) -> openxr::Result<sys::Result> {
|
||||
destroy_space(self, space.as_raw_openxr_space())
|
||||
}
|
||||
pub fn destroy_openxr_space(&self, space: openxr::Space) -> openxr::Result<sys::Result> {
|
||||
destroy_space(self, space.as_raw())
|
||||
}
|
||||
pub fn locate_space(
|
||||
&self,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<openxr::SpaceLocation> {
|
||||
locate_space(self, space, base, time)
|
||||
}
|
||||
pub fn locate_space_with_velocity(
|
||||
&self,
|
||||
space: &XrSpace,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<(openxr::SpaceLocation, openxr::SpaceVelocity)> {
|
||||
locate_space_with_velocity(self, space, base, time)
|
||||
}
|
||||
pub fn locate_hand_joints(
|
||||
&self,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<openxr::HandJointLocations>> {
|
||||
locate_hand_joints(self, tracker, base, time)
|
||||
}
|
||||
pub fn locate_hand_joints_with_velocities(
|
||||
&self,
|
||||
tracker: &openxr::HandTracker,
|
||||
base: &XrSpace,
|
||||
time: openxr::Time,
|
||||
) -> openxr::Result<Option<(HandJointLocations, HandJointVelocities)>> {
|
||||
locate_hand_joints_with_velocities(self, tracker, base, time)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This is an Extension trait. DO NOT IMPLEMENT IT!
|
||||
pub unsafe trait OxrSpaceExt {
|
||||
/// get an openxr::sys::Space as a reference to the XrSpace
|
||||
/// does not remove the space from the space managment system!
|
||||
fn as_raw_openxr_space(&self) -> sys::Space;
|
||||
/// Adds the openxr::sys::Space into the the space managment system
|
||||
fn from_raw_openxr_space(space: sys::Space) -> Self;
|
||||
/// Adds the openxr::Space into the the space manegment system
|
||||
fn from_openxr_space(space: openxr::Space) -> Self;
|
||||
/// get an openxr::Space as a reference to the XrSpace
|
||||
/// does not remove the space from the space managment system!
|
||||
/// # Safety
|
||||
/// Session has to be the session from which the space is from
|
||||
unsafe fn as_openxr_space<T>(&self, session: &openxr::Session<T>) -> openxr::Space;
|
||||
/// get an openxr::Space as an onwned version of the XrSpace
|
||||
/// removes the space from the space managment system!
|
||||
/// # Safety
|
||||
/// Session has to be the session from which the space is from
|
||||
unsafe fn into_openxr_space<T>(self, session: &openxr::Session<T>) -> openxr::Space;
|
||||
}
|
||||
|
||||
unsafe impl OxrSpaceExt for XrSpace {
|
||||
fn as_raw_openxr_space(&self) -> sys::Space {
|
||||
sys::Space::from_raw(self.as_raw())
|
||||
}
|
||||
|
||||
fn from_raw_openxr_space(space: sys::Space) -> Self {
|
||||
let raw = space.into_raw();
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(raw);
|
||||
unsafe { XrSpace::from_raw(raw) }
|
||||
}
|
||||
|
||||
fn from_openxr_space(space: openxr::Space) -> Self {
|
||||
Self::from_raw_openxr_space(space.as_raw())
|
||||
}
|
||||
|
||||
unsafe fn as_openxr_space<T>(&self, session: &openxr::Session<T>) -> openxr::Space {
|
||||
unsafe { openxr::Space::reference_from_raw(session.clone(), self.as_raw_openxr_space()) }
|
||||
}
|
||||
unsafe fn into_openxr_space<T>(self, session: &openxr::Session<T>) -> openxr::Space {
|
||||
OXR_DO_NOT_CALL_DESTOY_SPACE_FOR_SPACES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.remove(&self.as_raw());
|
||||
unsafe { openxr::Space::reference_from_raw(session.clone(), self.as_raw_openxr_space()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt(x: sys::Result) -> openxr::Result<sys::Result> {
|
||||
if x.into_raw() >= 0 {
|
||||
Ok(x)
|
||||
} else {
|
||||
Err(x)
|
||||
}
|
||||
}
|
||||
unsafe fn create_view(flags: openxr::ViewStateFlags, raw: &MaybeUninit<sys::View>) -> openxr::View {
|
||||
// Applications *must* not read invalid parts of a poses, i.e. they may be uninitialized
|
||||
let ptr = raw.as_ptr();
|
||||
openxr::View {
|
||||
pose: openxr::Posef {
|
||||
orientation: flags
|
||||
.contains(sys::ViewStateFlags::ORIENTATION_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).pose.orientation))
|
||||
.unwrap_or_default(),
|
||||
position: flags
|
||||
.contains(sys::ViewStateFlags::POSITION_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).pose.position))
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
fov: *ptr::addr_of!((*ptr).fov),
|
||||
}
|
||||
}
|
||||
unsafe fn create_space_location(raw: &MaybeUninit<sys::SpaceLocation>) -> openxr::SpaceLocation {
|
||||
// Applications *must* not read invalid parts of a pose, i.e. they may be uninitialized
|
||||
let ptr = raw.as_ptr();
|
||||
let flags = *ptr::addr_of!((*ptr).location_flags);
|
||||
openxr::SpaceLocation {
|
||||
location_flags: flags,
|
||||
pose: openxr::Posef {
|
||||
orientation: flags
|
||||
.contains(sys::SpaceLocationFlags::ORIENTATION_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).pose.orientation))
|
||||
.unwrap_or_default(),
|
||||
position: flags
|
||||
.contains(sys::SpaceLocationFlags::POSITION_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).pose.position))
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
unsafe fn create_space_velocity(raw: &MaybeUninit<sys::SpaceVelocity>) -> openxr::SpaceVelocity {
|
||||
// Applications *must* not read invalid velocities, i.e. they may be uninitialized
|
||||
let ptr = raw.as_ptr();
|
||||
let flags = *ptr::addr_of!((*ptr).velocity_flags);
|
||||
openxr::SpaceVelocity {
|
||||
velocity_flags: flags,
|
||||
linear_velocity: flags
|
||||
.contains(sys::SpaceVelocityFlags::LINEAR_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).linear_velocity))
|
||||
.unwrap_or_default(),
|
||||
angular_velocity: flags
|
||||
.contains(sys::SpaceVelocityFlags::ANGULAR_VALID)
|
||||
.then(|| *ptr::addr_of!((*ptr).angular_velocity))
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
fn get_arr_init<T: Copy>(
|
||||
init: T,
|
||||
mut getter: impl FnMut(u32, &mut u32, *mut T) -> sys::Result,
|
||||
) -> openxr::Result<Vec<T>> {
|
||||
let mut output = 0;
|
||||
cvt(getter(0, &mut output, std::ptr::null_mut()))?;
|
||||
let mut buffer = vec![init; output as usize];
|
||||
loop {
|
||||
match cvt(getter(output, &mut output, buffer.as_mut_ptr() as _)) {
|
||||
Ok(_) => {
|
||||
buffer.truncate(output as usize);
|
||||
return Ok(buffer);
|
||||
}
|
||||
Err(sys::Result::ERROR_SIZE_INSUFFICIENT) => {
|
||||
buffer.resize(output as usize, init);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
crates/bevy_openxr/src/openxr/types.rs
Normal file
98
crates/bevy_openxr/src/openxr/types.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::error::OxrError;
|
||||
use crate::graphics::{GraphicsExt, GraphicsType, GraphicsWrap};
|
||||
|
||||
pub use crate::openxr::exts::OxrExtensions;
|
||||
|
||||
pub use openxr::{EnvironmentBlendMode, SwapchainCreateFlags, SwapchainUsageFlags};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, OxrError>;
|
||||
|
||||
/// A container for all required graphics objects needed for a bevy app.
|
||||
pub struct WgpuGraphics(
|
||||
pub wgpu::Device,
|
||||
pub wgpu::Queue,
|
||||
pub wgpu::AdapterInfo,
|
||||
pub wgpu::Adapter,
|
||||
pub wgpu::Instance,
|
||||
);
|
||||
|
||||
/// A version number that can be stored inside of a u32
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Version(pub u8, pub u8, pub u16);
|
||||
|
||||
impl Version {
|
||||
/// Bevy's version number
|
||||
pub const BEVY: Self = Self(0, 13, 0);
|
||||
|
||||
pub const fn to_u32(self) -> u32 {
|
||||
let major = (self.0 as u32) << 24;
|
||||
let minor = (self.1 as u32) << 16;
|
||||
self.2 as u32 | major | minor
|
||||
}
|
||||
}
|
||||
|
||||
/// Info needed about an app for OpenXR
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppInfo {
|
||||
pub name: Cow<'static, str>,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
impl AppInfo {
|
||||
/// The default app info for a generic bevy app
|
||||
pub const BEVY: Self = Self {
|
||||
name: Cow::Borrowed("Bevy"),
|
||||
version: Version::BEVY,
|
||||
};
|
||||
}
|
||||
|
||||
impl Default for AppInfo {
|
||||
fn default() -> Self {
|
||||
Self::BEVY
|
||||
}
|
||||
}
|
||||
|
||||
/// Info needed to create a swapchain.
|
||||
/// This is an API agnostic version of [openxr::SwapchainCreateInfo] used for some of this library's functions
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct SwapchainCreateInfo {
|
||||
pub create_flags: SwapchainCreateFlags,
|
||||
pub usage_flags: SwapchainUsageFlags,
|
||||
pub format: wgpu::TextureFormat,
|
||||
pub sample_count: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub face_count: u32,
|
||||
pub array_size: u32,
|
||||
pub mip_count: u32,
|
||||
}
|
||||
|
||||
impl<G: GraphicsExt> TryFrom<SwapchainCreateInfo> for openxr::SwapchainCreateInfo<G> {
|
||||
type Error = OxrError;
|
||||
|
||||
fn try_from(value: SwapchainCreateInfo) -> Result<Self> {
|
||||
Ok(openxr::SwapchainCreateInfo {
|
||||
create_flags: value.create_flags,
|
||||
usage_flags: value.usage_flags,
|
||||
format: G::from_wgpu_format(value.format)
|
||||
.ok_or(OxrError::UnsupportedTextureFormat(value.format))?,
|
||||
sample_count: value.sample_count,
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
face_count: value.face_count,
|
||||
array_size: value.array_size,
|
||||
mip_count: value.mip_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Info needed to create a session. Mostly contains graphics info.
|
||||
/// This is an API agnostic version of [openxr::Graphics::SessionCreateInfo] used for some of this library's functions
|
||||
#[derive(Clone)]
|
||||
pub struct SessionCreateInfo(pub GraphicsWrap<Self>);
|
||||
|
||||
impl GraphicsType for SessionCreateInfo {
|
||||
type Inner<G: GraphicsExt> = G::SessionCreateInfo;
|
||||
}
|
||||
16
crates/bevy_webxr/Cargo.toml
Normal file
16
crates/bevy_webxr/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "bevy_mod_webxr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# bevy can't be placed behind target or proc macros won't work properly
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
|
||||
# all dependencies are placed under this since on anything but wasm, this crate is completely empty
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
thiserror = "1.0.57"
|
||||
wgpu = "0.19.3"
|
||||
wgpu-hal = "0.19.3"
|
||||
|
||||
bevy_mod_xr.path = "../bevy_xr"
|
||||
2
crates/bevy_webxr/README.md
Normal file
2
crates/bevy_webxr/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Bevy WebXR
|
||||
currently not yet in a working state
|
||||
5
crates/bevy_webxr/src/lib.rs
Normal file
5
crates/bevy_webxr/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod webxr;
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub use webxr::*;
|
||||
1
crates/bevy_webxr/src/webxr/mod.rs
Normal file
1
crates/bevy_webxr/src/webxr/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
9
crates/bevy_xr/Cargo.toml
Normal file
9
crates/bevy_xr/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_mod_xr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
107
crates/bevy_xr/src/actions.rs
Normal file
107
crates/bevy_xr/src/actions.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use std::{any::TypeId, marker::PhantomData};
|
||||
|
||||
use bevy::app::{App, Plugin};
|
||||
use bevy::ecs::system::Resource;
|
||||
use bevy::math::Vec2;
|
||||
|
||||
pub struct ActionPlugin<A: Action>(PhantomData<A>);
|
||||
|
||||
impl<A: Action> Default for ActionPlugin<A> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> Plugin for ActionPlugin<A> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<ActionList>()
|
||||
.init_resource::<ActionState<A>>();
|
||||
app.world.resource_mut::<ActionList>().0.push(A::info());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum ActionType {
|
||||
Bool,
|
||||
Float,
|
||||
Vector,
|
||||
}
|
||||
|
||||
pub trait ActionTy: Send + Sync + Default + Clone + Copy {
|
||||
const TYPE: ActionType;
|
||||
}
|
||||
|
||||
impl ActionTy for bool {
|
||||
const TYPE: ActionType = ActionType::Bool;
|
||||
}
|
||||
|
||||
impl ActionTy for f32 {
|
||||
const TYPE: ActionType = ActionType::Float;
|
||||
}
|
||||
|
||||
impl ActionTy for Vec2 {
|
||||
const TYPE: ActionType = ActionType::Float;
|
||||
}
|
||||
|
||||
pub trait Action: Send + Sync + 'static {
|
||||
type ActionType: ActionTy;
|
||||
|
||||
fn info() -> ActionInfo;
|
||||
}
|
||||
|
||||
pub struct ActionInfo {
|
||||
pub pretty_name: &'static str,
|
||||
pub name: &'static str,
|
||||
pub action_type: ActionType,
|
||||
pub type_id: TypeId,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ActionList(pub Vec<ActionInfo>);
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ActionState<A: Action> {
|
||||
previous_state: A::ActionType,
|
||||
current_state: A::ActionType,
|
||||
}
|
||||
|
||||
impl<A: Action> Default for ActionState<A> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
previous_state: Default::default(),
|
||||
current_state: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> ActionState<A> {
|
||||
pub fn current_state(&self) -> A::ActionType {
|
||||
self.current_state
|
||||
}
|
||||
|
||||
pub fn previous_state(&self) -> A::ActionType {
|
||||
self.previous_state
|
||||
}
|
||||
|
||||
pub fn set(&mut self, state: A::ActionType) {
|
||||
self.previous_state = std::mem::replace(&mut self.current_state, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action<ActionType = bool>> ActionState<A> {
|
||||
pub fn pressed(&self) -> bool {
|
||||
self.current_state
|
||||
}
|
||||
|
||||
pub fn just_pressed(&self) -> bool {
|
||||
self.previous_state == false && self.current_state == true
|
||||
}
|
||||
|
||||
pub fn just_released(&self) -> bool {
|
||||
self.previous_state == true && self.current_state == false
|
||||
}
|
||||
|
||||
pub fn press(&mut self) {
|
||||
self.current_state = true
|
||||
}
|
||||
}
|
||||
134
crates/bevy_xr/src/camera.rs
Normal file
134
crates/bevy_xr/src/camera.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use bevy::app::{App, Plugin, PostUpdate};
|
||||
use bevy::core_pipeline::core_3d::graph::Core3d;
|
||||
use bevy::core_pipeline::core_3d::Camera3d;
|
||||
use bevy::core_pipeline::tonemapping::{DebandDither, Tonemapping};
|
||||
use bevy::ecs::bundle::Bundle;
|
||||
use bevy::ecs::component::Component;
|
||||
use bevy::ecs::reflect::ReflectComponent;
|
||||
use bevy::ecs::schedule::IntoSystemConfigs;
|
||||
use bevy::math::{Mat4, Vec3A};
|
||||
use bevy::reflect::std_traits::ReflectDefault;
|
||||
use bevy::reflect::Reflect;
|
||||
use bevy::render::camera::{
|
||||
Camera, CameraMainTextureUsages, CameraProjection, CameraProjectionPlugin, CameraRenderGraph,
|
||||
Exposure,
|
||||
};
|
||||
use bevy::render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
||||
use bevy::render::primitives::Frustum;
|
||||
use bevy::render::view::{update_frusta, ColorGrading, VisibilitySystems, VisibleEntities};
|
||||
use bevy::transform::components::{GlobalTransform, Transform};
|
||||
use bevy::transform::TransformSystem;
|
||||
|
||||
pub struct XrCameraPlugin;
|
||||
|
||||
impl Plugin for XrCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(CameraProjectionPlugin::<XrProjection>::default());
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_frusta::<XrProjection>
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.before(VisibilitySystems::UpdatePerspectiveFrusta),
|
||||
);
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<XrProjection>::default(),
|
||||
ExtractComponentPlugin::<XrCamera>::default(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Component, Reflect, ExtractComponent)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct XrProjection {
|
||||
pub projection_matrix: Mat4,
|
||||
pub near: f32,
|
||||
}
|
||||
|
||||
impl Default for XrProjection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
near: 0.1,
|
||||
projection_matrix: Mat4::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Marker component for an XR view. It is the backends responsibility to update this.
|
||||
#[derive(Clone, Copy, Component, ExtractComponent, Debug, Default)]
|
||||
pub struct XrCamera(pub u32);
|
||||
|
||||
impl CameraProjection for XrProjection {
|
||||
fn get_projection_matrix(&self) -> Mat4 {
|
||||
self.projection_matrix
|
||||
}
|
||||
|
||||
fn update(&mut self, _width: f32, _height: f32) {}
|
||||
|
||||
fn far(&self) -> f32 {
|
||||
let far = self.projection_matrix.to_cols_array()[14]
|
||||
/ (self.projection_matrix.to_cols_array()[10] + 1.0);
|
||||
|
||||
far
|
||||
}
|
||||
|
||||
// TODO calculate this properly
|
||||
fn get_frustum_corners(&self, _z_near: f32, _z_far: f32) -> [Vec3A; 8] {
|
||||
let ndc_corners = [
|
||||
Vec3A::new(1.0, -1.0, 1.0), // Bottom-right far
|
||||
Vec3A::new(1.0, 1.0, 1.0), // Top-right far
|
||||
Vec3A::new(-1.0, 1.0, 1.0), // Top-left far
|
||||
Vec3A::new(-1.0, -1.0, 1.0), // Bottom-left far
|
||||
Vec3A::new(1.0, -1.0, -1.0), // Bottom-right near
|
||||
Vec3A::new(1.0, 1.0, -1.0), // Top-right near
|
||||
Vec3A::new(-1.0, 1.0, -1.0), // Top-left near
|
||||
Vec3A::new(-1.0, -1.0, -1.0), // Bottom-left near
|
||||
];
|
||||
|
||||
let mut view_space_corners = [Vec3A::ZERO; 8];
|
||||
let inverse_matrix = self.projection_matrix.inverse();
|
||||
for (i, corner) in ndc_corners.into_iter().enumerate() {
|
||||
view_space_corners[i] = inverse_matrix.transform_point3a(corner);
|
||||
}
|
||||
|
||||
view_space_corners
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct XrCameraBundle {
|
||||
pub camera: Camera,
|
||||
pub camera_render_graph: CameraRenderGraph,
|
||||
pub 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 exposure: Exposure,
|
||||
pub main_texture_usages: CameraMainTextureUsages,
|
||||
pub view: XrCamera,
|
||||
}
|
||||
|
||||
impl Default for XrCameraBundle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
camera_render_graph: CameraRenderGraph::new(Core3d),
|
||||
camera: Default::default(),
|
||||
projection: Default::default(),
|
||||
visible_entities: Default::default(),
|
||||
frustum: Default::default(),
|
||||
transform: Default::default(),
|
||||
global_transform: Default::default(),
|
||||
camera_3d: Default::default(),
|
||||
tonemapping: Default::default(),
|
||||
color_grading: Default::default(),
|
||||
exposure: Default::default(),
|
||||
main_texture_usages: Default::default(),
|
||||
dither: DebandDither::Enabled,
|
||||
view: XrCamera(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
143
crates/bevy_xr/src/hands.rs
Normal file
143
crates/bevy_xr/src/hands.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use bevy::{
|
||||
ecs::{component::Component, entity::Entity},
|
||||
math::bool,
|
||||
prelude::{Deref, DerefMut},
|
||||
};
|
||||
pub const HAND_JOINT_COUNT: usize = 26;
|
||||
|
||||
#[derive(Clone, Copy, Component, Debug)]
|
||||
pub struct LeftHand;
|
||||
|
||||
#[derive(Clone, Copy, Component, Debug)]
|
||||
pub struct RightHand;
|
||||
|
||||
#[derive(Deref, DerefMut, Component, Clone, Copy)]
|
||||
pub struct XrHandBoneEntities(pub [Entity; HAND_JOINT_COUNT]);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Component, Debug, DerefMut, Deref)]
|
||||
pub struct HandBoneRadius(pub f32);
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Component, Debug)]
|
||||
pub enum HandBone {
|
||||
Palm = 0,
|
||||
Wrist = 1,
|
||||
ThumbMetacarpal = 2,
|
||||
ThumbProximal = 3,
|
||||
ThumbDistal = 4,
|
||||
ThumbTip = 5,
|
||||
IndexMetacarpal = 6,
|
||||
IndexProximal = 7,
|
||||
IndexIntermediate = 8,
|
||||
IndexDistal = 9,
|
||||
IndexTip = 10,
|
||||
MiddleMetacarpal = 11,
|
||||
MiddleProximal = 12,
|
||||
MiddleIntermediate = 13,
|
||||
MiddleDistal = 14,
|
||||
MiddleTip = 15,
|
||||
RingMetacarpal = 16,
|
||||
RingProximal = 17,
|
||||
RingIntermediate = 18,
|
||||
RingDistal = 19,
|
||||
RingTip = 20,
|
||||
LittleMetacarpal = 21,
|
||||
LittleProximal = 22,
|
||||
LittleIntermediate = 23,
|
||||
LittleDistal = 24,
|
||||
LittleTip = 25,
|
||||
}
|
||||
|
||||
impl HandBone {
|
||||
pub const fn is_metacarpal(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::ThumbMetacarpal
|
||||
| HandBone::IndexMetacarpal
|
||||
| HandBone::MiddleMetacarpal
|
||||
| HandBone::RingMetacarpal
|
||||
| HandBone::LittleMetacarpal
|
||||
)
|
||||
}
|
||||
pub const fn is_thumb(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::ThumbMetacarpal
|
||||
| HandBone::ThumbProximal
|
||||
| HandBone::ThumbDistal
|
||||
| HandBone::ThumbTip
|
||||
)
|
||||
}
|
||||
pub const fn is_index(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::IndexMetacarpal
|
||||
| HandBone::IndexProximal
|
||||
| HandBone::IndexIntermediate
|
||||
| HandBone::IndexDistal
|
||||
| HandBone::IndexTip
|
||||
)
|
||||
}
|
||||
pub const fn is_middle(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::MiddleMetacarpal
|
||||
| HandBone::MiddleProximal
|
||||
| HandBone::MiddleIntermediate
|
||||
| HandBone::MiddleDistal
|
||||
| HandBone::MiddleTip
|
||||
)
|
||||
}
|
||||
pub const fn is_ring(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::RingMetacarpal
|
||||
| HandBone::RingProximal
|
||||
| HandBone::RingIntermediate
|
||||
| HandBone::RingDistal
|
||||
| HandBone::RingTip
|
||||
)
|
||||
}
|
||||
pub const fn is_little(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
HandBone::LittleMetacarpal
|
||||
| HandBone::LittleProximal
|
||||
| HandBone::LittleIntermediate
|
||||
| HandBone::LittleDistal
|
||||
| HandBone::LittleTip
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
]
|
||||
}
|
||||
}
|
||||
6
crates/bevy_xr/src/lib.rs
Normal file
6
crates/bevy_xr/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod actions;
|
||||
pub mod camera;
|
||||
pub mod hands;
|
||||
pub mod session;
|
||||
pub mod types;
|
||||
pub mod spaces;
|
||||
338
crates/bevy_xr/src/session.rs
Normal file
338
crates/bevy_xr/src/session.rs
Normal file
@@ -0,0 +1,338 @@
|
||||
use bevy::app::{AppExit, MainScheduleOrder};
|
||||
use bevy::ecs::schedule::ScheduleLabel;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin};
|
||||
use bevy::render::{Render, RenderApp, RenderSet};
|
||||
|
||||
/// Event sent to instruct backends to create an XR session. Only works when the [`XrState`] is [`Available`](XrState::Available).
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrCreateSessionEvent;
|
||||
|
||||
/// A schedule thats ran whenever an [`XrCreateSessionEvent`] is recieved while the [`XrState`] is [`Available`](XrState::Available)
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrCreateSession;
|
||||
|
||||
/// Event sent when [`XrCreateSession`] is ran
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrSessionCreatedEvent;
|
||||
|
||||
/// Event sent to instruct backends to destroy an XR session. Only works when the [`XrState`] is [`Exiting`](XrState::Exiting).
|
||||
/// If you would like to request that a running session be destroyed, send the [`XrRequestExitEvent`] instead.
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrDestroySessionEvent;
|
||||
|
||||
/// Resource flag thats inserted into the world and extracted to the render world to inform any session resources in the render world to drop.
|
||||
#[derive(Resource, ExtractResource, Clone, Copy, Default)]
|
||||
pub struct XrDestroySessionRender;
|
||||
|
||||
/// Schedule thats ran whenever an [`XrDestroySessionEvent`] is recieved while the [`XrState`] is [`Exiting`](XrState::Exiting).
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrDestroySession;
|
||||
|
||||
/// Event sent to instruct backends to begin an XR session. Only works when the [`XrState`] is [`Ready`](XrState::Ready).
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrBeginSessionEvent;
|
||||
|
||||
/// Schedule thats ran whenever an [`XrBeginSessionEvent`] is recieved while the [`XrState`] is [`Ready`](XrState::Ready).
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrBeginSession;
|
||||
|
||||
/// Event sent to backends to end an XR session. Only works when the [`XrState`] is [`Stopping`](XrState::Stopping).
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrEndSessionEvent;
|
||||
|
||||
/// Schedule thats rna whenever an [`XrEndSessionEvent`] is recieved while the [`XrState`] is [`Stopping`](XrState::Stopping).
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrEndSession;
|
||||
|
||||
/// Event sent to backends to request the [`XrState`] proceed to [`Exiting`](XrState::Exiting) and for the session to be exited. Can be called at any time a session exists.
|
||||
#[derive(Event, Clone, Copy, Default)]
|
||||
pub struct XrRequestExitEvent;
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrRequestExit;
|
||||
|
||||
/// Schedule ran before [`First`] to handle XR events.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
|
||||
pub struct XrFirst;
|
||||
|
||||
/// System set for systems related to handling XR session events and updating the [`XrState`]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub struct XrHandleEvents;
|
||||
|
||||
/// System sets ran in the render world for XR.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub enum XrRenderSet {
|
||||
/// Ran before [`XrRenderSet::PreRender`] but after [`RenderSet::ExtractCommands`].
|
||||
HandleEvents,
|
||||
/// For any XR systems needing to be ran before rendering begins.
|
||||
/// Ran after [`XrRenderSet::HandleEvents`] but before every render set except [`RenderSet::ExtractCommands`].
|
||||
PreRender,
|
||||
/// For any XR systems needing to be ran after [`RenderSet::Render`] but before [`RenderSet::Cleanup`].
|
||||
PostRender,
|
||||
}
|
||||
|
||||
/// The root transform's global position for late latching in the render world.
|
||||
#[derive(ExtractResource, Resource, Clone, Copy, Default)]
|
||||
pub struct XrRootTransform(pub GlobalTransform);
|
||||
|
||||
/// Component used to specify the entity we should use as the tracking root.
|
||||
#[derive(Component)]
|
||||
pub struct XrTrackingRoot;
|
||||
|
||||
pub struct XrSessionPlugin {
|
||||
pub auto_handle: bool,
|
||||
}
|
||||
|
||||
impl Plugin for XrSessionPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let mut xr_first = Schedule::new(XrFirst);
|
||||
xr_first.set_executor_kind(bevy::ecs::schedule::ExecutorKind::Simple);
|
||||
app.add_event::<XrCreateSessionEvent>()
|
||||
.add_event::<XrDestroySessionEvent>()
|
||||
.add_event::<XrBeginSessionEvent>()
|
||||
.add_event::<XrEndSessionEvent>()
|
||||
.add_event::<XrRequestExitEvent>()
|
||||
.add_event::<XrStateChanged>()
|
||||
.add_event::<XrSessionCreatedEvent>()
|
||||
.init_schedule(XrCreateSession)
|
||||
.init_schedule(XrDestroySession)
|
||||
.init_schedule(XrBeginSession)
|
||||
.init_schedule(XrEndSession)
|
||||
.init_schedule(XrRequestExit)
|
||||
.add_schedule(xr_first)
|
||||
.add_systems(
|
||||
XrFirst,
|
||||
(
|
||||
exits_session_on_app_exit
|
||||
.run_if(on_event::<AppExit>())
|
||||
.run_if(session_created),
|
||||
reset_per_frame_resources,
|
||||
run_xr_create_session
|
||||
.run_if(state_equals(XrState::Available))
|
||||
.run_if(on_event::<XrCreateSessionEvent>()),
|
||||
run_xr_destroy_session
|
||||
.run_if(state_matches!(XrState::Exiting { .. }))
|
||||
.run_if(on_event::<XrDestroySessionEvent>()),
|
||||
run_xr_begin_session
|
||||
.run_if(state_equals(XrState::Ready))
|
||||
.run_if(on_event::<XrBeginSessionEvent>()),
|
||||
run_xr_end_session
|
||||
.run_if(state_equals(XrState::Stopping))
|
||||
.run_if(on_event::<XrEndSessionEvent>()),
|
||||
run_xr_request_exit
|
||||
.run_if(session_created)
|
||||
.run_if(on_event::<XrRequestExitEvent>()),
|
||||
)
|
||||
.chain()
|
||||
.in_set(XrHandleEvents),
|
||||
);
|
||||
|
||||
app.world
|
||||
.resource_mut::<MainScheduleOrder>()
|
||||
.labels
|
||||
.insert(0, XrFirst.intern());
|
||||
|
||||
if self.auto_handle {
|
||||
app.add_systems(PreUpdate, auto_handle_session);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if app.get_sub_app(RenderApp).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
app.add_plugins((
|
||||
ExtractResourcePlugin::<XrState>::default(),
|
||||
ExtractResourcePlugin::<XrDestroySessionRender>::default(),
|
||||
ExtractResourcePlugin::<XrRootTransform>::default(),
|
||||
))
|
||||
.init_resource::<XrRootTransform>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
update_root_transform.after(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.add_systems(
|
||||
XrFirst,
|
||||
exits_session_on_app_exit
|
||||
.before(XrHandleEvents)
|
||||
.run_if(on_event::<AppExit>().and_then(session_running)),
|
||||
);
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app
|
||||
.init_schedule(XrDestroySession)
|
||||
.init_resource::<XrRootTransform>()
|
||||
.configure_sets(
|
||||
Render,
|
||||
(XrRenderSet::HandleEvents, XrRenderSet::PreRender).chain(),
|
||||
)
|
||||
.configure_sets(
|
||||
Render,
|
||||
XrRenderSet::HandleEvents.after(RenderSet::ExtractCommands),
|
||||
)
|
||||
.configure_sets(
|
||||
Render,
|
||||
XrRenderSet::PreRender
|
||||
.before(RenderSet::ManageViews)
|
||||
.before(RenderSet::PrepareAssets),
|
||||
)
|
||||
.configure_sets(
|
||||
Render,
|
||||
XrRenderSet::PostRender
|
||||
.after(RenderSet::Render)
|
||||
.before(RenderSet::Cleanup),
|
||||
)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
run_xr_destroy_session
|
||||
.run_if(resource_exists::<XrDestroySessionRender>)
|
||||
.in_set(XrRenderSet::HandleEvents),
|
||||
reset_per_frame_resources.in_set(RenderSet::Cleanup),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn exits_session_on_app_exit(mut request_exit: EventWriter<XrRequestExitEvent>) {
|
||||
request_exit.send_default();
|
||||
}
|
||||
|
||||
/// Event sent by backends whenever [`XrState`] is changed.
|
||||
#[derive(Event, Clone, Copy, Deref)]
|
||||
pub struct XrStateChanged(pub XrState);
|
||||
|
||||
/// A resource in the main world and render world representing the current session state.
|
||||
#[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum XrState {
|
||||
/// An XR session is not available here
|
||||
Unavailable,
|
||||
/// An XR session is available and ready to be created with an [`XrCreateSessionEvent`].
|
||||
Available,
|
||||
/// An XR session is created but not ready to begin. Backends are not required to use this state.
|
||||
Idle,
|
||||
/// An XR session has been created and is ready to start rendering with an [`XrBeginSessionEvent`].
|
||||
Ready,
|
||||
/// The XR session is running and can be stopped with an [`XrEndSessionEvent`].
|
||||
Running,
|
||||
/// The runtime has requested that the session should be ended with an [`XrEndSessionEvent`].
|
||||
Stopping,
|
||||
/// The XR session should be destroyed with an [`XrDestroySessionEvent`].
|
||||
Exiting {
|
||||
/// Whether we should automatically restart the session
|
||||
should_restart: bool,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn run_xr_create_session(world: &mut World) {
|
||||
world.run_schedule(XrCreateSession);
|
||||
world.send_event(XrSessionCreatedEvent);
|
||||
}
|
||||
|
||||
pub fn run_xr_destroy_session(world: &mut World) {
|
||||
world.run_schedule(XrDestroySession);
|
||||
world.insert_resource(XrDestroySessionRender);
|
||||
}
|
||||
|
||||
pub fn run_xr_begin_session(world: &mut World) {
|
||||
world.run_schedule(XrBeginSession);
|
||||
}
|
||||
|
||||
pub fn run_xr_end_session(world: &mut World) {
|
||||
world.run_schedule(XrEndSession);
|
||||
}
|
||||
|
||||
pub fn run_xr_request_exit(world: &mut World) {
|
||||
world.run_schedule(XrRequestExit);
|
||||
}
|
||||
|
||||
pub fn reset_per_frame_resources(world: &mut World) {
|
||||
world.remove_resource::<XrDestroySessionRender>();
|
||||
}
|
||||
|
||||
pub fn auto_handle_session(
|
||||
mut state_changed: EventReader<XrStateChanged>,
|
||||
mut create_session: EventWriter<XrCreateSessionEvent>,
|
||||
mut begin_session: EventWriter<XrBeginSessionEvent>,
|
||||
mut end_session: EventWriter<XrEndSessionEvent>,
|
||||
mut destroy_session: EventWriter<XrDestroySessionEvent>,
|
||||
) {
|
||||
for XrStateChanged(state) in state_changed.read() {
|
||||
match state {
|
||||
XrState::Available => {
|
||||
create_session.send_default();
|
||||
}
|
||||
XrState::Ready => {
|
||||
begin_session.send_default();
|
||||
}
|
||||
XrState::Stopping => {
|
||||
end_session.send_default();
|
||||
}
|
||||
XrState::Exiting { .. } => {
|
||||
destroy_session.send_default();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_root_transform(
|
||||
mut root_transform: ResMut<XrRootTransform>,
|
||||
root: Query<&GlobalTransform, With<XrTrackingRoot>>,
|
||||
) {
|
||||
let Ok(transform) = root.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
root_transform.0 = *transform;
|
||||
}
|
||||
|
||||
/// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`].
|
||||
pub fn status_changed_to(
|
||||
status: XrState,
|
||||
) -> impl FnMut(EventReader<XrStateChanged>) -> bool + Clone {
|
||||
move |mut reader: EventReader<XrStateChanged>| {
|
||||
reader.read().any(|new_status| new_status.0 == status)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrState`] exists and isn't [`Unavailable`](XrStatus::Unavailable).
|
||||
pub fn session_available(status: Option<Res<XrState>>) -> bool {
|
||||
status.is_some_and(|s| *s != XrState::Unavailable)
|
||||
}
|
||||
|
||||
pub fn session_created(status: Option<Res<XrState>>) -> bool {
|
||||
!matches!(
|
||||
status.as_deref(),
|
||||
Some(XrState::Unavailable | XrState::Available) | None
|
||||
)
|
||||
}
|
||||
|
||||
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is ready or running
|
||||
pub fn session_ready_or_running(status: Option<Res<XrState>>) -> bool {
|
||||
matches!(status.as_deref(), Some(XrState::Ready | XrState::Running))
|
||||
}
|
||||
|
||||
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is running
|
||||
pub fn session_running(status: Option<Res<XrState>>) -> bool {
|
||||
matches!(status.as_deref(), Some(XrState::Running))
|
||||
}
|
||||
|
||||
/// A function that returns a [`Condition`](bevy::ecs::schedule::Condition) system that says if the [`XrState`] is in a specific state
|
||||
pub fn state_equals(status: XrState) -> impl FnMut(Option<Res<XrState>>) -> bool {
|
||||
move |state: Option<Res<XrState>>| state.is_some_and(|s| *s == status)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! state_matches {
|
||||
($match:pat) => {
|
||||
(|state: Option<Res<XrState>>| core::matches!(state.as_deref(), Some($match)))
|
||||
};
|
||||
}
|
||||
|
||||
use bevy::transform::TransformSystem;
|
||||
pub use state_matches;
|
||||
44
crates/bevy_xr/src/spaces.rs
Normal file
44
crates/bevy_xr/src/spaces.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{extract_component::ExtractComponent, extract_resource::ExtractResource},
|
||||
};
|
||||
|
||||
use crate::types::XrPose;
|
||||
|
||||
/// Any Spaces will be invalid after the owning session exits
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Component, ExtractComponent)]
|
||||
pub struct XrSpace(u64);
|
||||
|
||||
// Does repr(transparent) even make sense here?
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Reflect, Debug, Component, ExtractComponent, Default, Deref, DerefMut,
|
||||
)]
|
||||
pub struct XrSpatialOffset(pub XrPose);
|
||||
|
||||
#[derive(Event, Clone, Copy, Deref, DerefMut)]
|
||||
pub struct XrDestroySpace(pub XrSpace);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Component, Deref, DerefMut, ExtractComponent,
|
||||
)]
|
||||
pub struct XrReferenceSpace(pub XrSpace);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(
|
||||
Clone, Copy, Hash, PartialEq, Eq, Reflect, Debug, Resource, Deref, DerefMut, ExtractResource,
|
||||
)]
|
||||
pub struct XrPrimaryReferenceSpace(pub XrReferenceSpace);
|
||||
|
||||
impl XrSpace {
|
||||
/// # Safety
|
||||
/// only call with known valid handles
|
||||
pub unsafe fn from_raw(handle: u64) -> Self {
|
||||
Self(handle)
|
||||
}
|
||||
pub fn as_raw(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
26
crates/bevy_xr/src/types.rs
Normal file
26
crates/bevy_xr/src/types.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use bevy::{
|
||||
math::{Quat, Vec3},
|
||||
reflect::Reflect,
|
||||
transform::components::Transform,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Reflect, Debug)]
|
||||
pub struct XrPose {
|
||||
pub translation: Vec3,
|
||||
pub rotation: Quat,
|
||||
}
|
||||
|
||||
impl Default for XrPose {
|
||||
fn default() -> Self {
|
||||
Self::IDENTITY
|
||||
}
|
||||
}
|
||||
impl XrPose {
|
||||
pub const IDENTITY: XrPose = XrPose {
|
||||
translation: Vec3::ZERO,
|
||||
rotation: Quat::IDENTITY,
|
||||
};
|
||||
pub const fn to_transform(self) -> Transform {
|
||||
Transform::from_translation(self.translation).with_rotation(self.rotation)
|
||||
}
|
||||
}
|
||||
14
crates/bevy_xr_utils/Cargo.toml
Normal file
14
crates/bevy_xr_utils/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "bevy_xr_utils"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
bevy_mod_xr.path = "../bevy_xr"
|
||||
bevy_mod_openxr.path = "../bevy_openxr"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
openxr = "0.18.0"
|
||||
34
crates/bevy_xr_utils/src/hand_gizmos.rs
Normal file
34
crates/bevy_xr_utils/src/hand_gizmos.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use bevy::{prelude::*, transform::TransformSystem};
|
||||
use bevy_mod_xr::hands::{HandBone, HandBoneRadius};
|
||||
pub struct HandGizmosPlugin;
|
||||
impl Plugin for HandGizmosPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
draw_hand_gizmos.after(TransformSystem::TransformPropagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
fn draw_hand_gizmos(
|
||||
mut gizmos: Gizmos,
|
||||
query: Query<(&GlobalTransform, &HandBone, &HandBoneRadius)>,
|
||||
) {
|
||||
for (transform, bone, radius) in &query {
|
||||
let pose = transform.compute_transform();
|
||||
gizmos.sphere(pose.translation, pose.rotation, **radius, gizmo_color(bone));
|
||||
}
|
||||
}
|
||||
|
||||
fn gizmo_color(bone: &HandBone) -> Color {
|
||||
match bone {
|
||||
HandBone::Palm => Color::WHITE,
|
||||
HandBone::Wrist => Color::GRAY,
|
||||
b if b.is_thumb() => Color::RED,
|
||||
b if b.is_index() => Color::ORANGE,
|
||||
b if b.is_middle() => Color::YELLOW,
|
||||
b if b.is_ring() => Color::GREEN,
|
||||
b if b.is_little() => Color::BLUE,
|
||||
// should be impossible to hit
|
||||
_ => Color::rgb(1.0, 0.0, 1.0),
|
||||
}
|
||||
}
|
||||
6
crates/bevy_xr_utils/src/lib.rs
Normal file
6
crates/bevy_xr_utils/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// use bevy::prelude::*;
|
||||
pub mod hand_gizmos;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod xr_utils_actions;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub mod transform_utils;
|
||||
73
crates/bevy_xr_utils/src/transform_utils.rs
Normal file
73
crates/bevy_xr_utils/src/transform_utils.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::{
|
||||
helper_traits::{ToQuat, ToVec3},
|
||||
resources::OxrViews,
|
||||
};
|
||||
use bevy_mod_xr::session::XrTrackingRoot;
|
||||
|
||||
pub struct TransformUtilitiesPlugin;
|
||||
|
||||
impl Plugin for TransformUtilitiesPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<SnapToRotation>();
|
||||
app.add_event::<SnapToPosition>();
|
||||
app.add_systems(PostUpdate, handle_transform_events);
|
||||
}
|
||||
}
|
||||
|
||||
//events
|
||||
#[derive(Event, Debug)]
|
||||
pub struct SnapToRotation(pub Quat);
|
||||
|
||||
#[derive(Event, Debug)]
|
||||
pub struct SnapToPosition(pub Vec3);
|
||||
|
||||
pub fn handle_transform_events(
|
||||
mut root_query: Query<&mut Transform, With<XrTrackingRoot>>,
|
||||
views: ResMut<OxrViews>,
|
||||
mut position_reader: EventReader<SnapToPosition>,
|
||||
mut rotation_reader: EventReader<SnapToRotation>,
|
||||
) {
|
||||
let result = root_query.get_single_mut();
|
||||
match result {
|
||||
Ok(mut root_transform) => {
|
||||
let view = views.first();
|
||||
match view {
|
||||
Some(view) => {
|
||||
//we want the view translation with a height of zero for a few calculations
|
||||
let mut view_translation = view.pose.position.to_vec3();
|
||||
view_translation.y = 0.0;
|
||||
|
||||
//position
|
||||
for position in position_reader.read() {
|
||||
root_transform.translation =
|
||||
position.0 - root_transform.rotation.mul_vec3(view_translation);
|
||||
}
|
||||
|
||||
//rotation
|
||||
let root_local = root_transform.translation.clone();
|
||||
let hmd_global =
|
||||
root_transform.rotation.mul_vec3(view_translation) + root_local;
|
||||
let view_rot = view.pose.orientation.to_quat();
|
||||
let root_rot = root_transform.rotation;
|
||||
let view_global_rotation = root_rot.mul_quat(view_rot).normalize();
|
||||
let (global_view_yaw, _pitch, _roll) =
|
||||
view_global_rotation.to_euler(bevy::math::EulerRot::YXZ);
|
||||
let up = Vec3::Y;
|
||||
for rotation in rotation_reader.read() {
|
||||
let (target_yaw, _pitch, _roll) =
|
||||
rotation.0.normalize().to_euler(bevy::math::EulerRot::YXZ);
|
||||
let diff_yaw = target_yaw - global_view_yaw;
|
||||
|
||||
//build a rotation quat?
|
||||
let rotation_quat = Quat::from_axis_angle(up, diff_yaw);
|
||||
//apply rotation this works
|
||||
root_transform.rotate_around(hmd_global, rotation_quat);
|
||||
}
|
||||
}
|
||||
None => debug!("error getting first view"),
|
||||
}
|
||||
}
|
||||
Err(_) => debug!("error getting root transform"),
|
||||
}
|
||||
}
|
||||
426
crates/bevy_xr_utils/src/xr_utils_actions.rs
Normal file
426
crates/bevy_xr_utils/src/xr_utils_actions.rs
Normal file
@@ -0,0 +1,426 @@
|
||||
//! This plugin and module are here to ease the creation of actions withing openxr
|
||||
//! The general idea is any plugin can create entities in startup before XRUtilsActionSystemSet::CreateEvents
|
||||
//! this plugin will then create the neccessary actions sets, actions, and bindings and get them ready for use.
|
||||
//!
|
||||
//! example creating actions
|
||||
//!
|
||||
//! //create a set
|
||||
//! let set = commands
|
||||
//! .spawn((
|
||||
//! XRUtilsActionSet {
|
||||
//! name: "flight".into(),
|
||||
//! pretty_name: "pretty flight set".into(),
|
||||
//! priority: u32::MIN,
|
||||
//! },
|
||||
//! ActiveSet, //marker to indicate we want this synced
|
||||
//! ))
|
||||
//! .id();
|
||||
//! //create an action
|
||||
//! let action = commands
|
||||
//! .spawn((
|
||||
//! XRUtilsAction {
|
||||
//! action_name: "flight_input".into(),
|
||||
//! localized_name: "flight_input_localized".into(),
|
||||
//! action_type: bevy_mod_xr::actions::ActionType::Vector,
|
||||
//! },
|
||||
//! FlightActionMarker, //lets try a marker component
|
||||
//! ))
|
||||
//! .id();
|
||||
//!
|
||||
//! //create a binding
|
||||
//! let binding = commands
|
||||
//! .spawn(XRUtilsBinding {
|
||||
//! profile: "/interaction_profiles/valve/index_controller".into(),
|
||||
//! binding: "/user/hand/right/input/thumbstick".into(),
|
||||
//! })
|
||||
//! .id();
|
||||
//!
|
||||
//! //add action to set, this isnt the best
|
||||
//! //TODO look into a better system
|
||||
//! commands.entity(action).add_child(binding);
|
||||
//! commands.entity(set).add_child(action);
|
||||
//!
|
||||
//! then you can read the action states after XRUtilsActionSystemSet::SyncActionStates
|
||||
//! for example
|
||||
//!
|
||||
//! fn read_action_with_marker_component(
|
||||
//! mut action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>,
|
||||
//! ) {
|
||||
//! //now for the actual checking
|
||||
//! for state in action_query.iter_mut() {
|
||||
//! info!("action state is: {:?}", state);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//!
|
||||
use bevy::prelude::*;
|
||||
use bevy_mod_openxr::{
|
||||
action_binding::OxrSuggestActionBinding, action_set_attaching::OxrAttachActionSet,
|
||||
action_set_syncing::OxrActionSetSyncSet, action_set_syncing::OxrSyncActionSet,
|
||||
resources::OxrInstance, session::OxrSession,
|
||||
};
|
||||
use bevy_mod_xr::session::{session_available, session_running};
|
||||
use openxr::{Path, Vector2f};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct XRUtilsActionsPlugin;
|
||||
impl Plugin for XRUtilsActionsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.configure_sets(
|
||||
Startup,
|
||||
XRUtilsActionSystemSet::CreateEvents.run_if(session_available),
|
||||
);
|
||||
app.configure_sets(
|
||||
PreUpdate,
|
||||
XRUtilsActionSystemSet::SyncActionStates.run_if(session_running),
|
||||
);
|
||||
app.add_systems(
|
||||
Startup,
|
||||
create_openxr_events
|
||||
.in_set(XRUtilsActionSystemSet::CreateEvents)
|
||||
.run_if(session_available),
|
||||
);
|
||||
app.add_systems(Update, sync_active_action_sets.run_if(session_running));
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
sync_and_update_action_states_f32
|
||||
.run_if(session_running)
|
||||
.in_set(XRUtilsActionSystemSet::SyncActionStates)
|
||||
.after(OxrActionSetSyncSet),
|
||||
);
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
sync_and_update_action_states_bool
|
||||
.run_if(session_running)
|
||||
.in_set(XRUtilsActionSystemSet::SyncActionStates)
|
||||
.after(OxrActionSetSyncSet),
|
||||
);
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
sync_and_update_action_states_vector
|
||||
.run_if(session_running)
|
||||
.in_set(XRUtilsActionSystemSet::SyncActionStates)
|
||||
.after(OxrActionSetSyncSet),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_openxr_events(
|
||||
action_sets_query: Query<(&XRUtilsActionSet, &Children, Entity)>,
|
||||
actions_query: Query<(&XRUtilsAction, &Children)>,
|
||||
bindings_query: Query<&XRUtilsBinding>,
|
||||
instance: ResMut<OxrInstance>,
|
||||
mut binding_writer: EventWriter<OxrSuggestActionBinding>,
|
||||
mut attach_writer: EventWriter<OxrAttachActionSet>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
//lets create some sets!
|
||||
for (set, children, id) in action_sets_query.iter() {
|
||||
//create action set
|
||||
let action_set: openxr::ActionSet = instance
|
||||
.create_action_set(&set.name, &set.pretty_name, set.priority)
|
||||
.unwrap();
|
||||
//now that we have the action set we need to put it back onto the entity for later
|
||||
let oxr_action_set = XRUtilsActionSetReference(action_set.clone());
|
||||
commands.entity(id).insert(oxr_action_set);
|
||||
|
||||
//since the actions are made from the sets lets go
|
||||
for &child in children.iter() {
|
||||
//first get the action entity and stuff
|
||||
let (create_action, bindings) = actions_query.get(child).unwrap();
|
||||
//lets create dat action
|
||||
match create_action.action_type {
|
||||
bevy_mod_xr::actions::ActionType::Bool => {
|
||||
let action: openxr::Action<bool> = action_set
|
||||
.create_action::<bool>(
|
||||
&create_action.action_name,
|
||||
&create_action.localized_name,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
//please put this in a function so I dont go crazy
|
||||
//insert a reference for later
|
||||
commands.entity(child).insert((
|
||||
ActionBooleference {
|
||||
action: action.clone(),
|
||||
},
|
||||
XRUtilsActionState::Bool(ActionStateBool {
|
||||
current_state: false,
|
||||
changed_since_last_sync: false,
|
||||
last_change_time: i64::MIN,
|
||||
is_active: false,
|
||||
}),
|
||||
));
|
||||
//since we need actions for bindings lets go!!
|
||||
for &bind in bindings.iter() {
|
||||
//interaction profile
|
||||
//get the binding entity and stuff
|
||||
let create_binding = bindings_query.get(bind).unwrap();
|
||||
let profile = create_binding.profile.clone();
|
||||
//bindings
|
||||
let binding = vec![create_binding.binding.clone()];
|
||||
let sugestion = OxrSuggestActionBinding {
|
||||
action: action.as_raw(),
|
||||
interaction_profile: profile,
|
||||
bindings: binding,
|
||||
};
|
||||
//finally send the suggestion
|
||||
binding_writer.send(sugestion);
|
||||
}
|
||||
}
|
||||
bevy_mod_xr::actions::ActionType::Float => {
|
||||
let action: openxr::Action<f32> = action_set
|
||||
.create_action::<f32>(
|
||||
&create_action.action_name,
|
||||
&create_action.localized_name,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//please put this in a function so I dont go crazy
|
||||
//insert a reference for later
|
||||
commands.entity(child).insert((
|
||||
Actionf32Reference {
|
||||
action: action.clone(),
|
||||
},
|
||||
XRUtilsActionState::Float(ActionStateFloat {
|
||||
current_state: 0.0,
|
||||
changed_since_last_sync: false,
|
||||
last_change_time: i64::MIN,
|
||||
is_active: false,
|
||||
}),
|
||||
));
|
||||
//since we need actions for bindings lets go!!
|
||||
for &bind in bindings.iter() {
|
||||
//interaction profile
|
||||
//get the binding entity and stuff
|
||||
let create_binding = bindings_query.get(bind).unwrap();
|
||||
let profile = create_binding.profile.clone();
|
||||
//bindings
|
||||
let binding = vec![create_binding.binding.clone()];
|
||||
let sugestion = OxrSuggestActionBinding {
|
||||
action: action.as_raw(),
|
||||
interaction_profile: profile,
|
||||
bindings: binding,
|
||||
};
|
||||
//finally send the suggestion
|
||||
binding_writer.send(sugestion);
|
||||
}
|
||||
}
|
||||
bevy_mod_xr::actions::ActionType::Vector => {
|
||||
let action: openxr::Action<Vector2f> = action_set
|
||||
.create_action::<Vector2f>(
|
||||
&create_action.action_name,
|
||||
&create_action.localized_name,
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//please put this in a function so I dont go crazy
|
||||
//insert a reference for later
|
||||
commands.entity(child).insert((
|
||||
ActionVector2fReference {
|
||||
action: action.clone(),
|
||||
},
|
||||
XRUtilsActionState::Vector(ActionStateVector {
|
||||
current_state: [0.0, 0.0],
|
||||
changed_since_last_sync: false,
|
||||
last_change_time: i64::MIN,
|
||||
is_active: false,
|
||||
}),
|
||||
));
|
||||
//since we need actions for bindings lets go!!
|
||||
for &bind in bindings.iter() {
|
||||
//interaction profile
|
||||
//get the binding entity and stuff
|
||||
let create_binding = bindings_query.get(bind).unwrap();
|
||||
let profile = create_binding.profile.clone();
|
||||
//bindings
|
||||
let binding = vec![create_binding.binding.clone()];
|
||||
let sugestion = OxrSuggestActionBinding {
|
||||
action: action.as_raw(),
|
||||
interaction_profile: profile,
|
||||
bindings: binding,
|
||||
};
|
||||
//finally send the suggestion
|
||||
binding_writer.send(sugestion);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
attach_writer.send(OxrAttachActionSet(action_set));
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_active_action_sets(
|
||||
mut sync_set: EventWriter<OxrSyncActionSet>,
|
||||
active_action_set_query: Query<&XRUtilsActionSetReference, With<ActiveSet>>,
|
||||
) {
|
||||
for set in &active_action_set_query {
|
||||
sync_set.send(OxrSyncActionSet(set.0.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_and_update_action_states_f32(
|
||||
session: Res<OxrSession>,
|
||||
mut f32_query: Query<(&Actionf32Reference, &mut XRUtilsActionState)>,
|
||||
) {
|
||||
//now we do the action state for f32
|
||||
for (reference, mut silly_state) in f32_query.iter_mut() {
|
||||
let state = reference.action.state(&session, Path::NULL);
|
||||
match state {
|
||||
Ok(s) => {
|
||||
let new_state = XRUtilsActionState::Float(ActionStateFloat {
|
||||
current_state: s.current_state,
|
||||
changed_since_last_sync: s.changed_since_last_sync,
|
||||
last_change_time: s.last_change_time.as_nanos(),
|
||||
is_active: s.is_active,
|
||||
});
|
||||
|
||||
*silly_state = new_state;
|
||||
}
|
||||
Err(_) => {
|
||||
info!("error getting action state");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_and_update_action_states_bool(
|
||||
session: Res<OxrSession>,
|
||||
mut f32_query: Query<(&ActionBooleference, &mut XRUtilsActionState)>,
|
||||
) {
|
||||
//now we do the action state for f32
|
||||
for (reference, mut silly_state) in f32_query.iter_mut() {
|
||||
let state = reference.action.state(&session, Path::NULL);
|
||||
match state {
|
||||
Ok(s) => {
|
||||
let new_state = XRUtilsActionState::Bool(ActionStateBool {
|
||||
current_state: s.current_state,
|
||||
changed_since_last_sync: s.changed_since_last_sync,
|
||||
last_change_time: s.last_change_time.as_nanos(),
|
||||
is_active: s.is_active,
|
||||
});
|
||||
|
||||
*silly_state = new_state;
|
||||
}
|
||||
Err(_) => {
|
||||
info!("error getting action state");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_and_update_action_states_vector(
|
||||
session: Res<OxrSession>,
|
||||
mut vector_query: Query<(&ActionVector2fReference, &mut XRUtilsActionState)>,
|
||||
) {
|
||||
//now we do the action state for f32
|
||||
for (reference, mut silly_state) in vector_query.iter_mut() {
|
||||
let state = reference.action.state(&session, Path::NULL);
|
||||
match state {
|
||||
Ok(s) => {
|
||||
let new_state = XRUtilsActionState::Vector(ActionStateVector {
|
||||
current_state: [s.current_state.x, s.current_state.y],
|
||||
changed_since_last_sync: s.changed_since_last_sync,
|
||||
last_change_time: s.last_change_time.as_nanos(),
|
||||
is_active: s.is_active,
|
||||
});
|
||||
|
||||
*silly_state = new_state;
|
||||
}
|
||||
Err(_) => {
|
||||
info!("error getting action state");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
|
||||
pub enum XRUtilsActionSystemSet {
|
||||
/// Runs in Startup
|
||||
CreateEvents,
|
||||
/// Runs in PreUpdate
|
||||
SyncActionStates,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRUtilsActionSet {
|
||||
pub name: Cow<'static, str>,
|
||||
pub pretty_name: Cow<'static, str>,
|
||||
pub priority: u32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
pub struct XRUtilsActionSetReference(pub openxr::ActionSet);
|
||||
|
||||
//I want to use this to indicate when an action set is attached
|
||||
// #[derive(Component)]
|
||||
// struct AttachedActionSet;
|
||||
|
||||
//this is used to determine if this set should be synced
|
||||
#[derive(Component)]
|
||||
pub struct ActiveSet;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRUtilsAction {
|
||||
pub action_name: Cow<'static, str>,
|
||||
pub localized_name: Cow<'static, str>,
|
||||
pub action_type: bevy_mod_xr::actions::ActionType,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct XRUtilsBinding {
|
||||
pub profile: Cow<'static, str>,
|
||||
pub binding: Cow<'static, str>,
|
||||
}
|
||||
|
||||
//Prototype action states
|
||||
//TODO refactor this
|
||||
#[derive(Component, Debug)]
|
||||
pub enum XRUtilsActionState {
|
||||
Bool(ActionStateBool),
|
||||
Float(ActionStateFloat),
|
||||
Vector(ActionStateVector),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActionStateBool {
|
||||
pub current_state: bool,
|
||||
pub changed_since_last_sync: bool,
|
||||
pub last_change_time: i64,
|
||||
pub is_active: bool,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ActionStateFloat {
|
||||
pub current_state: f32,
|
||||
pub changed_since_last_sync: bool,
|
||||
pub last_change_time: i64,
|
||||
pub is_active: bool,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct ActionStateVector {
|
||||
pub current_state: [f32; 2],
|
||||
pub changed_since_last_sync: bool,
|
||||
pub last_change_time: i64,
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
//prototype action references
|
||||
//TODO refactor along with action states
|
||||
#[derive(Component)]
|
||||
struct Actionf32Reference {
|
||||
action: openxr::Action<f32>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ActionBooleference {
|
||||
action: openxr::Action<bool>,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ActionVector2fReference {
|
||||
action: openxr::Action<Vector2f>,
|
||||
}
|
||||
3
examples/android/.gitignore
vendored
3
examples/android/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
/target
|
||||
/Cargo.lock
|
||||
/runtime_libs
|
||||
@@ -1,67 +0,0 @@
|
||||
# Bevy OpenXR Android example
|
||||
|
||||
## Setup
|
||||
Get libopenxr_loader.so from the Oculus OpenXR Mobile SDK and add it to `examples/android/runtime_libs/arm64-v8a`
|
||||
https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/
|
||||
`examples/android/runtime_libs/arm64-v8a/libopenxr_loader.so`
|
||||
|
||||
Also, install either `cargo-apk` (marked as deprecated):
|
||||
```sh
|
||||
cargo install cargo-apk
|
||||
```
|
||||
or, install `xbuild` as it supersedes `cargo-apk`. Note that the `--git` is
|
||||
very important here.
|
||||
```sh
|
||||
cargo install --git https://github.com/rust-mobile/xbuild
|
||||
```
|
||||
|
||||
## Run
|
||||
Running on Meta Quest can be done with https://github.com/rust-mobile/cargo-apk.
|
||||
```sh
|
||||
cargo apk run --release
|
||||
```
|
||||
But cargo-apk is deprecated in favour of xbuild https://github.com/rust-mobile/xbuild.
|
||||
```sh
|
||||
# List devices and copy device string "adb:***"
|
||||
x devices
|
||||
|
||||
# Run on this device
|
||||
x run --release --device adb:***
|
||||
```
|
||||
There is [manifest.yaml](./manifest.yaml) example required by xbuild.
|
||||
Interface for this manifest can be found as AndroidConfig struct in https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/config.rs
|
||||
|
||||
## Notes
|
||||
|
||||
### Relase mode
|
||||
More optimisations enabled in Cargo.toml for the release mode.
|
||||
This gives more performance but longer build time.
|
||||
```toml
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
```
|
||||
|
||||
### Cargo apk
|
||||
If you see error like `Error: String `` is not a PID`, try to install cargo apk with a fix in branch.
|
||||
```sh
|
||||
cargo install --git https://github.com/rust-mobile/cargo-apk --branch=adb-logcat-uid
|
||||
```
|
||||
|
||||
### Temporary JNIEnv log
|
||||
This message is logged every frame. It's not yet fixed.
|
||||
```sh
|
||||
I JniUtils-inl: Creating temporary JNIEnv. This is a heavy operation and should be infrequent. To optimize, use JNI AttachCurrentThread on calling threa
|
||||
```
|
||||
|
||||
### Android keystore
|
||||
Release mode requires keystore. See Cargo.toml `package.metadata.android.signing.release`.
|
||||
|
||||
When creating your own apps, make sure to generate your own keystore, rather than using our example one!
|
||||
You can use `keytool` like so:
|
||||
```sh
|
||||
keytool -genkey -v -keystore my-release-key.keystore -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
For more information on key signing and why it's so important, check out this article:
|
||||
https://developer.android.com/studio/publish/app-signing
|
||||
@@ -1,130 +0,0 @@
|
||||
use bevy::color::palettes;
|
||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
||||
use bevy::prelude::*;
|
||||
use bevy::transform::components::Transform;
|
||||
use bevy_oxr::graphics::extensions::XrExtensions;
|
||||
use bevy_oxr::graphics::XrAppInfo;
|
||||
use bevy_oxr::passthrough::{PausePassthrough, ResumePassthrough, XrPassthroughState};
|
||||
use bevy_oxr::xr_init::xr_only;
|
||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
||||
use bevy_oxr::xr_input::hands::HandBone;
|
||||
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
|
||||
use bevy_oxr::xr_input::trackers::{
|
||||
OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
|
||||
};
|
||||
use bevy_oxr::DefaultXrPlugins;
|
||||
|
||||
#[bevy_main]
|
||||
fn main() {
|
||||
let mut xr_extensions = XrExtensions::default();
|
||||
xr_extensions.enable_fb_passthrough();
|
||||
xr_extensions.enable_hand_tracking();
|
||||
App::new()
|
||||
.add_plugins(DefaultXrPlugins {
|
||||
reqeusted_extensions: xr_extensions,
|
||||
app_info: XrAppInfo {
|
||||
name: "Bevy OXR Android Example".into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
// .add_plugins(OpenXrDebugRenderer)
|
||||
.add_plugins(LogDiagnosticsPlugin::default())
|
||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||
.add_plugins(HandInputDebugRenderer)
|
||||
.add_plugins(bevy_oxr::passthrough::EnablePassthroughStartup)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(proto_locomotion, toggle_passthrough).run_if(xr_only()),
|
||||
)
|
||||
.add_systems(Update, debug_hand_render.run_if(xr_only()))
|
||||
.add_systems(Startup, spawn_controllers_example)
|
||||
.insert_resource(PrototypeLocomotionConfig::default())
|
||||
.run();
|
||||
}
|
||||
|
||||
fn debug_hand_render(query: Query<&GlobalTransform, With<HandBone>>, mut gizmos: Gizmos) {
|
||||
for transform in &query {
|
||||
gizmos.sphere(
|
||||
transform.translation(),
|
||||
Quat::IDENTITY,
|
||||
0.01,
|
||||
palettes::css::RED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// plane
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(2.5))),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.3, 0.5, 0.3))),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(Cuboid::from_size(Vec3::splat(0.1)))),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.0, 0.0))),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 1.0),
|
||||
..default()
|
||||
});
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1500.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_controllers_example(mut commands: Commands) {
|
||||
//left hand
|
||||
commands.spawn((
|
||||
OpenXRLeftController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
));
|
||||
//right hand
|
||||
commands.spawn((
|
||||
OpenXRRightController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: make this a vr button
|
||||
fn toggle_passthrough(
|
||||
keys: Res<ButtonInput<KeyCode>>,
|
||||
passthrough_state: Res<XrPassthroughState>,
|
||||
mut resume: EventWriter<ResumePassthrough>,
|
||||
mut pause: EventWriter<PausePassthrough>,
|
||||
) {
|
||||
if keys.just_pressed(KeyCode::Space) {
|
||||
match *passthrough_state {
|
||||
XrPassthroughState::Unsupported => {}
|
||||
XrPassthroughState::Running => {
|
||||
pause.send_default();
|
||||
}
|
||||
XrPassthroughState::Paused => {
|
||||
resume.send_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
description = "Demo using bevy_oxr"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[dependencies]
|
||||
bevy.workspace = true
|
||||
bevy_oxr.path = "../../"
|
||||
bevy_rapier3d = "0.25"
|
||||
color-eyre.workspace = true
|
||||
@@ -1,11 +0,0 @@
|
||||
## setup
|
||||
install xbuild ```cargo install --git https://github.com/rust-mobile/xbuild```
|
||||
run ```x doctor``` and install all required depencencies
|
||||
download the [openxr loader](https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/) and put it in the runtime_libs/arm64-v8a folder
|
||||
|
||||
## how to run
|
||||
run ```x devices```
|
||||
|
||||
and get the device name that looks something like this ```adb:1WD523S``` (probably a bit longer)
|
||||
|
||||
then ```run x run --release```
|
||||
@@ -1,39 +0,0 @@
|
||||
android:
|
||||
runtime_libs:
|
||||
- "runtime_libs"
|
||||
manifest:
|
||||
package: "org.bevyengine.demo_openxr_android"
|
||||
uses_feature:
|
||||
- name: "android.hardware.vr.headtracking"
|
||||
required: true
|
||||
- name: "oculus.software.handtracking"
|
||||
required: true
|
||||
- name: "com.oculus.feature.PASSTHROUGH"
|
||||
required: true
|
||||
- name: "com.oculus.experimental.enabled"
|
||||
required: true
|
||||
uses_permission:
|
||||
- name: "com.oculus.permission.HAND_TRACKING"
|
||||
application:
|
||||
label: "Bevy Openxr Android"
|
||||
theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
|
||||
meta_data:
|
||||
- name: "com.oculus.intent.category.VR"
|
||||
value: "vr_only"
|
||||
- name: "com.samsung.android.vr.application.mode"
|
||||
value: "vr_only"
|
||||
- name: "com.oculus.supportedDevices"
|
||||
value: "quest|quest2|quest3|questpro"
|
||||
activities:
|
||||
- config_changes: "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode|screenLayout"
|
||||
launch_mode: "singleTask"
|
||||
orientation: "landscape"
|
||||
intent_filters:
|
||||
- actions:
|
||||
- "android.intent.action.MAIN"
|
||||
categories:
|
||||
- "com.oculus.intent.category.VR"
|
||||
- "android.intent.category.LAUNCHER"
|
||||
- "org.khronos.openxr.intent.category.IMMERSIVE_HMD"
|
||||
sdk:
|
||||
target_sdk_version: 32
|
||||
@@ -1,771 +0,0 @@
|
||||
mod setup;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
ecs::schedule::ScheduleLabel,
|
||||
log::info,
|
||||
math::primitives::{Capsule3d, Cuboid},
|
||||
prelude::{
|
||||
bevy_main, default, App, Assets, Color, Commands, Component, Entity, Event, EventReader,
|
||||
EventWriter, FixedUpdate, GlobalTransform, IntoSystemConfigs, IntoSystemSetConfigs, Mesh,
|
||||
PbrBundle, PostUpdate, Query, Res, ResMut, Resource, Schedule, SpatialBundle,
|
||||
StandardMaterial, Startup, Transform, Update, Vec3, With, Without, World,
|
||||
},
|
||||
render::mesh::Meshable,
|
||||
time::{Fixed, Time, Timer, TimerMode},
|
||||
transform::TransformSystem,
|
||||
};
|
||||
use bevy_oxr::{
|
||||
graphics::{extensions::XrExtensions, XrAppInfo},
|
||||
input::XrInput,
|
||||
resources::{XrFrameState, XrSession},
|
||||
xr_init::xr_only,
|
||||
xr_input::{
|
||||
actions::XrActionSets,
|
||||
debug_gizmos::OpenXrDebugRenderer,
|
||||
hands::common::{HandInputDebugRenderer, HandResource, HandsResource},
|
||||
hands::HandBone,
|
||||
interactions::{
|
||||
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
|
||||
update_interactable_states, InteractionEvent, Touched, XRDirectInteractor,
|
||||
XRInteractable, XRInteractableState, XRInteractorState, XRSelection,
|
||||
},
|
||||
oculus_touch::OculusController,
|
||||
prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig},
|
||||
trackers::{OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker},
|
||||
Hand,
|
||||
},
|
||||
DefaultXrPlugins,
|
||||
};
|
||||
|
||||
use crate::setup::setup_scene;
|
||||
use bevy_rapier3d::prelude::*;
|
||||
|
||||
#[bevy_main]
|
||||
pub fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
|
||||
info!("Running bevy_openxr demo");
|
||||
let mut app = App::new();
|
||||
let mut xr_extensions = XrExtensions::default();
|
||||
xr_extensions.enable_fb_passthrough();
|
||||
|
||||
app
|
||||
//lets get the usual diagnostic stuff added
|
||||
.add_plugins(LogDiagnosticsPlugin::default())
|
||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||
//lets get the xr defaults added
|
||||
.add_plugins(DefaultXrPlugins {
|
||||
reqeusted_extensions: xr_extensions,
|
||||
app_info: XrAppInfo {
|
||||
name: "Bevy OXR Demo".into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
//lets add the debug renderer for the controllers
|
||||
.add_plugins(OpenXrDebugRenderer)
|
||||
//rapier goes here
|
||||
.add_plugins(RapierPhysicsPlugin::<NoUserData>::default().with_default_system_setup(false))
|
||||
// .add_plugins(RapierDebugRenderPlugin::default())
|
||||
//lets setup the starting scene
|
||||
.add_systems(Startup, setup_scene)
|
||||
.add_systems(Startup, spawn_controllers_example) //you need to spawn controllers or it crashes TODO:: Fix this
|
||||
//add locomotion
|
||||
.add_systems(Update, proto_locomotion.run_if(xr_only()))
|
||||
.insert_resource(PrototypeLocomotionConfig::default())
|
||||
//lets add the interaction systems
|
||||
.add_event::<InteractionEvent>()
|
||||
.add_systems(Update, prototype_interaction_input.run_if(xr_only()))
|
||||
.add_systems(Update, interactions.before(update_interactable_states))
|
||||
.add_systems(Update, update_interactable_states)
|
||||
.add_systems(
|
||||
Update,
|
||||
socket_interactions.before(update_interactable_states),
|
||||
)
|
||||
//add the grabbable system
|
||||
.add_systems(Update, update_grabbables.after(update_interactable_states))
|
||||
//draw the interaction gizmos
|
||||
.add_systems(
|
||||
Update,
|
||||
draw_interaction_gizmos
|
||||
.run_if(xr_only())
|
||||
.after(update_interactable_states),
|
||||
)
|
||||
.add_systems(Update, draw_socket_gizmos.after(update_interactable_states))
|
||||
//add our cube spawning system
|
||||
.add_event::<SpawnCubeRequest>()
|
||||
.insert_resource(SpawnCubeTimer(Timer::from_seconds(
|
||||
0.25,
|
||||
bevy::time::TimerMode::Once,
|
||||
)))
|
||||
.add_systems(Update, request_cube_spawn.run_if(xr_only()))
|
||||
.add_systems(Update, cube_spawner.after(request_cube_spawn))
|
||||
//test capsule
|
||||
.add_systems(Startup, spawn_capsule)
|
||||
//physics hands
|
||||
// .add_plugins(OpenXrHandInput)
|
||||
.add_plugins(HandInputDebugRenderer)
|
||||
.add_systems(Startup, spawn_physics_hands)
|
||||
.add_systems(
|
||||
FixedUpdate,
|
||||
update_physics_hands.before(PhysicsSet::SyncBackend),
|
||||
)
|
||||
.add_event::<GhostHandEvent>()
|
||||
.add_systems(Update, handle_ghost_hand_events.after(update_grabbables))
|
||||
.insert_resource(GhostTimers {
|
||||
left: Timer::from_seconds(0.25, TimerMode::Once),
|
||||
right: Timer::from_seconds(0.25, TimerMode::Once),
|
||||
})
|
||||
.add_systems(Update, watch_ghost_timers.before(handle_ghost_hand_events));
|
||||
|
||||
//configure rapier sets
|
||||
let mut physics_schedule = Schedule::new(PhysicsSchedule);
|
||||
|
||||
physics_schedule.configure_sets(
|
||||
(
|
||||
PhysicsSet::SyncBackend,
|
||||
PhysicsSet::StepSimulation,
|
||||
PhysicsSet::Writeback,
|
||||
)
|
||||
.chain()
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
);
|
||||
|
||||
app.configure_sets(
|
||||
PostUpdate,
|
||||
(
|
||||
PhysicsSet::SyncBackend,
|
||||
PhysicsSet::StepSimulation,
|
||||
PhysicsSet::Writeback,
|
||||
)
|
||||
.chain()
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
);
|
||||
//add rapier systems
|
||||
physics_schedule.add_systems((
|
||||
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::SyncBackend)
|
||||
.in_set(PhysicsSet::SyncBackend),
|
||||
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::StepSimulation)
|
||||
.in_set(PhysicsSet::StepSimulation),
|
||||
RapierPhysicsPlugin::<NoUserData>::get_systems(PhysicsSet::Writeback)
|
||||
.in_set(PhysicsSet::Writeback),
|
||||
));
|
||||
app.add_schedule(physics_schedule) // configure our fixed timestep schedule to run at the rate we want
|
||||
.insert_resource(Time::<Fixed>::from_duration(Duration::from_secs_f32(
|
||||
FIXED_TIMESTEP,
|
||||
)))
|
||||
.add_systems(FixedUpdate, run_physics_schedule)
|
||||
.add_systems(Startup, configure_physics);
|
||||
app.run();
|
||||
}
|
||||
|
||||
//fixed timesteps?
|
||||
const FIXED_TIMESTEP: f32 = 1. / 90.;
|
||||
|
||||
// A label for our new Schedule!
|
||||
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
struct PhysicsSchedule;
|
||||
|
||||
fn run_physics_schedule(world: &mut World) {
|
||||
world.run_schedule(PhysicsSchedule);
|
||||
}
|
||||
|
||||
fn configure_physics(mut rapier_config: ResMut<RapierConfiguration>) {
|
||||
rapier_config.timestep_mode = TimestepMode::Fixed {
|
||||
dt: FIXED_TIMESTEP,
|
||||
substeps: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_controllers_example(mut commands: Commands) {
|
||||
//left hand
|
||||
commands.spawn((
|
||||
OpenXRLeftController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRDirectInteractor,
|
||||
XRInteractorState::default(),
|
||||
XRSelection::default(),
|
||||
Hand::Left,
|
||||
));
|
||||
//right hand
|
||||
commands.spawn((
|
||||
OpenXRRightController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRDirectInteractor,
|
||||
XRInteractorState::default(),
|
||||
XRSelection::default(),
|
||||
Hand::Right,
|
||||
));
|
||||
}
|
||||
|
||||
fn spawn_capsule(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Capsule3d::new(0.033, 0.115).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 2.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
// Collider::capsule_y(0.0575, 0.034),
|
||||
Collider::capsule(
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: -0.0575,
|
||||
z: 0.0,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0575,
|
||||
z: 0.0,
|
||||
},
|
||||
0.034,
|
||||
),
|
||||
RigidBody::Dynamic,
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Component, PartialEq, Debug, Clone, Copy)]
|
||||
pub enum PhysicsHandBone {
|
||||
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,
|
||||
}
|
||||
#[derive(Component, PartialEq)]
|
||||
pub enum BoneInitState {
|
||||
True,
|
||||
False,
|
||||
}
|
||||
|
||||
fn spawn_physics_hands(mut commands: Commands) {
|
||||
//here we go
|
||||
let hands = [Hand::Left, Hand::Right];
|
||||
let bones = [
|
||||
PhysicsHandBone::Palm,
|
||||
PhysicsHandBone::Wrist,
|
||||
PhysicsHandBone::ThumbMetacarpal,
|
||||
PhysicsHandBone::ThumbProximal,
|
||||
PhysicsHandBone::ThumbDistal,
|
||||
PhysicsHandBone::ThumbTip,
|
||||
PhysicsHandBone::IndexMetacarpal,
|
||||
PhysicsHandBone::IndexProximal,
|
||||
PhysicsHandBone::IndexIntermediate,
|
||||
PhysicsHandBone::IndexDistal,
|
||||
PhysicsHandBone::IndexTip,
|
||||
PhysicsHandBone::MiddleMetacarpal,
|
||||
PhysicsHandBone::MiddleProximal,
|
||||
PhysicsHandBone::MiddleIntermediate,
|
||||
PhysicsHandBone::MiddleDistal,
|
||||
PhysicsHandBone::MiddleTip,
|
||||
PhysicsHandBone::RingMetacarpal,
|
||||
PhysicsHandBone::RingProximal,
|
||||
PhysicsHandBone::RingIntermediate,
|
||||
PhysicsHandBone::RingDistal,
|
||||
PhysicsHandBone::RingTip,
|
||||
PhysicsHandBone::LittleMetacarpal,
|
||||
PhysicsHandBone::LittleProximal,
|
||||
PhysicsHandBone::LittleIntermediate,
|
||||
PhysicsHandBone::LittleDistal,
|
||||
PhysicsHandBone::LittleTip,
|
||||
];
|
||||
let radius = 0.010;
|
||||
let left_hand_membership_group = Group::GROUP_1;
|
||||
let right_hand_membership_group = Group::GROUP_2;
|
||||
let floor_membership = Group::GROUP_3;
|
||||
|
||||
for hand in hands.iter() {
|
||||
let hand_membership = match hand {
|
||||
Hand::Left => left_hand_membership_group,
|
||||
Hand::Right => right_hand_membership_group,
|
||||
};
|
||||
let mut hand_filter: Group = Group::ALL;
|
||||
hand_filter.remove(hand_membership);
|
||||
hand_filter.remove(floor_membership);
|
||||
for bone in bones.iter() {
|
||||
//spawn the thing
|
||||
commands.spawn((
|
||||
SpatialBundle::default(),
|
||||
Collider::capsule(
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: -0.0575,
|
||||
z: 0.0,
|
||||
},
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0575,
|
||||
z: 0.0,
|
||||
},
|
||||
radius,
|
||||
),
|
||||
RigidBody::Dynamic,
|
||||
Velocity::default(),
|
||||
CollisionGroups::new(hand_membership, Group::from_bits(0b0001).unwrap()),
|
||||
// SolverGroups::new(self_group, interaction_group),
|
||||
bone.clone(),
|
||||
BoneInitState::False,
|
||||
hand.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MatchingType {
|
||||
PositionMatching,
|
||||
VelocityMatching,
|
||||
}
|
||||
|
||||
fn update_physics_hands(
|
||||
hands_res: Option<Res<HandsResource>>,
|
||||
mut bone_query: Query<(
|
||||
&mut Transform,
|
||||
&mut Collider,
|
||||
&PhysicsHandBone,
|
||||
&mut BoneInitState,
|
||||
&Hand,
|
||||
&mut Velocity,
|
||||
)>,
|
||||
hand_query: Query<(&Transform, &HandBone, &Hand), Without<PhysicsHandBone>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
let matching = MatchingType::VelocityMatching;
|
||||
//sanity check do we even have hands?
|
||||
match hands_res {
|
||||
Some(res) => {
|
||||
//config stuff
|
||||
let radius = 0.010;
|
||||
for mut bone in bone_query.iter_mut() {
|
||||
let hand_res = match bone.4 {
|
||||
Hand::Left => res.left,
|
||||
Hand::Right => res.right,
|
||||
};
|
||||
|
||||
//lets just do the Right ThumbMetacarpal for now
|
||||
let result = get_start_and_end_entities(hand_res, bone.2);
|
||||
if let Some((start_entity, end_entity)) = result {
|
||||
//now we need their transforms
|
||||
let start_components = hand_query.get(start_entity);
|
||||
let end_components = hand_query.get(end_entity);
|
||||
let direction = end_components.unwrap().0.translation
|
||||
- start_components.unwrap().0.translation;
|
||||
if direction.length() < 0.001 {
|
||||
//i hate this but we need to skip init if the length is zero
|
||||
return;
|
||||
}
|
||||
|
||||
match *bone.3 {
|
||||
BoneInitState::True => {
|
||||
match matching {
|
||||
MatchingType::PositionMatching => {
|
||||
//if we are init then we just move em?
|
||||
*bone.0 = start_components
|
||||
.unwrap()
|
||||
.0
|
||||
.clone()
|
||||
.looking_at(end_components.unwrap().0.translation, Vec3::Y);
|
||||
}
|
||||
MatchingType::VelocityMatching => {
|
||||
//calculate position difference
|
||||
let diff = (start_components.unwrap().0.translation
|
||||
- bone.0.translation)
|
||||
/ time.delta_seconds();
|
||||
bone.5.linvel = diff;
|
||||
//calculate angular velocity?
|
||||
// gizmos.ray(bone.0.translation, bone.0.forward(), Color::WHITE);
|
||||
let desired_forward = start_components
|
||||
.unwrap()
|
||||
.0
|
||||
.clone()
|
||||
.looking_at(end_components.unwrap().0.translation, Vec3::Y)
|
||||
.rotation;
|
||||
// gizmos.ray(
|
||||
// bone.0.translation,
|
||||
// desired_forward.mul_vec3(-Vec3::Z),
|
||||
// Color::GREEN,
|
||||
// );
|
||||
let cross =
|
||||
bone.0.forward().cross(desired_forward.mul_vec3(-Vec3::Z));
|
||||
|
||||
// gizmos.ray(
|
||||
// bone.0.translation,
|
||||
// cross,
|
||||
// Color::RED,
|
||||
// );
|
||||
bone.5.angvel = cross / time.delta_seconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
BoneInitState::False => {
|
||||
//build a new collider?
|
||||
*bone.1 = Collider::capsule(
|
||||
Vec3::splat(0.0),
|
||||
Vec3 {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: -direction.length(),
|
||||
},
|
||||
radius,
|
||||
);
|
||||
*bone.3 = BoneInitState::True;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => info!("hand states resource not initialized yet"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_start_and_end_entities(
|
||||
hand_res: HandResource,
|
||||
bone: &PhysicsHandBone,
|
||||
) -> Option<(Entity, Entity)> {
|
||||
match bone {
|
||||
PhysicsHandBone::Palm => return None,
|
||||
PhysicsHandBone::Wrist => return None,
|
||||
PhysicsHandBone::ThumbMetacarpal => {
|
||||
return Some((hand_res.thumb.metacarpal, hand_res.thumb.proximal))
|
||||
}
|
||||
PhysicsHandBone::ThumbProximal => {
|
||||
return Some((hand_res.thumb.proximal, hand_res.thumb.distal))
|
||||
}
|
||||
PhysicsHandBone::ThumbDistal => return Some((hand_res.thumb.distal, hand_res.thumb.tip)),
|
||||
PhysicsHandBone::ThumbTip => return None,
|
||||
PhysicsHandBone::IndexMetacarpal => {
|
||||
return Some((hand_res.index.metacarpal, hand_res.index.proximal))
|
||||
}
|
||||
PhysicsHandBone::IndexProximal => {
|
||||
return Some((hand_res.index.proximal, hand_res.index.intermediate))
|
||||
}
|
||||
PhysicsHandBone::IndexIntermediate => {
|
||||
return Some((hand_res.index.intermediate, hand_res.index.distal))
|
||||
}
|
||||
PhysicsHandBone::IndexDistal => return Some((hand_res.index.distal, hand_res.index.tip)),
|
||||
PhysicsHandBone::IndexTip => return None,
|
||||
PhysicsHandBone::MiddleMetacarpal => {
|
||||
return Some((hand_res.middle.metacarpal, hand_res.middle.proximal))
|
||||
}
|
||||
PhysicsHandBone::MiddleProximal => {
|
||||
return Some((hand_res.middle.proximal, hand_res.middle.intermediate))
|
||||
}
|
||||
PhysicsHandBone::MiddleIntermediate => {
|
||||
return Some((hand_res.middle.intermediate, hand_res.middle.distal))
|
||||
}
|
||||
PhysicsHandBone::MiddleDistal => {
|
||||
return Some((hand_res.middle.distal, hand_res.middle.tip))
|
||||
}
|
||||
PhysicsHandBone::MiddleTip => return None,
|
||||
PhysicsHandBone::RingMetacarpal => {
|
||||
return Some((hand_res.ring.metacarpal, hand_res.ring.proximal))
|
||||
}
|
||||
PhysicsHandBone::RingProximal => {
|
||||
return Some((hand_res.ring.proximal, hand_res.ring.intermediate))
|
||||
}
|
||||
PhysicsHandBone::RingIntermediate => {
|
||||
return Some((hand_res.ring.intermediate, hand_res.ring.distal))
|
||||
}
|
||||
PhysicsHandBone::RingDistal => return Some((hand_res.ring.distal, hand_res.ring.tip)),
|
||||
PhysicsHandBone::RingTip => return None,
|
||||
PhysicsHandBone::LittleMetacarpal => {
|
||||
return Some((hand_res.little.metacarpal, hand_res.little.proximal))
|
||||
}
|
||||
PhysicsHandBone::LittleProximal => {
|
||||
return Some((hand_res.little.proximal, hand_res.little.intermediate))
|
||||
}
|
||||
PhysicsHandBone::LittleIntermediate => {
|
||||
return Some((hand_res.little.intermediate, hand_res.little.distal))
|
||||
}
|
||||
PhysicsHandBone::LittleDistal => {
|
||||
return Some((hand_res.little.distal, hand_res.little.tip))
|
||||
}
|
||||
PhysicsHandBone::LittleTip => return None,
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Event, Default)]
|
||||
pub struct SpawnCubeRequest;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct SpawnCubeTimer(Timer);
|
||||
|
||||
fn request_cube_spawn(
|
||||
oculus_controller: Res<OculusController>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
mut writer: EventWriter<SpawnCubeRequest>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<SpawnCubeTimer>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
timer.0.tick(time.delta());
|
||||
if timer.0.finished() {
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
//get controller triggers
|
||||
let left_main_button = controller.a_button();
|
||||
if left_main_button {
|
||||
writer.send(SpawnCubeRequest::default());
|
||||
timer.0.reset();
|
||||
}
|
||||
let right_main_button = controller.x_button();
|
||||
if right_main_button {
|
||||
writer.send(SpawnCubeRequest::default());
|
||||
timer.0.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cube_spawner(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut events: EventReader<SpawnCubeRequest>,
|
||||
) {
|
||||
for _request in events.read() {
|
||||
// cube
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
RigidBody::Dynamic,
|
||||
Collider::cuboid(0.05, 0.05, 0.05),
|
||||
ColliderDebugColor(Color::hsl(220.0, 1.0, 0.3)),
|
||||
XRInteractable,
|
||||
XRInteractableState::default(),
|
||||
Grabbable,
|
||||
Touched(false),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: find a real place for this
|
||||
fn prototype_interaction_input(
|
||||
oculus_controller: Res<OculusController>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
mut right_interactor_query: Query<
|
||||
&mut XRInteractorState,
|
||||
(
|
||||
With<XRDirectInteractor>,
|
||||
With<OpenXRRightController>,
|
||||
Without<OpenXRLeftController>,
|
||||
),
|
||||
>,
|
||||
mut left_interactor_query: Query<
|
||||
&mut XRInteractorState,
|
||||
(
|
||||
With<XRDirectInteractor>,
|
||||
With<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
),
|
||||
>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
//get controller triggers
|
||||
let left_trigger = controller.trigger(Hand::Left);
|
||||
let right_trigger = controller.trigger(Hand::Right);
|
||||
//get the interactors and do state stuff
|
||||
let mut left_state = left_interactor_query.single_mut();
|
||||
if left_trigger > 0.8 {
|
||||
*left_state = XRInteractorState::Selecting;
|
||||
} else {
|
||||
*left_state = XRInteractorState::Idle;
|
||||
}
|
||||
let mut right_state = right_interactor_query.single_mut();
|
||||
if right_trigger > 0.8 {
|
||||
*right_state = XRInteractorState::Selecting;
|
||||
} else {
|
||||
*right_state = XRInteractorState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
//this event is for transitioning the physics hand in an out of existent so we can drop things better
|
||||
#[derive(Event)]
|
||||
pub struct GhostHandEvent {
|
||||
pub hand: Hand,
|
||||
pub desired_state: bool, //true for no interactions, false for normal interactions
|
||||
}
|
||||
#[derive(Resource)]
|
||||
pub struct GhostTimers {
|
||||
pub left: Timer,
|
||||
pub right: Timer,
|
||||
}
|
||||
|
||||
pub fn handle_ghost_hand_events(
|
||||
mut events: EventReader<GhostHandEvent>,
|
||||
mut bones: Query<(&Hand, &mut CollisionGroups), With<PhysicsHandBone>>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
// info!(
|
||||
// "Ghost hand Event: {:?}, {:?}",
|
||||
// event.hand, event.desired_state
|
||||
// );
|
||||
//do work
|
||||
for mut bone in bones.iter_mut() {
|
||||
match *bone.0 == event.hand {
|
||||
true => match event.desired_state {
|
||||
true => bone.1.filters = Group::NONE,
|
||||
false => bone.1.filters = Group::from_bits(0b0001).unwrap(),
|
||||
},
|
||||
false => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn watch_ghost_timers(
|
||||
mut timers: ResMut<GhostTimers>,
|
||||
mut writer: EventWriter<GhostHandEvent>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
//tick both timers
|
||||
timers.left.tick(time.delta());
|
||||
timers.right.tick(time.delta());
|
||||
//if they finish send events to make the hands physical again
|
||||
if timers.left.just_finished() {
|
||||
writer.send(GhostHandEvent {
|
||||
hand: Hand::Left,
|
||||
desired_state: false,
|
||||
});
|
||||
}
|
||||
if timers.right.just_finished() {
|
||||
writer.send(GhostHandEvent {
|
||||
hand: Hand::Right,
|
||||
desired_state: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Grabbable;
|
||||
|
||||
pub fn update_grabbables(
|
||||
mut events: EventReader<InteractionEvent>,
|
||||
mut grabbable_query: Query<
|
||||
(Entity, &mut Transform, Option<&mut RigidBody>),
|
||||
(Without<XRDirectInteractor>, With<Grabbable>),
|
||||
>,
|
||||
mut interactor_query: Query<
|
||||
(
|
||||
&GlobalTransform,
|
||||
&XRInteractorState,
|
||||
&mut XRSelection,
|
||||
&Hand,
|
||||
),
|
||||
Without<Grabbable>,
|
||||
>,
|
||||
mut writer: EventWriter<GhostHandEvent>,
|
||||
mut timers: ResMut<GhostTimers>,
|
||||
) {
|
||||
//so basically the idea is to try all the events?
|
||||
for event in events.read() {
|
||||
// info!("some event");
|
||||
match grabbable_query.get_mut(event.interactable) {
|
||||
Ok(mut grabbable_transform) => {
|
||||
// info!("we got a grabbable");
|
||||
//now we need the location of our interactor
|
||||
match interactor_query.get_mut(event.interactor) {
|
||||
Ok(mut interactor_transform) => {
|
||||
match *interactor_transform.2 {
|
||||
XRSelection::Empty => {
|
||||
match interactor_transform.1 {
|
||||
XRInteractorState::Idle => match grabbable_transform.2 {
|
||||
Some(mut thing) => {
|
||||
*thing = RigidBody::Dynamic;
|
||||
*interactor_transform.2 = XRSelection::Empty;
|
||||
}
|
||||
None => (),
|
||||
},
|
||||
XRInteractorState::Selecting => {
|
||||
// info!("its a direct interactor?");
|
||||
match grabbable_transform.2 {
|
||||
Some(mut thing) => {
|
||||
*thing = RigidBody::KinematicPositionBased;
|
||||
*interactor_transform.2 =
|
||||
XRSelection::Full(grabbable_transform.0);
|
||||
//raise enter ghost hand event
|
||||
writer.send(GhostHandEvent {
|
||||
hand: *interactor_transform.3,
|
||||
desired_state: true,
|
||||
});
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
*grabbable_transform.1 =
|
||||
interactor_transform.0.compute_transform();
|
||||
}
|
||||
}
|
||||
}
|
||||
XRSelection::Full(ent) => {
|
||||
info!("nah bro we holding something");
|
||||
match grabbable_transform.0 == ent {
|
||||
true => {
|
||||
*grabbable_transform.1 =
|
||||
interactor_transform.0.compute_transform();
|
||||
}
|
||||
false => {}
|
||||
}
|
||||
match interactor_transform.1 {
|
||||
XRInteractorState::Idle => {
|
||||
*interactor_transform.2 = XRSelection::Empty;
|
||||
//reset timers to make hands physical again
|
||||
match *interactor_transform.3 {
|
||||
Hand::Left => timers.left.reset(),
|
||||
Hand::Right => timers.right.reset(),
|
||||
}
|
||||
}
|
||||
XRInteractorState::Selecting => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// info!("not a direct interactor")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// info!("not a grabbable?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
demo::main();
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
use bevy::{
|
||||
math::primitives::{Cuboid, Plane3d},
|
||||
prelude::{
|
||||
Assets, Camera3dBundle, Color, Commands, Mesh, PbrBundle, ResMut, StandardMaterial,
|
||||
Transform, Vec3,
|
||||
},
|
||||
render::mesh::Meshable,
|
||||
utils::default,
|
||||
};
|
||||
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
|
||||
use bevy_rapier3d::{
|
||||
prelude::{Collider, CollisionGroups, Group, RigidBody},
|
||||
render::ColliderDebugColor,
|
||||
};
|
||||
|
||||
use crate::Grabbable;
|
||||
|
||||
/// set up a simple 3D scene
|
||||
pub fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
/*
|
||||
* workbench plane
|
||||
*/
|
||||
let ground_size = 2.5;
|
||||
let ground_height = 0.825;
|
||||
let ground_thickness = 0.05;
|
||||
// plane
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Plane3d::new(Vec3::Y).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
||||
transform: Transform::from_xyz(0.0, ground_height, 0.0),
|
||||
..default()
|
||||
},
|
||||
RigidBody::Fixed,
|
||||
Collider::cuboid(ground_size, ground_thickness, ground_size),
|
||||
CollisionGroups::new(Group::GROUP_3, Group::ALL),
|
||||
));
|
||||
// cube
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1))),
|
||||
material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
RigidBody::Dynamic,
|
||||
Collider::cuboid(0.05, 0.05, 0.05),
|
||||
ColliderDebugColor(Color::hsl(220.0, 1.0, 0.3)),
|
||||
XRInteractable,
|
||||
XRInteractableState::default(),
|
||||
Grabbable,
|
||||
Touched(false),
|
||||
));
|
||||
|
||||
// light
|
||||
// commands.spawn(PointLightBundle {
|
||||
// point_light: PointLight {
|
||||
// intensity: 1500.0,
|
||||
// shadows_enabled: true,
|
||||
// ..default()
|
||||
// },
|
||||
// transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
// ..default()
|
||||
// });
|
||||
// camera
|
||||
commands.spawn((Camera3dBundle {
|
||||
transform: Transform::from_xyz(5.25, 5.25, 5.0).looking_at(
|
||||
Vec3 {
|
||||
x: -0.548,
|
||||
y: -0.161,
|
||||
z: -0.137,
|
||||
},
|
||||
Vec3::Y,
|
||||
),
|
||||
..default()
|
||||
},));
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
use bevy::diagnostic::LogDiagnosticsPlugin;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_asset::RenderAssetUsages;
|
||||
use bevy::transform::components::Transform;
|
||||
use bevy_oxr::graphics::XrAppInfo;
|
||||
use bevy_oxr::prelude::XrSystems;
|
||||
use bevy_oxr::resources::XrViews;
|
||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
||||
use bevy_oxr::xr_input::interactions::{
|
||||
InteractionEvent, XRDirectInteractor, XRInteractorState, XRRayInteractor, XRSocketInteractor,
|
||||
};
|
||||
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
|
||||
use bevy_oxr::xr_input::trackers::{
|
||||
AimPose, OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
|
||||
OpenXRTrackingRoot,
|
||||
};
|
||||
use bevy_oxr::xr_input::Vec3Conv;
|
||||
use bevy_oxr::DefaultXrPlugins;
|
||||
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||
|
||||
fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
|
||||
App::new()
|
||||
.add_plugins(DefaultXrPlugins {
|
||||
app_info: XrAppInfo {
|
||||
name: "Bevy OXR Globe Example".into(),
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.add_plugins(LogDiagnosticsPlugin::default())
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (proto_locomotion, pull_to_ground).chain().xr_only())
|
||||
.insert_resource(PrototypeLocomotionConfig::default())
|
||||
.add_systems(Startup, spawn_controllers_example)
|
||||
.add_plugins(HandInputDebugRenderer)
|
||||
.add_event::<InteractionEvent>()
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Globe {
|
||||
radius: f32,
|
||||
}
|
||||
|
||||
/// Creates a colorful test pattern
|
||||
fn uv_debug_texture() -> Image {
|
||||
const TEXTURE_SIZE: usize = 8;
|
||||
|
||||
let mut palette: [u8; 32] = [
|
||||
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
|
||||
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
|
||||
];
|
||||
|
||||
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
|
||||
for y in 0..TEXTURE_SIZE {
|
||||
let offset = TEXTURE_SIZE * y * 4;
|
||||
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
|
||||
palette.rotate_right(4);
|
||||
}
|
||||
|
||||
Image::new_fill(
|
||||
Extent3d {
|
||||
width: TEXTURE_SIZE as u32,
|
||||
height: TEXTURE_SIZE as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
TextureDimension::D2,
|
||||
&texture_data,
|
||||
TextureFormat::Rgba8UnormSrgb,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
) {
|
||||
// plane
|
||||
let radius = 5.0;
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::new(radius)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color_texture: Some(images.add(uv_debug_texture())),
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(0.0, -radius, 0.0),
|
||||
..default()
|
||||
},
|
||||
Globe { radius },
|
||||
));
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// socket
|
||||
commands.spawn((
|
||||
SpatialBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.5, 1.0),
|
||||
..default()
|
||||
},
|
||||
XRInteractorState::Selecting,
|
||||
XRSocketInteractor,
|
||||
));
|
||||
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1500.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn((Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
|
||||
Vec3 {
|
||||
x: -0.548,
|
||||
y: -0.161,
|
||||
z: -0.137,
|
||||
},
|
||||
Vec3::Y,
|
||||
),
|
||||
..default()
|
||||
},));
|
||||
}
|
||||
|
||||
fn spawn_controllers_example(mut commands: Commands) {
|
||||
//left hand
|
||||
commands.spawn((
|
||||
OpenXRLeftController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRRayInteractor,
|
||||
AimPose(Transform::default()),
|
||||
XRInteractorState::default(),
|
||||
));
|
||||
//right hand
|
||||
commands.spawn((
|
||||
OpenXRRightController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRDirectInteractor,
|
||||
XRInteractorState::default(),
|
||||
));
|
||||
}
|
||||
|
||||
fn pull_to_ground(
|
||||
time: Res<Time>,
|
||||
mut tracking_root_query: Query<&mut Transform, (With<OpenXRTrackingRoot>, Without<Globe>)>,
|
||||
globe: Query<(&Transform, &Globe), Without<OpenXRTrackingRoot>>,
|
||||
views: ResMut<XrViews>,
|
||||
) {
|
||||
let mut root = tracking_root_query.single_mut();
|
||||
let (globe_pos, globe) = globe.single();
|
||||
|
||||
// Get player position (position of playground + position within playground)
|
||||
let Some(view) = views.first() else { return };
|
||||
let mut hmd_translation = view.pose.position.to_vec3();
|
||||
hmd_translation.y = 0.0;
|
||||
let local = root.translation;
|
||||
let offset = root.rotation.mul_vec3(hmd_translation);
|
||||
let global = offset + local;
|
||||
|
||||
let adjustment_rate = (time.delta_seconds() * 10.0).min(1.0);
|
||||
|
||||
// Lower player onto sphere
|
||||
let up = (global - globe_pos.translation).normalize();
|
||||
let diff = up * globe.radius + globe_pos.translation - offset - root.translation;
|
||||
root.translation += diff * adjustment_rate;
|
||||
|
||||
// Rotate player to be upright on sphere
|
||||
let angle_diff = Quat::from_rotation_arc(*root.up(), up);
|
||||
let point = root.translation + offset;
|
||||
root.rotate_around(point, Quat::IDENTITY.slerp(angle_diff, adjustment_rate));
|
||||
}
|
||||
252
examples/xr.rs
252
examples/xr.rs
@@ -1,252 +0,0 @@
|
||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::transform::components::Transform;
|
||||
use bevy_oxr::graphics::XrAppInfo;
|
||||
use bevy_oxr::input::XrInput;
|
||||
use bevy_oxr::resources::{XrFrameState, XrSession};
|
||||
|
||||
use bevy_oxr::xr_init::{xr_only, EndXrSession, StartXrSession, XrSetup};
|
||||
use bevy_oxr::xr_input::actions::XrActionSets;
|
||||
use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
|
||||
use bevy_oxr::xr_input::interactions::{
|
||||
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
|
||||
update_interactable_states, InteractionEvent, Touched, XRDirectInteractor, XRInteractable,
|
||||
XRInteractableState, XRInteractorState, XRRayInteractor, XRSocketInteractor,
|
||||
};
|
||||
use bevy_oxr::xr_input::oculus_touch::OculusController;
|
||||
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
|
||||
use bevy_oxr::xr_input::trackers::{
|
||||
AimPose, OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
|
||||
};
|
||||
use bevy_oxr::xr_input::Hand;
|
||||
use bevy_oxr::DefaultXrPlugins;
|
||||
|
||||
fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
|
||||
info!("Running `openxr-6dof` skill");
|
||||
App::new()
|
||||
.add_plugins(DefaultXrPlugins {
|
||||
app_info: XrAppInfo {
|
||||
name: "Bevy OXR Example".into(),
|
||||
},
|
||||
..default()
|
||||
})
|
||||
// .add_plugins(OpenXrDebugRenderer) //new debug renderer adds gizmos to
|
||||
.add_plugins(LogDiagnosticsPlugin::default())
|
||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, proto_locomotion.run_if(xr_only()))
|
||||
.insert_resource(PrototypeLocomotionConfig::default())
|
||||
.add_systems(XrSetup, spawn_controllers_example)
|
||||
.add_plugins(HandInputDebugRenderer)
|
||||
.add_systems(
|
||||
Update,
|
||||
draw_interaction_gizmos
|
||||
.after(update_interactable_states)
|
||||
.run_if(xr_only()),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
draw_socket_gizmos
|
||||
.after(update_interactable_states)
|
||||
.run_if(xr_only()),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
interactions
|
||||
.before(update_interactable_states)
|
||||
.run_if(xr_only()),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
socket_interactions.before(update_interactable_states),
|
||||
)
|
||||
.add_systems(Update, prototype_interaction_input.run_if(xr_only()))
|
||||
.add_systems(Update, update_interactable_states)
|
||||
.add_systems(Update, update_grabbables.after(update_interactable_states))
|
||||
.add_systems(Update, start_stop_session)
|
||||
.add_event::<InteractionEvent>()
|
||||
.run();
|
||||
}
|
||||
|
||||
fn start_stop_session(
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mut start: EventWriter<StartXrSession>,
|
||||
mut stop: EventWriter<EndXrSession>,
|
||||
) {
|
||||
if keyboard.just_pressed(KeyCode::KeyS) {
|
||||
start.send_default();
|
||||
}
|
||||
if keyboard.just_pressed(KeyCode::KeyE) {
|
||||
stop.send_default();
|
||||
}
|
||||
}
|
||||
|
||||
/// set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// plane
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(2.5)).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.3, 0.5, 0.3))),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::srgb(0.8, 0.7, 0.6))),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// socket
|
||||
commands.spawn((
|
||||
SpatialBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.5, 1.0),
|
||||
..default()
|
||||
},
|
||||
XRInteractorState::Selecting,
|
||||
XRSocketInteractor,
|
||||
));
|
||||
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1500.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
// camera
|
||||
commands.spawn((Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
|
||||
Vec3 {
|
||||
x: -0.548,
|
||||
y: -0.161,
|
||||
z: -0.137,
|
||||
},
|
||||
Vec3::Y,
|
||||
),
|
||||
..default()
|
||||
},));
|
||||
//simple interactable
|
||||
commands.spawn((
|
||||
SpatialBundle {
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
XRInteractable,
|
||||
XRInteractableState::default(),
|
||||
Grabbable,
|
||||
Touched(false),
|
||||
));
|
||||
}
|
||||
|
||||
fn spawn_controllers_example(mut commands: Commands) {
|
||||
//left hand
|
||||
commands.spawn((
|
||||
OpenXRLeftController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRRayInteractor,
|
||||
AimPose(Transform::default()),
|
||||
XRInteractorState::default(),
|
||||
));
|
||||
//right hand
|
||||
commands.spawn((
|
||||
OpenXRRightController,
|
||||
OpenXRController,
|
||||
OpenXRTracker,
|
||||
SpatialBundle::default(),
|
||||
XRDirectInteractor,
|
||||
XRInteractorState::default(),
|
||||
));
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn prototype_interaction_input(
|
||||
oculus_controller: Res<OculusController>,
|
||||
frame_state: Res<XrFrameState>,
|
||||
xr_input: Res<XrInput>,
|
||||
session: Res<XrSession>,
|
||||
mut right_interactor_query: Query<
|
||||
&mut XRInteractorState,
|
||||
(
|
||||
With<XRDirectInteractor>,
|
||||
With<OpenXRRightController>,
|
||||
Without<OpenXRLeftController>,
|
||||
),
|
||||
>,
|
||||
mut left_interactor_query: Query<
|
||||
&mut XRInteractorState,
|
||||
(
|
||||
With<XRRayInteractor>,
|
||||
With<OpenXRLeftController>,
|
||||
Without<OpenXRRightController>,
|
||||
),
|
||||
>,
|
||||
action_sets: Res<XrActionSets>,
|
||||
) {
|
||||
//get controller
|
||||
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
|
||||
//get controller triggers
|
||||
let left_trigger = controller.trigger(Hand::Left);
|
||||
let right_trigger = controller.trigger(Hand::Right);
|
||||
//get the interactors and do state stuff
|
||||
let mut left_state = left_interactor_query.single_mut();
|
||||
if left_trigger > 0.8 {
|
||||
*left_state = XRInteractorState::Selecting;
|
||||
} else {
|
||||
*left_state = XRInteractorState::Idle;
|
||||
}
|
||||
let mut right_state = right_interactor_query.single_mut();
|
||||
if right_trigger > 0.8 {
|
||||
*right_state = XRInteractorState::Selecting;
|
||||
} else {
|
||||
*right_state = XRInteractorState::Idle;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Grabbable;
|
||||
|
||||
pub fn update_grabbables(
|
||||
mut events: EventReader<InteractionEvent>,
|
||||
mut grabbable_query: Query<&mut Transform, (With<Grabbable>, Without<XRDirectInteractor>)>,
|
||||
interactor_query: Query<(&GlobalTransform, &XRInteractorState), Without<Grabbable>>,
|
||||
) {
|
||||
//so basically the idea is to try all the events?
|
||||
for event in events.read() {
|
||||
// info!("some event");
|
||||
match grabbable_query.get_mut(event.interactable) {
|
||||
Ok(mut grabbable_transform) => {
|
||||
// info!("we got a grabbable");
|
||||
//now we need the location of our interactor
|
||||
match interactor_query.get(event.interactor) {
|
||||
Ok(interactor_transform) => {
|
||||
match interactor_transform.1 {
|
||||
XRInteractorState::Idle => (),
|
||||
XRInteractorState::Selecting => {
|
||||
// info!("its a direct interactor?");
|
||||
*grabbable_transform = interactor_transform.0.compute_transform();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// info!("not a direct interactor")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// info!("not a grabbable?")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
flake.lock
generated
Normal file
100
flake.lock
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1714631076,
|
||||
"narHash": "sha256-at4+1R9gx3CGvX0ZJo9GwDZyt3RzOft7qDCTsYHjI4M=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "22a9eb3f20dd340d084cee4426f386a90b1351ca",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1714656196,
|
||||
"narHash": "sha256-kjQkA98lMcsom6Gbhw8SYzmwrSo+2nruiTcTZp5jK7o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "94035b482d181af0a0f8f77823a790b256b7c3cc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1714572655,
|
||||
"narHash": "sha256-xjD8vmit0Nz1qaSSSpeXOK3saSvAZtOGHS2SHZE75Ek=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "cfce2bb46da62950a8b70ddb0b2a12332da1b1e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
115
flake.nix
Normal file
115
flake.nix
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
fenix,
|
||||
flake-utils,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
# setup pkgs
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [fenix.overlays.default];
|
||||
config = {
|
||||
android_sdk.accept_license = true;
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
# the entire rust toolchain with required targets
|
||||
rustToolchain = with fenix.packages.${system};
|
||||
combine [
|
||||
(stable.withComponents [
|
||||
"rustc"
|
||||
"cargo"
|
||||
"rustfmt"
|
||||
"clippy"
|
||||
"rust-src"
|
||||
])
|
||||
|
||||
targets.wasm32-unknown-unknown.stable.rust-std
|
||||
targets.aarch64-linux-android.stable.rust-std
|
||||
targets.x86_64-pc-windows-gnu.stable.rust-std
|
||||
];
|
||||
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
||||
abiVersions = ["arm64-v8a"];
|
||||
includeNDK = true;
|
||||
platformVersions = ["32"];
|
||||
};
|
||||
in {
|
||||
devShells.default = pkgs.mkShell rec {
|
||||
# build dependencies
|
||||
nativeBuildInputs = with pkgs; [
|
||||
# the entire rust toolchain
|
||||
rustToolchain
|
||||
# tool for cross compiling
|
||||
cargo-apk
|
||||
# xbuild
|
||||
|
||||
pkg-config
|
||||
|
||||
# Common cargo tools we often use
|
||||
cargo-deny
|
||||
cargo-expand
|
||||
cargo-binutils
|
||||
|
||||
# cmake for openxr
|
||||
cmake
|
||||
];
|
||||
|
||||
# runtime dependencies
|
||||
buildInputs =
|
||||
[
|
||||
pkgs.zstd
|
||||
pkgs.libxml2
|
||||
]
|
||||
++ pkgs.lib.optionals pkgs.stdenv.isLinux (with pkgs; [
|
||||
# bevy dependencies
|
||||
udev
|
||||
alsa-lib
|
||||
# vulkan
|
||||
vulkan-loader
|
||||
vulkan-headers
|
||||
vulkan-tools
|
||||
vulkan-validation-layers
|
||||
# x11
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
# wayland
|
||||
libxkbcommon
|
||||
wayland
|
||||
# xr
|
||||
openxr-loader
|
||||
libGL
|
||||
])
|
||||
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
|
||||
pkgs.darwin.apple_sdk.frameworks.Cocoa
|
||||
# # This is missing on mac m1 nix, for some reason.
|
||||
# # see https://stackoverflow.com/a/69732679
|
||||
pkgs.libiconv
|
||||
];
|
||||
|
||||
# android vars
|
||||
ANDROID_SDK_ROOT = "${androidComposition.androidsdk}/libexec/android-sdk";
|
||||
ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";
|
||||
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
||||
# this is most likely not needed. for some reason shadows flicker without it.
|
||||
AMD_VULKAN_ICD = "RADV";
|
||||
};
|
||||
# This only formats the nix files.
|
||||
formatter = pkgs.nixpkgs-fmt;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
543
src/lib.rs
543
src/lib.rs
@@ -1,541 +1,2 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use bevy_mod_openxr;
|
||||
pub use bevy_mod_xr;
|
||||
|
||||
@@ -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