make app close when requested by os and other stuff (#61)
* idk * mr stuff * done * fix windows * add patches to Readme
This commit is contained in:
10
Cargo.toml
10
Cargo.toml
@@ -8,8 +8,8 @@ license = "MIT/Apache-2.0"
|
|||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["linked"]
|
default = []
|
||||||
linked = ["openxr/linked"]
|
force-link = ["openxr/linked"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["examples/android", "examples/demo"]
|
members = ["examples/android", "examples/demo"]
|
||||||
@@ -24,9 +24,10 @@ wgpu = "0.17.1"
|
|||||||
wgpu-core = { version = "0.17.1", features = ["vulkan"] }
|
wgpu-core = { version = "0.17.1", features = ["vulkan"] }
|
||||||
wgpu-hal = "0.17.1"
|
wgpu-hal = "0.17.1"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
openxr = { version = "0.17.1", features = ["linked","static","mint"] }
|
||||||
[target.'cfg(all(target_family = "unix", not(target_arch = "wasm32")) )'.dependencies]
|
[target.'cfg(all(target_family = "unix", not(target_arch = "wasm32")) )'.dependencies]
|
||||||
openxr = { version = "0.17.1", features = ["mint"] }
|
openxr = { version = "0.17.1", features = ["mint"] }
|
||||||
|
|
||||||
[target.'cfg(all(not(target_family = "unix"), not(target_arch = "wasm32")))'.dependencies]
|
[target.'cfg(all(not(target_family = "unix"), not(target_arch = "wasm32")))'.dependencies]
|
||||||
openxr = { version = "0.17.1", features = ["mint", "static"] }
|
openxr = { version = "0.17.1", features = ["mint", "static"] }
|
||||||
|
|
||||||
@@ -41,3 +42,6 @@ path = "examples/xr.rs"
|
|||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
ndk = { git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -10,3 +10,14 @@ To see it in action run the example in `examples` with `cargo run --example xr`
|
|||||||
- Make sure, if you're on Linux, that you have the `openxr` package installed on your system.
|
- Make sure, if you're on Linux, that you have the `openxr` package installed on your system.
|
||||||
- I'm getting poor performance.
|
- 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 --example xr --release`)
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
for now we recommend you to add the folowing to your root Cargo.toml
|
||||||
|
```
|
||||||
|
[patch.crates-io]
|
||||||
|
ndk = { git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
ndk-sys = { package = "ndk-sys", git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
ndk-context = { package = "ndk-context", git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
bevy_pbr = { package = "bevy_pbr", git = "https://github.com/MalekiRe/bevy", branch = "release-0.12.1" }
|
||||||
|
```
|
||||||
|
|||||||
@@ -10,13 +10,9 @@ license = "MIT OR Apache-2.0"
|
|||||||
name = "bevy_openxr_android"
|
name = "bevy_openxr_android"
|
||||||
crate-type = ["rlib", "cdylib"]
|
crate-type = ["rlib", "cdylib"]
|
||||||
|
|
||||||
[target.'cfg(not(target_os="android"))'.dependencies.bevy_oxr]
|
|
||||||
path = "../../"
|
|
||||||
default-features = true
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_oxr = { path = "../..", default-features = false }
|
bevy_oxr.path = "../.."
|
||||||
bevy = "0.12"
|
bevy = "0.12"
|
||||||
openxr = { git = "https://github.com/Ralith/openxrs", features = ["mint"] }
|
openxr = { git = "https://github.com/Ralith/openxrs", features = ["mint"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::transform::components::Transform;
|
use bevy::transform::components::Transform;
|
||||||
|
use bevy_oxr::graphics::XrAppInfo;
|
||||||
use bevy_oxr::xr_input::debug_gizmos::OpenXrDebugRenderer;
|
use bevy_oxr::xr_input::debug_gizmos::OpenXrDebugRenderer;
|
||||||
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
|
use bevy_oxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
|
||||||
use bevy_oxr::xr_input::trackers::{
|
use bevy_oxr::xr_input::trackers::{
|
||||||
@@ -11,7 +12,12 @@ use bevy_oxr::DefaultXrPlugins;
|
|||||||
#[bevy_main]
|
#[bevy_main]
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultXrPlugins)
|
.add_plugins(DefaultXrPlugins {
|
||||||
|
app_info: XrAppInfo {
|
||||||
|
name: "Bevy OXR Android Example".into(),
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
})
|
||||||
.add_plugins(OpenXrDebugRenderer)
|
.add_plugins(OpenXrDebugRenderer)
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
.add_plugins(LogDiagnosticsPlugin::default())
|
||||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||||
|
|||||||
@@ -10,12 +10,21 @@ crate-type = ["rlib", "cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = "0.12"
|
bevy = "0.12"
|
||||||
bevy_oxr = { path = "../../", default-features = false }
|
bevy_oxr.path = "../../"
|
||||||
bevy_rapier3d = { git = "https://github.com/devil-ira/bevy_rapier", branch = "bevy-0.12" }
|
bevy_rapier3d = { git = "https://github.com/devil-ira/bevy_rapier", branch = "bevy-0.12" }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(not(target_os="android"))'.dependencies.bevy_oxr]
|
# [target.'cfg(not(target_os="android"))'.dependencies.bevy_oxr]
|
||||||
path = "../../"
|
# path = "../../"
|
||||||
# May need to be more specific. needs to be false at least on linux without an active runtime to run in flat
|
# # May need to be more specific. needs to be false at least on linux without an active runtime to run in flat
|
||||||
default-features = true
|
# default-features = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
ndk = { git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
ndk-sys = { package = "ndk-sys", git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
ndk-context = { package = "ndk-context", git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
ndk-glue = { package = "ndk-glue", git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ android:
|
|||||||
- "runtime_libs"
|
- "runtime_libs"
|
||||||
manifest:
|
manifest:
|
||||||
package: "org.bevyengine.demo_openxr_android"
|
package: "org.bevyengine.demo_openxr_android"
|
||||||
|
# Are features and permissions fliped?
|
||||||
uses_feature:
|
uses_feature:
|
||||||
- name: "android.hardware.vr.headtracking"
|
- name: "android.hardware.vr.headtracking"
|
||||||
required: true
|
required: true
|
||||||
- name: "oculus.software.handtracking"
|
- name: "oculus.software.handtracking"
|
||||||
required: false
|
required: false
|
||||||
- name: "com.oculus.experimental.enabled"
|
# - name: "com.oculus.feature.PASSTHROUGH"
|
||||||
required: true
|
# required: true
|
||||||
uses_permission:
|
uses_permission:
|
||||||
- name: "com.oculus.permission.HAND_TRACKING"
|
- name: "com.oculus.permission.HAND_TRACKING"
|
||||||
application:
|
application:
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ use bevy::{
|
|||||||
transform::TransformSystem,
|
transform::TransformSystem,
|
||||||
};
|
};
|
||||||
use bevy_oxr::{
|
use bevy_oxr::{
|
||||||
|
graphics::{extensions::XrExtensions, XrAppInfo, XrPreferdBlendMode},
|
||||||
input::XrInput,
|
input::XrInput,
|
||||||
xr_init::{XrEnableRequest, XrEnableStatus, xr_only},
|
|
||||||
resources::{XrFrameState, XrInstance, XrSession},
|
resources::{XrFrameState, XrInstance, XrSession},
|
||||||
|
xr_init::{xr_only, XrEnableRequest, XrEnableStatus},
|
||||||
xr_input::{
|
xr_input::{
|
||||||
actions::XrActionSets,
|
actions::XrActionSets,
|
||||||
debug_gizmos::OpenXrDebugRenderer,
|
debug_gizmos::OpenXrDebugRenderer,
|
||||||
@@ -31,7 +32,10 @@ use bevy_oxr::{
|
|||||||
},
|
},
|
||||||
oculus_touch::OculusController,
|
oculus_touch::OculusController,
|
||||||
prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig},
|
prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig},
|
||||||
trackers::{OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker},
|
trackers::{
|
||||||
|
OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
|
||||||
|
OpenXRTrackingRoot,
|
||||||
|
},
|
||||||
Hand,
|
Hand,
|
||||||
},
|
},
|
||||||
DefaultXrPlugins,
|
DefaultXrPlugins,
|
||||||
@@ -61,13 +65,20 @@ pub fn main() {
|
|||||||
|
|
||||||
info!("Running bevy_openxr demo");
|
info!("Running bevy_openxr demo");
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
let mut xr_extensions = XrExtensions::default();
|
||||||
|
|
||||||
app.add_systems(Update, input_stuff)
|
app.add_systems(Update, input_stuff)
|
||||||
//lets get the usual diagnostic stuff added
|
//lets get the usual diagnostic stuff added
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
.add_plugins(LogDiagnosticsPlugin::default())
|
||||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||||
//lets get the xr defaults added
|
//lets get the xr defaults added
|
||||||
.add_plugins(DefaultXrPlugins)
|
.add_plugins(DefaultXrPlugins {
|
||||||
|
reqeusted_extensions: xr_extensions,
|
||||||
|
prefered_blend_mode: XrPreferdBlendMode::Opaque,
|
||||||
|
app_info: XrAppInfo {
|
||||||
|
name: "Bevy OXR Demo".into(),
|
||||||
|
},
|
||||||
|
})
|
||||||
//lets add the debug renderer for the controllers
|
//lets add the debug renderer for the controllers
|
||||||
.add_plugins(OpenXrDebugRenderer)
|
.add_plugins(OpenXrDebugRenderer)
|
||||||
//rapier goes here
|
//rapier goes here
|
||||||
@@ -93,7 +104,9 @@ pub fn main() {
|
|||||||
//draw the interaction gizmos
|
//draw the interaction gizmos
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
draw_interaction_gizmos.run_if(xr_only()).after(update_interactable_states),
|
draw_interaction_gizmos
|
||||||
|
.run_if(xr_only())
|
||||||
|
.after(update_interactable_states),
|
||||||
)
|
)
|
||||||
.add_systems(Update, draw_socket_gizmos.after(update_interactable_states))
|
.add_systems(Update, draw_socket_gizmos.after(update_interactable_states))
|
||||||
//add our cube spawning system
|
//add our cube spawning system
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
|
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
|
||||||
use bevy_rapier3d::{
|
use bevy_rapier3d::{
|
||||||
prelude::{Collider, RigidBody, Group, CollisionGroups},
|
prelude::{Collider, CollisionGroups, Group, RigidBody},
|
||||||
render::ColliderDebugColor,
|
render::ColliderDebugColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ pub fn setup_scene(
|
|||||||
// });
|
// });
|
||||||
// camera
|
// camera
|
||||||
commands.spawn((Camera3dBundle {
|
commands.spawn((Camera3dBundle {
|
||||||
transform: Transform::from_xyz(0.25, 1.25, 0.0).looking_at(
|
transform: Transform::from_xyz(5.25, 5.25, 5.0).looking_at(
|
||||||
Vec3 {
|
Vec3 {
|
||||||
x: -0.548,
|
x: -0.548,
|
||||||
y: -0.161,
|
y: -0.161,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use bevy::diagnostic::LogDiagnosticsPlugin;
|
use bevy::diagnostic::LogDiagnosticsPlugin;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::transform::components::Transform;
|
use bevy::transform::components::Transform;
|
||||||
|
use bevy_oxr::graphics::XrAppInfo;
|
||||||
use bevy_oxr::resources::XrViews;
|
use bevy_oxr::resources::XrViews;
|
||||||
use bevy_oxr::xr_input::hands::common::{HandInputDebugRenderer, OpenXrHandInput};
|
use bevy_oxr::xr_input::hands::common::{HandInputDebugRenderer, OpenXrHandInput};
|
||||||
use bevy_oxr::xr_input::interactions::{
|
use bevy_oxr::xr_input::interactions::{
|
||||||
@@ -19,7 +20,12 @@ fn main() {
|
|||||||
color_eyre::install().unwrap();
|
color_eyre::install().unwrap();
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultXrPlugins)
|
.add_plugins(DefaultXrPlugins {
|
||||||
|
app_info: XrAppInfo {
|
||||||
|
name: "Bevy OXR Globe Example".into(),
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
})
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
.add_plugins(LogDiagnosticsPlugin::default())
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, (proto_locomotion, pull_to_ground).chain())
|
.add_systems(Update, (proto_locomotion, pull_to_ground).chain())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::transform::components::Transform;
|
use bevy::transform::components::Transform;
|
||||||
|
use bevy_oxr::graphics::XrAppInfo;
|
||||||
use bevy_oxr::input::XrInput;
|
use bevy_oxr::input::XrInput;
|
||||||
use bevy_oxr::resources::{XrFrameState, XrSession};
|
use bevy_oxr::resources::{XrFrameState, XrSession};
|
||||||
|
|
||||||
@@ -25,7 +26,12 @@ fn main() {
|
|||||||
|
|
||||||
info!("Running `openxr-6dof` skill");
|
info!("Running `openxr-6dof` skill");
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultXrPlugins)
|
.add_plugins(DefaultXrPlugins {
|
||||||
|
app_info: XrAppInfo {
|
||||||
|
name: "Bevy OXR Example".into(),
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
})
|
||||||
//.add_plugins(OpenXrDebugRenderer) //new debug renderer adds gizmos to
|
//.add_plugins(OpenXrDebugRenderer) //new debug renderer adds gizmos to
|
||||||
.add_plugins(LogDiagnosticsPlugin::default())
|
.add_plugins(LogDiagnosticsPlugin::default())
|
||||||
.add_plugins(FrameTimeDiagnosticsPlugin)
|
.add_plugins(FrameTimeDiagnosticsPlugin)
|
||||||
|
|||||||
242
src/graphics/extensions.rs
Normal file
242
src/graphics/extensions.rs
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
Self(exts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ops::BitAnd for XrExtensions {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn bitand(self, rhs: Self) -> Self::Output {
|
||||||
|
let mut out = ExtensionSet::default();
|
||||||
|
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,3 +1,4 @@
|
|||||||
|
pub mod extensions;
|
||||||
mod vulkan;
|
mod vulkan;
|
||||||
|
|
||||||
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||||
@@ -12,8 +13,37 @@ use crate::resources::{
|
|||||||
|
|
||||||
use openxr as xr;
|
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 initialize_xr_graphics(
|
pub fn initialize_xr_graphics(
|
||||||
window: Option<RawHandleWrapper>,
|
window: Option<RawHandleWrapper>,
|
||||||
|
reqeusted_extensions: XrExtensions,
|
||||||
|
prefered_blend_mode: XrPreferdBlendMode,
|
||||||
|
app_info: XrAppInfo,
|
||||||
) -> anyhow::Result<(
|
) -> anyhow::Result<(
|
||||||
RenderDevice,
|
RenderDevice,
|
||||||
RenderQueue,
|
RenderQueue,
|
||||||
@@ -32,13 +62,13 @@ pub fn initialize_xr_graphics(
|
|||||||
XrViews,
|
XrViews,
|
||||||
XrFrameState,
|
XrFrameState,
|
||||||
)> {
|
)> {
|
||||||
vulkan::initialize_xr_graphics(window)
|
vulkan::initialize_xr_graphics(window, reqeusted_extensions, prefered_blend_mode, app_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn xr_entry() -> anyhow::Result<xr::Entry> {
|
pub fn xr_entry() -> anyhow::Result<xr::Entry> {
|
||||||
#[cfg(feature = "linked")]
|
#[cfg(windows)]
|
||||||
let entry = Ok(xr::Entry::linked());
|
let entry = Ok(xr::Entry::linked());
|
||||||
#[cfg(not(feature = "linked"))]
|
#[cfg(not(windows))]
|
||||||
let entry = unsafe { xr::Entry::load().map_err(|e| anyhow::anyhow!(e)) };
|
let entry = unsafe { xr::Entry::load().map_err(|e| anyhow::anyhow!(e)) };
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, Ren
|
|||||||
use bevy::window::RawHandleWrapper;
|
use bevy::window::RawHandleWrapper;
|
||||||
use openxr as xr;
|
use openxr as xr;
|
||||||
use wgpu::Instance;
|
use wgpu::Instance;
|
||||||
|
use xr::EnvironmentBlendMode;
|
||||||
|
|
||||||
|
use crate::graphics::extensions::XrExtensions;
|
||||||
use crate::input::XrInput;
|
use crate::input::XrInput;
|
||||||
use crate::resources::{
|
use crate::resources::{
|
||||||
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter,
|
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter,
|
||||||
@@ -18,10 +20,13 @@ use crate::resources::{
|
|||||||
};
|
};
|
||||||
use crate::VIEW_TYPE;
|
use crate::VIEW_TYPE;
|
||||||
|
|
||||||
|
use super::{XrAppInfo, XrPreferdBlendMode};
|
||||||
|
|
||||||
pub fn initialize_xr_graphics(
|
pub fn initialize_xr_graphics(
|
||||||
window: Option<RawHandleWrapper>,
|
window: Option<RawHandleWrapper>,
|
||||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
reqeusted_extensions: XrExtensions,
|
||||||
// any extension at some point
|
prefered_blend_mode: XrPreferdBlendMode,
|
||||||
|
app_info: XrAppInfo,
|
||||||
) -> anyhow::Result<(
|
) -> anyhow::Result<(
|
||||||
RenderDevice,
|
RenderDevice,
|
||||||
RenderQueue,
|
RenderQueue,
|
||||||
@@ -39,8 +44,6 @@ pub fn initialize_xr_graphics(
|
|||||||
XrInput,
|
XrInput,
|
||||||
XrViews,
|
XrViews,
|
||||||
XrFrameState,
|
XrFrameState,
|
||||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
|
||||||
// any extension at some point
|
|
||||||
)> {
|
)> {
|
||||||
use wgpu_hal::{api::Vulkan as V, Api};
|
use wgpu_hal::{api::Vulkan as V, Api};
|
||||||
|
|
||||||
@@ -49,26 +52,25 @@ pub fn initialize_xr_graphics(
|
|||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
xr_entry.initialize_android_loader()?;
|
xr_entry.initialize_android_loader()?;
|
||||||
|
|
||||||
let available_extensions = xr_entry.enumerate_extensions()?;
|
let available_extensions: XrExtensions = xr_entry.enumerate_extensions()?.into();
|
||||||
assert!(available_extensions.khr_vulkan_enable2);
|
assert!(available_extensions.raw().khr_vulkan_enable2);
|
||||||
info!("available xr exts: {:#?}", available_extensions);
|
info!("available xr exts: {:#?}", available_extensions);
|
||||||
|
|
||||||
let mut enabled_extensions = xr::ExtensionSet::default();
|
let mut enabled_extensions: xr::ExtensionSet =
|
||||||
|
(available_extensions & reqeusted_extensions).into();
|
||||||
enabled_extensions.khr_vulkan_enable2 = true;
|
enabled_extensions.khr_vulkan_enable2 = true;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
{
|
{
|
||||||
enabled_extensions.khr_android_create_instance = true;
|
enabled_extensions.khr_android_create_instance = true;
|
||||||
}
|
}
|
||||||
enabled_extensions.ext_hand_tracking = available_extensions.ext_hand_tracking;
|
|
||||||
// enabled_extensions.ext_hand_joints_motion_range = available_extensions.ext_hand_joints_motion_range;
|
|
||||||
|
|
||||||
|
|
||||||
let available_layers = xr_entry.enumerate_layers()?;
|
let available_layers = xr_entry.enumerate_layers()?;
|
||||||
info!("available xr layers: {:#?}", available_layers);
|
info!("available xr layers: {:#?}", available_layers);
|
||||||
|
|
||||||
let xr_instance = xr_entry.create_instance(
|
let xr_instance = xr_entry.create_instance(
|
||||||
&xr::ApplicationInfo {
|
&xr::ApplicationInfo {
|
||||||
application_name: "Ambient",
|
application_name: &app_info.name,
|
||||||
|
engine_name: "Bevy",
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
&enabled_extensions,
|
&enabled_extensions,
|
||||||
@@ -90,7 +92,22 @@ pub fn initialize_xr_graphics(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let blend_mode = xr_instance.enumerate_environment_blend_modes(xr_system_id, VIEW_TYPE)?[0];
|
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) => {
|
||||||
|
EnvironmentBlendMode::OPAQUE
|
||||||
|
}
|
||||||
|
XrPreferdBlendMode::Additive if blend_modes.contains(&EnvironmentBlendMode::ADDITIVE) => {
|
||||||
|
EnvironmentBlendMode::ADDITIVE
|
||||||
|
}
|
||||||
|
XrPreferdBlendMode::AlphaBlend
|
||||||
|
if blend_modes.contains(&EnvironmentBlendMode::ALPHA_BLEND) =>
|
||||||
|
{
|
||||||
|
EnvironmentBlendMode::ALPHA_BLEND
|
||||||
|
}
|
||||||
|
_ => EnvironmentBlendMode::OPAQUE,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
let vk_target_version = vk::make_api_version(0, 1, 2, 0);
|
let vk_target_version = vk::make_api_version(0, 1, 2, 0);
|
||||||
@@ -131,7 +148,7 @@ pub fn initialize_xr_graphics(
|
|||||||
let vk_instance = unsafe {
|
let vk_instance = unsafe {
|
||||||
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
|
let extensions_cchar: Vec<_> = extensions.iter().map(|s| s.as_ptr()).collect();
|
||||||
|
|
||||||
let app_name = CString::new("Ambient")?;
|
let app_name = CString::new(app_info.name)?;
|
||||||
let vk_app_info = vk::ApplicationInfo::builder()
|
let vk_app_info = vk::ApplicationInfo::builder()
|
||||||
.application_name(&app_name)
|
.application_name(&app_name)
|
||||||
.application_version(1)
|
.application_version(1)
|
||||||
@@ -409,8 +426,6 @@ pub fn initialize_xr_graphics(
|
|||||||
should_render: true,
|
should_render: true,
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
// Horrible hack to get the Handtacking extension Loaded, Replace with good system to load
|
|
||||||
// any extension at some point
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
src/input.rs
30
src/input.rs
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use openxr as xr;
|
use openxr as xr;
|
||||||
|
use xr::{FrameState, FrameWaiter, ViewConfigurationType};
|
||||||
|
|
||||||
#[derive(Clone, Resource)]
|
#[derive(Clone, Resource)]
|
||||||
pub struct XrInput {
|
pub struct XrInput {
|
||||||
@@ -14,13 +15,12 @@ pub struct XrInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl XrInput {
|
impl XrInput {
|
||||||
pub fn new(_instance: xr::Instance, session: xr::Session<xr::AnyGraphics>) -> xr::Result<Self> {
|
pub fn new(
|
||||||
// let action_set = instance.create_action_set("input", "input pose information", 0)?;
|
instance: xr::Instance,
|
||||||
// let left_hand_subaction_path = instance.string_to_path("/user/hand/left").unwrap();
|
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_subaction_path = instance.string_to_path("/user/hand/right").unwrap();
|
||||||
// let left_hand_grip_pose_path = instance
|
|
||||||
// .string_to_path("/user/hand/left/input/grip/pose")
|
|
||||||
// .unwrap();
|
|
||||||
// let right_hand_grip_pose_path = instance
|
// let right_hand_grip_pose_path = instance
|
||||||
// .string_to_path("/user/hand/right/input/grip/pose")
|
// .string_to_path("/user/hand/right/input/grip/pose")
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
@@ -49,11 +49,23 @@ impl XrInput {
|
|||||||
// left_hand_subaction_path,
|
// left_hand_subaction_path,
|
||||||
// xr::Posef::IDENTITY,
|
// xr::Posef::IDENTITY,
|
||||||
// )?;
|
// )?;
|
||||||
|
|
||||||
let stage =
|
let stage =
|
||||||
session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;
|
session.create_reference_space(xr::ReferenceSpaceType::STAGE, xr::Posef::IDENTITY)?;
|
||||||
let head = session
|
let head =
|
||||||
.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)
|
session.create_reference_space(xr::ReferenceSpaceType::VIEW, xr::Posef::IDENTITY)?;
|
||||||
.unwrap();
|
// 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(&[&action_set])?;
|
||||||
//session.attach_action_sets(&[])?;
|
//session.attach_action_sets(&[])?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|||||||
96
src/lib.rs
96
src/lib.rs
@@ -1,5 +1,6 @@
|
|||||||
mod graphics;
|
pub mod graphics;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
// pub mod passthrough;
|
||||||
pub mod resource_macros;
|
pub mod resource_macros;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod xr_init;
|
pub mod xr_init;
|
||||||
@@ -10,33 +11,23 @@ use std::sync::{Arc, Mutex};
|
|||||||
use crate::xr_init::RenderRestartPlugin;
|
use crate::xr_init::RenderRestartPlugin;
|
||||||
use crate::xr_input::hands::hand_tracking::DisableHandTracking;
|
use crate::xr_input::hands::hand_tracking::DisableHandTracking;
|
||||||
use crate::xr_input::oculus_touch::ActionSets;
|
use crate::xr_input::oculus_touch::ActionSets;
|
||||||
use bevy::app::PluginGroupBuilder;
|
use bevy::app::{AppExit, PluginGroupBuilder};
|
||||||
use bevy::ecs::system::{RunSystemOnce, SystemState};
|
use bevy::ecs::system::SystemState;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::camera::{
|
use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews};
|
||||||
CameraPlugin, ManualTextureView, ManualTextureViewHandle, ManualTextureViews,
|
|
||||||
};
|
|
||||||
use bevy::render::globals::GlobalsPlugin;
|
|
||||||
use bevy::render::mesh::morph::MorphPlugin;
|
|
||||||
use bevy::render::mesh::MeshPlugin;
|
|
||||||
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
use bevy::render::pipelined_rendering::PipelinedRenderingPlugin;
|
||||||
use bevy::render::render_asset::RenderAssetDependency;
|
use bevy::render::renderer::{render_system, RenderInstance};
|
||||||
use bevy::render::render_resource::ShaderLoader;
|
|
||||||
use bevy::render::renderer::{
|
|
||||||
render_system, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
|
|
||||||
};
|
|
||||||
use bevy::render::settings::RenderCreation;
|
use bevy::render::settings::RenderCreation;
|
||||||
use bevy::render::view::{self, ViewPlugin, WindowRenderPlugin};
|
use bevy::render::{Render, RenderApp, RenderPlugin, RenderSet};
|
||||||
use bevy::render::{color, primitives, Render, RenderApp, RenderPlugin, RenderSet};
|
|
||||||
use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper};
|
use bevy::window::{PresentMode, PrimaryWindow, RawHandleWrapper};
|
||||||
|
use graphics::extensions::XrExtensions;
|
||||||
|
use graphics::{XrAppInfo, XrPreferdBlendMode};
|
||||||
use input::XrInput;
|
use input::XrInput;
|
||||||
use openxr as xr;
|
use openxr as xr;
|
||||||
|
// use passthrough::{start_passthrough, supports_passthrough, XrPassthroughLayer};
|
||||||
use resources::*;
|
use resources::*;
|
||||||
use xr::FormFactor;
|
use xr::FormFactor;
|
||||||
use xr_init::{
|
use xr_init::{xr_only, XrEnableStatus, XrRenderData};
|
||||||
init_non_xr_graphics, update_xr_stuff, xr_only, RenderCreationData, XrEnableRequest,
|
|
||||||
XrEnableStatus, XrRenderData, XrRenderUpdate,
|
|
||||||
};
|
|
||||||
use xr_input::controllers::XrControllerType;
|
use xr_input::controllers::XrControllerType;
|
||||||
use xr_input::hands::emulated::HandEmulationPlugin;
|
use xr_input::hands::emulated::HandEmulationPlugin;
|
||||||
use xr_input::hands::hand_tracking::{HandTrackingData, HandTrackingPlugin};
|
use xr_input::hands::hand_tracking::{HandTrackingData, HandTrackingPlugin};
|
||||||
@@ -48,12 +39,11 @@ pub const LEFT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHan
|
|||||||
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
|
pub const RIGHT_XR_TEXTURE_HANDLE: ManualTextureViewHandle = ManualTextureViewHandle(3383858418);
|
||||||
|
|
||||||
/// Adds OpenXR support to an App
|
/// Adds OpenXR support to an App
|
||||||
pub struct OpenXrPlugin;
|
#[derive(Default)]
|
||||||
|
pub struct OpenXrPlugin {
|
||||||
impl Default for OpenXrPlugin {
|
reqeusted_extensions: XrExtensions,
|
||||||
fn default() -> Self {
|
prefered_blend_mode: XrPreferdBlendMode,
|
||||||
OpenXrPlugin
|
app_info: XrAppInfo,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@@ -76,6 +66,9 @@ pub struct FutureXrResources(
|
|||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
);
|
);
|
||||||
|
// fn mr_test(mut commands: Commands, passthrough_layer: Option<Res<XrPassthroughLayer>>) {
|
||||||
|
// commands.insert_resource(ClearColor(Color::rgba(0.0, 0.0, 0.0, 0.0)));
|
||||||
|
// }
|
||||||
|
|
||||||
impl Plugin for OpenXrPlugin {
|
impl Plugin for OpenXrPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
@@ -84,7 +77,12 @@ impl Plugin for OpenXrPlugin {
|
|||||||
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
match graphics::initialize_xr_graphics(primary_window.clone()) {
|
match graphics::initialize_xr_graphics(
|
||||||
|
primary_window.clone(),
|
||||||
|
self.reqeusted_extensions.clone(),
|
||||||
|
self.prefered_blend_mode,
|
||||||
|
self.app_info.clone(),
|
||||||
|
) {
|
||||||
Ok((
|
Ok((
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
@@ -149,6 +147,7 @@ impl Plugin for OpenXrPlugin {
|
|||||||
app.insert_resource(XrEnableStatus::Disabled);
|
app.insert_resource(XrEnableStatus::Disabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// app.add_systems(PreUpdate, mr_test);
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
app.add_plugins(RenderPlugin::default());
|
app.add_plugins(RenderPlugin::default());
|
||||||
@@ -180,6 +179,23 @@ impl Plugin for OpenXrPlugin {
|
|||||||
} else {
|
} else {
|
||||||
app.insert_resource(DisableHandTracking::Both);
|
app.insert_resource(DisableHandTracking::Both);
|
||||||
}
|
}
|
||||||
|
// let passthrough = data.xr_instance.exts().fb_passthrough.is_some()
|
||||||
|
// && supports_passthrough(
|
||||||
|
// &data.xr_instance,
|
||||||
|
// data.xr_instance
|
||||||
|
// .system(FormFactor::HEAD_MOUNTED_DISPLAY)
|
||||||
|
// .unwrap(),
|
||||||
|
// )
|
||||||
|
// .is_ok_and(|v| v);
|
||||||
|
// if passthrough {
|
||||||
|
// info!("Passthrough!");
|
||||||
|
// let (pl, p) = start_passthrough(&data);
|
||||||
|
// app.insert_resource(pl);
|
||||||
|
// app.insert_resource(p);
|
||||||
|
// // if !app.world.contains_resource::<ClearColor>() {
|
||||||
|
// // info!("ClearColor!");
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
|
||||||
let (left, right) = data.xr_swapchain.get_render_views();
|
let (left, right) = data.xr_swapchain.get_render_views();
|
||||||
let left = ManualTextureView {
|
let left = ManualTextureView {
|
||||||
@@ -225,7 +241,12 @@ impl Plugin for OpenXrPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DefaultXrPlugins;
|
#[derive(Default)]
|
||||||
|
pub struct DefaultXrPlugins {
|
||||||
|
pub reqeusted_extensions: XrExtensions,
|
||||||
|
pub prefered_blend_mode: XrPreferdBlendMode,
|
||||||
|
pub app_info: XrAppInfo,
|
||||||
|
}
|
||||||
|
|
||||||
impl PluginGroup for DefaultXrPlugins {
|
impl PluginGroup for DefaultXrPlugins {
|
||||||
fn build(self) -> PluginGroupBuilder {
|
fn build(self) -> PluginGroupBuilder {
|
||||||
@@ -233,7 +254,11 @@ impl PluginGroup for DefaultXrPlugins {
|
|||||||
.build()
|
.build()
|
||||||
.disable::<RenderPlugin>()
|
.disable::<RenderPlugin>()
|
||||||
.disable::<PipelinedRenderingPlugin>()
|
.disable::<PipelinedRenderingPlugin>()
|
||||||
.add_before::<RenderPlugin, _>(OpenXrPlugin)
|
.add_before::<RenderPlugin, _>(OpenXrPlugin {
|
||||||
|
prefered_blend_mode: self.prefered_blend_mode,
|
||||||
|
reqeusted_extensions: self.reqeusted_extensions,
|
||||||
|
app_info: self.app_info.clone(),
|
||||||
|
})
|
||||||
.add_after::<OpenXrPlugin, _>(OpenXrInput::new(XrControllerType::OculusTouch))
|
.add_after::<OpenXrPlugin, _>(OpenXrInput::new(XrControllerType::OculusTouch))
|
||||||
.add_before::<OpenXrPlugin, _>(RenderRestartPlugin)
|
.add_before::<OpenXrPlugin, _>(RenderRestartPlugin)
|
||||||
.add(HandEmulationPlugin)
|
.add(HandEmulationPlugin)
|
||||||
@@ -241,7 +266,9 @@ impl PluginGroup for DefaultXrPlugins {
|
|||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
primary_window: Some(Window {
|
primary_window: Some(Window {
|
||||||
|
transparent: true,
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
|
title: self.app_info.name.clone(),
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
@@ -264,6 +291,7 @@ pub fn xr_begin_frame(
|
|||||||
swapchain: Res<XrSwapchain>,
|
swapchain: Res<XrSwapchain>,
|
||||||
views: Res<XrViews>,
|
views: Res<XrViews>,
|
||||||
input: Res<XrInput>,
|
input: Res<XrInput>,
|
||||||
|
mut app_exit: EventWriter<AppExit>,
|
||||||
) {
|
) {
|
||||||
{
|
{
|
||||||
let _span = info_span!("xr_poll_events");
|
let _span = info_span!("xr_poll_events");
|
||||||
@@ -282,8 +310,12 @@ pub fn xr_begin_frame(
|
|||||||
xr::SessionState::STOPPING => {
|
xr::SessionState::STOPPING => {
|
||||||
session.end().unwrap();
|
session.end().unwrap();
|
||||||
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
|
session_running.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
app_exit.send(AppExit);
|
||||||
|
}
|
||||||
|
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => {
|
||||||
|
app_exit.send(AppExit);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => return,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,6 +393,7 @@ pub fn end_frame(
|
|||||||
swapchain: Res<XrSwapchain>,
|
swapchain: Res<XrSwapchain>,
|
||||||
resolution: Res<XrResolution>,
|
resolution: Res<XrResolution>,
|
||||||
environment_blend_mode: Res<XrEnvironmentBlendMode>,
|
environment_blend_mode: Res<XrEnvironmentBlendMode>,
|
||||||
|
// passthrough_layer: Option<Res<XrPassthroughLayer>>,
|
||||||
) {
|
) {
|
||||||
{
|
{
|
||||||
let _span = info_span!("xr_release_image").entered();
|
let _span = info_span!("xr_release_image").entered();
|
||||||
@@ -370,10 +403,11 @@ pub fn end_frame(
|
|||||||
let _span = info_span!("xr_end_frame").entered();
|
let _span = info_span!("xr_end_frame").entered();
|
||||||
let result = swapchain.end(
|
let result = swapchain.end(
|
||||||
xr_frame_state.lock().unwrap().predicted_display_time,
|
xr_frame_state.lock().unwrap().predicted_display_time,
|
||||||
&*views.lock().unwrap(),
|
&views.lock().unwrap(),
|
||||||
&input.stage,
|
&input.stage,
|
||||||
**resolution,
|
**resolution,
|
||||||
**environment_blend_mode,
|
**environment_blend_mode,
|
||||||
|
// passthrough_layer.map(|p| p.into_inner()),
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
// use crate::passthrough::XrPassthroughLayer;
|
||||||
use crate::resource_macros::*;
|
use crate::resource_macros::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use openxr as xr;
|
use openxr as xr;
|
||||||
@@ -58,6 +59,7 @@ impl Swapchain {
|
|||||||
stage: &xr::Space,
|
stage: &xr::Space,
|
||||||
resolution: UVec2,
|
resolution: UVec2,
|
||||||
environment_blend_mode: xr::EnvironmentBlendMode,
|
environment_blend_mode: xr::EnvironmentBlendMode,
|
||||||
|
// passthrough_layer: Option<&XrPassthroughLayer>,
|
||||||
) -> xr::Result<()> {
|
) -> xr::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Swapchain::Vulkan(swapchain) => swapchain.end(
|
Swapchain::Vulkan(swapchain) => swapchain.end(
|
||||||
@@ -66,6 +68,7 @@ impl Swapchain {
|
|||||||
stage,
|
stage,
|
||||||
resolution,
|
resolution,
|
||||||
environment_blend_mode,
|
environment_blend_mode,
|
||||||
|
// passthrough_layer,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,6 +128,7 @@ impl<G: xr::Graphics> SwapchainInner<G> {
|
|||||||
stage: &xr::Space,
|
stage: &xr::Space,
|
||||||
resolution: UVec2,
|
resolution: UVec2,
|
||||||
environment_blend_mode: xr::EnvironmentBlendMode,
|
environment_blend_mode: xr::EnvironmentBlendMode,
|
||||||
|
// passthrough_layer: Option<&XrPassthroughLayer>,
|
||||||
) -> xr::Result<()> {
|
) -> xr::Result<()> {
|
||||||
let rect = xr::Rect2Di {
|
let rect = xr::Rect2Di {
|
||||||
offset: xr::Offset2Di { x: 0, y: 0 },
|
offset: xr::Offset2Di { x: 0, y: 0 },
|
||||||
@@ -138,6 +142,51 @@ impl<G: xr::Graphics> SwapchainInner<G> {
|
|||||||
warn!("views are len of 0");
|
warn!("views are len of 0");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
// match passthrough_layer {
|
||||||
|
// Some(pass) => {
|
||||||
|
// // info!("Rendering with pass through");
|
||||||
|
// let passthrough_layer = xr::sys::CompositionLayerPassthroughFB {
|
||||||
|
// ty: CompositionLayerPassthroughFB::TYPE,
|
||||||
|
// next: ptr::null(),
|
||||||
|
// flags: CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA,
|
||||||
|
// space: xr::sys::Space::NULL,
|
||||||
|
// layer_handle: pass.0,
|
||||||
|
// };
|
||||||
|
// self.stream.lock().unwrap().end(
|
||||||
|
// predicted_display_time,
|
||||||
|
// environment_blend_mode,
|
||||||
|
// &[
|
||||||
|
// &xr::CompositionLayerProjection::new()
|
||||||
|
// .layer_flags(CompositionLayerFlags::UNPREMULTIPLIED_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),
|
||||||
|
// ),
|
||||||
|
// ]),
|
||||||
|
// unsafe {
|
||||||
|
// &*(&passthrough_layer as *const _ as *const CompositionLayerBase<G>)
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// None =>
|
||||||
self.stream.lock().unwrap().end(
|
self.stream.lock().unwrap().end(
|
||||||
predicted_display_time,
|
predicted_display_time,
|
||||||
environment_blend_mode,
|
environment_blend_mode,
|
||||||
@@ -162,5 +211,6 @@ impl<G: xr::Graphics> SwapchainInner<G> {
|
|||||||
),
|
),
|
||||||
])],
|
])],
|
||||||
)
|
)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pub fn setup_oxr_actions(world: &mut World) {
|
|||||||
actions.insert(action_name, typed_action);
|
actions.insert(action_name, typed_action);
|
||||||
for (device_path, bindings) in action.bindings.into_iter() {
|
for (device_path, bindings) in action.bindings.into_iter() {
|
||||||
for b in bindings {
|
for b in bindings {
|
||||||
info!("binding {} to {}", action_name, b);
|
// info!("binding {} to {}", action_name, b);
|
||||||
action_bindings
|
action_bindings
|
||||||
.entry((set_name, action_name))
|
.entry((set_name, action_name))
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -117,7 +117,6 @@ pub fn setup_oxr_actions(world: &mut World) {
|
|||||||
.map(move |(dev, bindings)| (action, dev, bindings))
|
.map(move |(dev, bindings)| (action, dev, bindings))
|
||||||
})
|
})
|
||||||
.map(|(action, dev, bindings)| {
|
.map(|(action, dev, bindings)| {
|
||||||
info!("Hi");
|
|
||||||
(
|
(
|
||||||
dev,
|
dev,
|
||||||
bindings
|
bindings
|
||||||
@@ -136,7 +135,6 @@ pub fn setup_oxr_actions(world: &mut World) {
|
|||||||
b_indings.entry(dev).or_default().append(&mut bindings);
|
b_indings.entry(dev).or_default().append(&mut bindings);
|
||||||
}
|
}
|
||||||
for (dev, bindings) in b_indings.into_iter() {
|
for (dev, bindings) in b_indings.into_iter() {
|
||||||
info!(dev);
|
|
||||||
instance
|
instance
|
||||||
.suggest_interaction_profile_bindings(instance.string_to_path(dev).unwrap(), &bindings)
|
.suggest_interaction_profile_bindings(instance.string_to_path(dev).unwrap(), &bindings)
|
||||||
.expect("Unable to suggest interaction bindings!");
|
.expect("Unable to suggest interaction bindings!");
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use bevy::prelude::{
|
|||||||
Query, Resource, SpatialBundle, Startup, Transform,
|
Query, Resource, SpatialBundle, Startup, Transform,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::xr_input::{Hand, trackers::OpenXRTracker};
|
use crate::xr_input::{trackers::OpenXRTracker, Hand};
|
||||||
|
|
||||||
use super::{HandBone, BoneTrackingStatus};
|
use super::{BoneTrackingStatus, HandBone};
|
||||||
|
|
||||||
/// add debug renderer for controllers
|
/// add debug renderer for controllers
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use openxr::{HandTracker, Result, SpaceLocationFlags};
|
use openxr::{HandTracker, Result, SpaceLocationFlags};
|
||||||
|
|
||||||
|
use super::common::HandBoneRadius;
|
||||||
use crate::{
|
use crate::{
|
||||||
input::XrInput,
|
input::XrInput,
|
||||||
|
|
||||||
resources::{XrFrameState, XrSession},
|
resources::{XrFrameState, XrSession},
|
||||||
xr_input::{
|
xr_init::xr_only,
|
||||||
hands::HandBone, trackers::OpenXRTrackingRoot, Hand, QuatConv,
|
xr_input::{hands::HandBone, trackers::OpenXRTrackingRoot, Hand, QuatConv, Vec3Conv},
|
||||||
Vec3Conv,
|
|
||||||
}, xr_init::xr_only,
|
|
||||||
};
|
};
|
||||||
use super::common::HandBoneRadius;
|
|
||||||
|
|
||||||
use super::BoneTrackingStatus;
|
use super::BoneTrackingStatus;
|
||||||
|
|
||||||
@@ -126,9 +123,11 @@ impl Plugin for HandTrackingPlugin {
|
|||||||
app.add_systems(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
(
|
(
|
||||||
update_hand_bones.run_if(|dh: Option<Res<DisableHandTracking>>| {
|
update_hand_bones
|
||||||
|
.run_if(|dh: Option<Res<DisableHandTracking>>| {
|
||||||
!dh.is_some_and(|v| *v == DisableHandTracking::Both)
|
!dh.is_some_and(|v| *v == DisableHandTracking::Both)
|
||||||
}).run_if(xr_only()),
|
})
|
||||||
|
.run_if(xr_only()),
|
||||||
update_tracking_state_on_disable,
|
update_tracking_state_on_disable,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use bevy::{app::PluginGroupBuilder, prelude::*};
|
|||||||
|
|
||||||
use self::{emulated::HandEmulationPlugin, hand_tracking::HandTrackingPlugin};
|
use self::{emulated::HandEmulationPlugin, hand_tracking::HandTrackingPlugin};
|
||||||
|
|
||||||
|
pub mod common;
|
||||||
pub mod emulated;
|
pub mod emulated;
|
||||||
pub mod hand_tracking;
|
pub mod hand_tracking;
|
||||||
pub mod common;
|
|
||||||
|
|
||||||
pub struct XrHandPlugins;
|
pub struct XrHandPlugins;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub mod xr_camera;
|
|||||||
|
|
||||||
use crate::resources::{XrInstance, XrSession};
|
use crate::resources::{XrInstance, XrSession};
|
||||||
use crate::xr_begin_frame;
|
use crate::xr_begin_frame;
|
||||||
use crate::xr_init::{xr_only, XrPostSetup, XrSetup};
|
use crate::xr_init::{xr_only, XrPostSetup, XrSetup, XrPreSetup};
|
||||||
use crate::xr_input::controllers::XrControllerType;
|
use crate::xr_input::controllers::XrControllerType;
|
||||||
use crate::xr_input::oculus_touch::setup_oculus_controller;
|
use crate::xr_input::oculus_touch::setup_oculus_controller;
|
||||||
use crate::xr_input::xr_camera::{xr_camera_head_sync, Eye, XRProjection, XrCameraBundle};
|
use crate::xr_input::xr_camera::{xr_camera_head_sync, Eye, XRProjection, XrCameraBundle};
|
||||||
@@ -30,7 +30,7 @@ use bevy::utils::HashMap;
|
|||||||
use openxr::Binding;
|
use openxr::Binding;
|
||||||
|
|
||||||
use self::actions::{setup_oxr_actions, OpenXrActionsPlugin};
|
use self::actions::{setup_oxr_actions, OpenXrActionsPlugin};
|
||||||
use self::oculus_touch::{post_action_setup_oculus_controller, ActionSets};
|
use self::oculus_touch::{post_action_setup_oculus_controller, ActionSets, init_subaction_path};
|
||||||
use self::trackers::{
|
use self::trackers::{
|
||||||
adopt_open_xr_trackers, update_open_xr_controllers, OpenXRLeftEye, OpenXRRightEye,
|
adopt_open_xr_trackers, update_open_xr_controllers, OpenXRLeftEye, OpenXRRightEye,
|
||||||
OpenXRTrackingRoot,
|
OpenXRTrackingRoot,
|
||||||
@@ -77,6 +77,7 @@ impl Plugin for OpenXrInput {
|
|||||||
.after(TransformSystem::TransformPropagate)
|
.after(TransformSystem::TransformPropagate)
|
||||||
.before(VisibilitySystems::UpdatePerspectiveFrusta),
|
.before(VisibilitySystems::UpdatePerspectiveFrusta),
|
||||||
);
|
);
|
||||||
|
app.add_systems(XrPreSetup, init_subaction_path);
|
||||||
app.add_systems(XrSetup, setup_xr_cameras);
|
app.add_systems(XrSetup, setup_xr_cameras);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::input::XrInput;
|
|||||||
use crate::resources::{XrInstance, XrSession};
|
use crate::resources::{XrInstance, XrSession};
|
||||||
use crate::xr_input::controllers::Handed;
|
use crate::xr_input::controllers::Handed;
|
||||||
use crate::xr_input::Hand;
|
use crate::xr_input::Hand;
|
||||||
use bevy::prelude::{Commands, Res, ResMut, Resource};
|
use bevy::prelude::{default, Commands, Res, ResMut, Resource};
|
||||||
use openxr::{
|
use openxr::{
|
||||||
ActionSet, AnyGraphics, FrameState, Instance, Path, Posef, Session, Space, SpaceLocation,
|
ActionSet, AnyGraphics, FrameState, Instance, Path, Posef, Session, Space, SpaceLocation,
|
||||||
SpaceVelocity,
|
SpaceVelocity,
|
||||||
@@ -50,7 +50,6 @@ pub fn setup_oculus_controller(
|
|||||||
action_sets: ResMut<SetupActionSets>,
|
action_sets: ResMut<SetupActionSets>,
|
||||||
) {
|
) {
|
||||||
let oculus_controller = OculusController::new(action_sets).unwrap();
|
let oculus_controller = OculusController::new(action_sets).unwrap();
|
||||||
init_subaction_path(&instance);
|
|
||||||
commands.insert_resource(oculus_controller);
|
commands.insert_resource(oculus_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +64,10 @@ pub struct OculusControllerRef<'a> {
|
|||||||
xr_input: &'a XrInput,
|
xr_input: &'a XrInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
static RIGHT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
pub static RIGHT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
||||||
static LEFT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
pub static LEFT_SUBACTION_PATH: OnceLock<Path> = OnceLock::new();
|
||||||
|
|
||||||
pub fn init_subaction_path(instance: &Instance) {
|
pub fn init_subaction_path(instance: Res<XrInstance>) {
|
||||||
let _ = LEFT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/left").unwrap());
|
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());
|
let _ = RIGHT_SUBACTION_PATH.set(instance.string_to_path("/user/hand/right").unwrap());
|
||||||
}
|
}
|
||||||
@@ -82,7 +81,7 @@ pub fn subaction_path(hand: Hand) -> Path {
|
|||||||
|
|
||||||
impl OculusControllerRef<'_> {
|
impl OculusControllerRef<'_> {
|
||||||
pub fn grip_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
pub fn grip_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
||||||
match hand {
|
let d = match hand {
|
||||||
Hand::Left => self
|
Hand::Left => self
|
||||||
.oculus_controller
|
.oculus_controller
|
||||||
.grip_space
|
.grip_space
|
||||||
@@ -103,11 +102,14 @@ impl OculusControllerRef<'_> {
|
|||||||
&self.xr_input.stage,
|
&self.xr_input.stage,
|
||||||
self.frame_state.predicted_display_time,
|
self.frame_state.predicted_display_time,
|
||||||
),
|
),
|
||||||
|
};
|
||||||
|
match d {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => (SpaceLocation::default(), SpaceVelocity::default()),
|
||||||
}
|
}
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
pub fn aim_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
pub fn aim_space(&self, hand: Hand) -> (SpaceLocation, SpaceVelocity) {
|
||||||
match hand {
|
let d = match hand {
|
||||||
Hand::Left => self
|
Hand::Left => self
|
||||||
.oculus_controller
|
.oculus_controller
|
||||||
.aim_space
|
.aim_space
|
||||||
@@ -128,146 +130,212 @@ impl OculusControllerRef<'_> {
|
|||||||
&self.xr_input.stage,
|
&self.xr_input.stage,
|
||||||
self.frame_state.predicted_display_time,
|
self.frame_state.predicted_display_time,
|
||||||
),
|
),
|
||||||
|
};
|
||||||
|
match d {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(_) => (SpaceLocation::default(), SpaceVelocity::default()),
|
||||||
}
|
}
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
pub fn squeeze(&self, hand: Hand) -> f32 {
|
pub fn squeeze(&self, hand: Hand) -> f32 {
|
||||||
let action = &self
|
match &self
|
||||||
.action_sets
|
.action_sets
|
||||||
.get_action_f32("oculus_input", "squeeze")
|
.get_action_f32("oculus_input", "squeeze")
|
||||||
.unwrap();
|
|
||||||
action
|
|
||||||
.state(&self.session, subaction_path(hand))
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.state(&self.session, subaction_path(hand))
|
||||||
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn trigger(&self, hand: Hand) -> f32 {
|
pub fn trigger(&self, hand: Hand) -> f32 {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_f32("oculus_input", "trigger")
|
.get_action_f32("oculus_input", "trigger")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn trigger_touched(&self, hand: Hand) -> bool {
|
pub fn trigger_touched(&self, hand: Hand) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "trigger_touched")
|
.get_action_bool("oculus_input", "trigger_touched")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn x_button(&self) -> bool {
|
pub fn x_button(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "x_button")
|
.get_action_bool("oculus_input", "x_button")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn x_button_touched(&self) -> bool {
|
pub fn x_button_touched(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "x_button_touch")
|
.get_action_bool("oculus_input", "x_button_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn y_button(&self) -> bool {
|
pub fn y_button(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "y_button")
|
.get_action_bool("oculus_input", "y_button")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn y_button_touched(&self) -> bool {
|
pub fn y_button_touched(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "y_button_touch")
|
.get_action_bool("oculus_input", "y_button_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn menu_button(&self) -> bool {
|
pub fn menu_button(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "menu_button")
|
.get_action_bool("oculus_input", "menu_button")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn a_button(&self) -> bool {
|
pub fn a_button(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "a_button")
|
.get_action_bool("oculus_input", "a_button")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn a_button_touched(&self) -> bool {
|
pub fn a_button_touched(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "a_button_touch")
|
.get_action_bool("oculus_input", "a_button_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn b_button(&self) -> bool {
|
pub fn b_button(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "b_button")
|
.get_action_bool("oculus_input", "b_button")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn b_button_touched(&self) -> bool {
|
pub fn b_button_touched(&self) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "b_button_touch")
|
.get_action_bool("oculus_input", "b_button_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, Path::NULL)
|
.state(&self.session, Path::NULL)
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn thumbstick_touch(&self, hand: Hand) -> bool {
|
pub fn thumbstick_touch(&self, hand: Hand) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "thumbstick_touch")
|
.get_action_bool("oculus_input", "thumbstick_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
pub fn thumbstick(&self, hand: Hand) -> Thumbstick {
|
pub fn thumbstick(&self, hand: Hand) -> Thumbstick {
|
||||||
Thumbstick {
|
Thumbstick {
|
||||||
x: self
|
x: match self
|
||||||
.action_sets
|
.action_sets
|
||||||
.get_action_f32("oculus_input", "thumbstick_x")
|
.get_action_f32("oculus_input", "thumbstick_x")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
.map(|v| v.current_state)
|
||||||
.current_state,
|
{
|
||||||
y: self
|
Ok(v) => v,
|
||||||
|
Err(_) => default(),
|
||||||
|
},
|
||||||
|
y: match self
|
||||||
.action_sets
|
.action_sets
|
||||||
.get_action_f32("oculus_input", "thumbstick_y")
|
.get_action_f32("oculus_input", "thumbstick_y")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
.map(|v| v.current_state)
|
||||||
.current_state,
|
{
|
||||||
click: self
|
Ok(v) => v,
|
||||||
|
Err(_) => default(),
|
||||||
|
},
|
||||||
|
click: match self
|
||||||
.action_sets
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "thumbstick_click")
|
.get_action_bool("oculus_input", "thumbstick_click")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
.map(|v| v.current_state)
|
||||||
.current_state,
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => default(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn thumbrest_touch(&self, hand: Hand) -> bool {
|
pub fn thumbrest_touch(&self, hand: Hand) -> bool {
|
||||||
self.action_sets
|
match self
|
||||||
|
.action_sets
|
||||||
.get_action_bool("oculus_input", "thumbrest_touch")
|
.get_action_bool("oculus_input", "thumbrest_touch")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.state(&self.session, subaction_path(hand))
|
.state(&self.session, subaction_path(hand))
|
||||||
.unwrap()
|
{
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return default(),
|
||||||
|
}
|
||||||
.current_state
|
.current_state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user