Merge pull request #68 from Schmarni-Dev/runtime_sessions

Allow XR Session starting and stopping at runtime, add pipelined rendering and fix on platforms without fb passthrough
This commit is contained in:
Schmarni
2024-02-22 11:27:30 +01:00
committed by GitHub
35 changed files with 1765 additions and 1251 deletions

View File

@@ -15,14 +15,15 @@ force-link = ["openxr/linked"]
members = ["examples/android", "examples/demo"] members = ["examples/android", "examples/demo"]
[dependencies] [dependencies]
anyhow = "1.0.75" # anyhow = "1.0.75"
ash = "0.37.3" ash = "0.37.3"
bevy = "0.12" bevy = "0.13"
futures-lite = "2.0.1" futures-lite = "2.0.1"
mint = "0.5.9" mint = "0.5.9"
wgpu = "0.17.1" wgpu = "0.19"
wgpu-core = { version = "0.17.1", features = ["vulkan"] } wgpu-core = { version = "0.19", features = ["vulkan"] }
wgpu-hal = "0.17.1" wgpu-hal = "0.19"
eyre = "0.6.11"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
openxr = { git = "https://github.com/Ralith/openxrs", rev = "0177d2d", features = [ openxr = { git = "https://github.com/Ralith/openxrs", rev = "0177d2d", features = [
@@ -45,9 +46,9 @@ ndk-context = "0.1"
jni = "0.20" jni = "0.20"
[dev-dependencies] [dev-dependencies]
bevy = "0.12" bevy = "0.13"
color-eyre = "0.6.2"
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"
[[example]] [[example]]
name = "xr" name = "xr"
@@ -57,4 +58,4 @@ path = "examples/xr.rs"
debug = true debug = true
[patch.crates-io] [patch.crates-io]
ndk = { git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" } # ndk = { git = "https://github.com/Schmarni-Dev/ndk.git", branch = "070" }

View File

@@ -13,13 +13,13 @@ crate-type = ["rlib", "cdylib"]
[dependencies] [dependencies]
bevy_oxr.path = "../.." bevy_oxr.path = "../.."
bevy = "0.12" bevy = "0.13"
openxr = { git = "https://github.com/Ralith/openxrs", rev = "0177d2d", features = ["mint"] } openxr = { git = "https://github.com/Ralith/openxrs", rev = "0177d2d", features = ["mint"] }
[profile.release] # [profile.release]
lto = "fat" # lto = "fat"
codegen-units = 1 # codegen-units = 1
panic = "abort" # panic = "abort"
# This metadata is used by `cargo-apk` - `xbuild` uses the `manifest.yaml` instead. # This metadata is used by `cargo-apk` - `xbuild` uses the `manifest.yaml` instead.
[package.metadata.android] [package.metadata.android]

View File

@@ -12,6 +12,8 @@ android:
required: true required: true
- name: "com.oculus.experimental.enabled" - name: "com.oculus.experimental.enabled"
required: true required: true
uses_permission:
- name: "com.oculus.permission.HAND_TRACKING"
application: application:
label: "Bevy Openxr Android" label: "Bevy Openxr Android"
theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen" theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"

View File

@@ -3,10 +3,11 @@ use bevy::prelude::*;
use bevy::transform::components::Transform; use bevy::transform::components::Transform;
use bevy_oxr::graphics::extensions::XrExtensions; use bevy_oxr::graphics::extensions::XrExtensions;
use bevy_oxr::graphics::XrAppInfo; use bevy_oxr::graphics::XrAppInfo;
use bevy_oxr::graphics::XrPreferdBlendMode::AlphaBlend; use bevy_oxr::passthrough::{PausePassthrough, ResumePassthrough, XrPassthroughState};
use bevy_oxr::passthrough::{passthrough_layer_pause, passthrough_layer_resume}; use bevy_oxr::xr_init::xr_only;
use bevy_oxr::xr_init::XrRenderData;
use bevy_oxr::xr_input::debug_gizmos::OpenXrDebugRenderer; use bevy_oxr::xr_input::debug_gizmos::OpenXrDebugRenderer;
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::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig};
use bevy_oxr::xr_input::trackers::{ use bevy_oxr::xr_input::trackers::{
OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker, OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker,
@@ -16,7 +17,8 @@ use bevy_oxr::DefaultXrPlugins;
#[bevy_main] #[bevy_main]
fn main() { fn main() {
let mut xr_extensions = XrExtensions::default(); let mut xr_extensions = XrExtensions::default();
xr_extensions.enable_fb_passthrough(); // xr_extensions.enable_fb_passthrough();
xr_extensions.enable_hand_tracking();
App::new() App::new()
.add_plugins(DefaultXrPlugins { .add_plugins(DefaultXrPlugins {
reqeusted_extensions: xr_extensions, reqeusted_extensions: xr_extensions,
@@ -25,16 +27,28 @@ fn main() {
}, },
prefered_blend_mode: bevy_oxr::graphics::XrPreferdBlendMode::Opaque, prefered_blend_mode: bevy_oxr::graphics::XrPreferdBlendMode::Opaque,
}) })
.add_plugins(OpenXrDebugRenderer) // .add_plugins(OpenXrDebugRenderer)
.add_plugins(LogDiagnosticsPlugin::default()) .add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin) .add_plugins(FrameTimeDiagnosticsPlugin)
.add_plugins(HandInputDebugRenderer)
// .add_plugins(bevy_oxr::passthrough::EnablePassthroughStartup)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (proto_locomotion, toggle_passthrough)) .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) .add_systems(Startup, spawn_controllers_example)
.insert_resource(PrototypeLocomotionConfig::default()) .insert_resource(PrototypeLocomotionConfig::default())
.run(); .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, Color::RED);
}
}
/// set up a simple 3D scene /// set up a simple 3D scene
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
@@ -43,21 +57,21 @@ fn setup(
) { ) {
// plane // plane
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()), mesh: meshes.add(Plane3d::new(Vec3::Y)),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
..default() ..default()
}); });
// cube // cube
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
transform: Transform::from_xyz(0.0, 0.5, 0.0), transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default() ..default()
}); });
// cube // cube
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Mesh::from(Cuboid::from_size(Vec3::splat(0.1)))),
material: materials.add(Color::rgb(0.8, 0.0, 0.0).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.0, 0.0))),
transform: Transform::from_xyz(0.0, 0.5, 1.0), transform: Transform::from_xyz(0.0, 0.5, 1.0),
..default() ..default()
}); });
@@ -90,15 +104,22 @@ fn spawn_controllers_example(mut commands: Commands) {
)); ));
} }
// Does this work? Not getting logs // TODO: make this a vr button
fn toggle_passthrough(keys: Res<Input<KeyCode>>, mut xr_data: ResMut<XrRenderData>) { 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) { if keys.just_pressed(KeyCode::Space) {
if xr_data.xr_passthrough_active { match *passthrough_state {
passthrough_layer_pause(xr_data); XrPassthroughState::Unsupported => {}
bevy::log::info!("Passthrough paused"); XrPassthroughState::Running => {
} else { pause.send_default();
passthrough_layer_resume(xr_data); }
bevy::log::info!("Passthrough resumed"); XrPassthroughState::Paused => {
resume.send_default();
}
} }
} }
} }

View File

@@ -9,9 +9,10 @@ crate-type = ["rlib", "cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bevy = "0.12" bevy = "0.13"
bevy_oxr.path = "../../" 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" }
bevy_rapier3d = "0.25"
color-eyre = "0.6.2" color-eyre = "0.6.2"

View File

@@ -3,26 +3,29 @@ 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: true
# - name: "com.oculus.feature.PASSTHROUGH" - name: "com.oculus.feature.PASSTHROUGH"
# required: true required: true
- name: "com.oculus.experimental.enabled"
required: true
uses_permission: uses_permission:
- name: "com.oculus.permission.HAND_TRACKING" - name: "com.oculus.permission.HAND_TRACKING"
application: application:
label: "Bevy Openxr Android" label: "Bevy Openxr Android"
theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen" theme: "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen"
meta_data: meta_data:
- name: "com.oculus.intent.category.VR"
value: "vr_only"
- name: "com.samsung.android.vr.application.mode" - name: "com.samsung.android.vr.application.mode"
value: "vr_only" value: "vr_only"
- name: "com.oculus.supportedDevices" - name: "com.oculus.supportedDevices"
value: "quest|quest2|quest3" value: "quest|quest2|quest3|questpro"
activities: activities:
- config_changes: "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode" - config_changes: "density|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode|screenLayout"
launch_mode: "singleTask" launch_mode: "singleTask"
orientation: "landscape" orientation: "landscape"
intent_filters: intent_filters:
@@ -31,5 +34,6 @@ android:
categories: categories:
- "com.oculus.intent.category.VR" - "com.oculus.intent.category.VR"
- "android.intent.category.LAUNCHER" - "android.intent.category.LAUNCHER"
- "org.khronos.openxr.intent.category.IMMERSIVE_HMD"
sdk: sdk:
target_sdk_version: 32 target_sdk_version: 32

View File

@@ -3,8 +3,9 @@ use std::{f32::consts::PI, ops::Mul, time::Duration};
use bevy::{ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
ecs::schedule::ScheduleLabel, ecs::schedule::ScheduleLabel,
input::{keyboard::KeyCode, Input}, input::{keyboard::KeyCode, ButtonInput},
log::info, log::info,
math::primitives::{Capsule3d, Cuboid},
prelude::{ prelude::{
bevy_main, default, shape, App, Assets, Color, Commands, Component, Entity, Event, bevy_main, default, shape, App, Assets, Color, Commands, Component, Entity, Event,
EventReader, EventWriter, FixedUpdate, Gizmos, GlobalTransform, IntoSystemConfigs, EventReader, EventWriter, FixedUpdate, Gizmos, GlobalTransform, IntoSystemConfigs,
@@ -12,6 +13,7 @@ use bevy::{
Schedule, SpatialBundle, StandardMaterial, Startup, Transform, Update, Vec3, Vec3Swizzles, Schedule, SpatialBundle, StandardMaterial, Startup, Transform, Update, Vec3, Vec3Swizzles,
With, Without, World, With, Without, World,
}, },
render::mesh::Meshable,
time::{Fixed, Time, Timer, TimerMode}, time::{Fixed, Time, Timer, TimerMode},
transform::TransformSystem, transform::TransformSystem,
}; };
@@ -19,11 +21,11 @@ use bevy_oxr::{
graphics::{extensions::XrExtensions, XrAppInfo, XrPreferdBlendMode}, graphics::{extensions::XrExtensions, XrAppInfo, XrPreferdBlendMode},
input::XrInput, input::XrInput,
resources::{XrFrameState, XrInstance, XrSession}, resources::{XrFrameState, XrInstance, XrSession},
xr_init::{xr_only, XrEnableRequest, XrEnableStatus}, xr_init::{xr_only, XrStatus},
xr_input::{ xr_input::{
actions::XrActionSets, actions::XrActionSets,
debug_gizmos::OpenXrDebugRenderer, debug_gizmos::OpenXrDebugRenderer,
hands::common::{HandInputDebugRenderer, HandResource, HandsResource, OpenXrHandInput}, hands::common::{HandInputDebugRenderer, HandResource, HandsResource},
hands::HandBone, hands::HandBone,
interactions::{ interactions::{
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions, draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
@@ -41,19 +43,18 @@ use bevy_oxr::{
DefaultXrPlugins, DefaultXrPlugins,
}; };
fn input_stuff( // fn input_stuff(
keys: Res<Input<KeyCode>>, // keys: Res<Input<KeyCode>>,
status: Res<XrEnableStatus>, // status: Res<XrEnableStatus>,
mut request: EventWriter<XrEnableRequest>, // mut request: EventWriter<XrEnableRequest>,
) { // ) {
if keys.just_pressed(KeyCode::Space) { // if keys.just_pressed(KeyCode::Space) {
match status.into_inner() { // match status.into_inner() {
XrEnableStatus::Enabled => request.send(XrEnableRequest::TryDisable), // XrEnableStatus::Enabled => request.send(XrEnableRequest::TryDisable),
XrEnableStatus::Disabled => request.send(XrEnableRequest::TryEnable), // XrEnableStatus::Disabled => request.send(XrEnableRequest::TryEnable),
XrEnableStatus::Waiting => (), // }
} // }
} // }
}
mod setup; mod setup;
use crate::setup::setup_scene; use crate::setup::setup_scene;
@@ -66,8 +67,9 @@ 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(); let mut xr_extensions = XrExtensions::default();
xr_extensions.enable_fb_passthrough();
app.add_systems(Update, input_stuff) app
//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)
@@ -120,7 +122,7 @@ pub fn main() {
//test capsule //test capsule
.add_systems(Startup, spawn_capsule) .add_systems(Startup, spawn_capsule)
//physics hands //physics hands
.add_plugins(OpenXrHandInput) // .add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer) .add_plugins(HandInputDebugRenderer)
.add_systems(Startup, spawn_physics_hands) .add_systems(Startup, spawn_physics_hands)
.add_systems( .add_systems(
@@ -226,12 +228,8 @@ fn spawn_capsule(
) { ) {
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {
mesh: meshes.add(Mesh::from(shape::Capsule { mesh: meshes.add(Capsule3d::new(0.033, 0.115).mesh()),
radius: 0.033, material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
depth: 0.115,
..default()
})),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 2.0, 0.0), transform: Transform::from_xyz(0.0, 2.0, 0.0),
..default() ..default()
}, },
@@ -376,7 +374,7 @@ fn update_physics_hands(
&Hand, &Hand,
&mut Velocity, &mut Velocity,
)>, )>,
hand_query: Query<(&Transform, &HandBone, &Hand, Without<PhysicsHandBone>)>, hand_query: Query<(&Transform, &HandBone, &Hand), Without<PhysicsHandBone>>,
time: Res<Time>, time: Res<Time>,
mut gizmos: Gizmos, mut gizmos: Gizmos,
) { ) {
@@ -560,8 +558,6 @@ fn request_cube_spawn(
) { ) {
timer.0.tick(time.delta()); timer.0.tick(time.delta());
if timer.0.finished() { if timer.0.finished() {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers //get controller triggers
@@ -588,8 +584,8 @@ fn cube_spawner(
// cube // cube
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
transform: Transform::from_xyz(0.0, 1.0, 0.0), transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default() ..default()
}, },
@@ -628,8 +624,6 @@ fn prototype_interaction_input(
>, >,
action_sets: Res<XrActionSets>, action_sets: Res<XrActionSets>,
) { ) {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers //get controller triggers
@@ -664,7 +658,7 @@ pub struct GhostTimers {
pub fn handle_ghost_hand_events( pub fn handle_ghost_hand_events(
mut events: EventReader<GhostHandEvent>, mut events: EventReader<GhostHandEvent>,
mut bones: Query<(&Hand, &mut CollisionGroups, With<PhysicsHandBone>)>, mut bones: Query<(&Hand, &mut CollisionGroups), With<PhysicsHandBone>>,
) { ) {
for event in events.read() { for event in events.read() {
// info!( // info!(
@@ -712,20 +706,19 @@ pub struct Grabbable;
pub fn update_grabbables( pub fn update_grabbables(
mut events: EventReader<InteractionEvent>, mut events: EventReader<InteractionEvent>,
mut grabbable_query: Query<( mut grabbable_query: Query<
Entity, (Entity, &mut Transform, Option<&mut RigidBody>),
&mut Transform, (Without<XRDirectInteractor>, With<Grabbable>),
With<Grabbable>, >,
Without<XRDirectInteractor>, mut interactor_query: Query<
Option<&mut RigidBody>, (
)>,
mut interactor_query: Query<(
&GlobalTransform, &GlobalTransform,
&XRInteractorState, &XRInteractorState,
&mut XRSelection, &mut XRSelection,
&Hand, &Hand,
),
Without<Grabbable>, Without<Grabbable>,
)>, >,
mut writer: EventWriter<GhostHandEvent>, mut writer: EventWriter<GhostHandEvent>,
mut timers: ResMut<GhostTimers>, mut timers: ResMut<GhostTimers>,
) { ) {
@@ -741,7 +734,7 @@ pub fn update_grabbables(
match *interactor_transform.2 { match *interactor_transform.2 {
XRSelection::Empty => { XRSelection::Empty => {
match interactor_transform.1 { match interactor_transform.1 {
XRInteractorState::Idle => match grabbable_transform.4 { XRInteractorState::Idle => match grabbable_transform.2 {
Some(mut thing) => { Some(mut thing) => {
*thing = RigidBody::Dynamic; *thing = RigidBody::Dynamic;
*interactor_transform.2 = XRSelection::Empty; *interactor_transform.2 = XRSelection::Empty;
@@ -750,7 +743,7 @@ pub fn update_grabbables(
}, },
XRInteractorState::Selecting => { XRInteractorState::Selecting => {
// info!("its a direct interactor?"); // info!("its a direct interactor?");
match grabbable_transform.4 { match grabbable_transform.2 {
Some(mut thing) => { Some(mut thing) => {
*thing = RigidBody::KinematicPositionBased; *thing = RigidBody::KinematicPositionBased;
*interactor_transform.2 = *interactor_transform.2 =

View File

@@ -1,9 +1,10 @@
use bevy::{ use bevy::{
math::primitives::{Cuboid, Plane3d},
prelude::{ prelude::{
shape, Assets, Camera3dBundle, Color, Commands, Mesh, PbrBundle, PointLight, Assets, Camera3dBundle, Color, Commands, Mesh, PbrBundle, ResMut, StandardMaterial,
PointLightBundle, ResMut, SpatialBundle, StandardMaterial, Transform, Vec3, Transform, Vec3,
}, },
transform::TransformBundle, render::mesh::Meshable,
utils::default, utils::default,
}; };
use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState}; use bevy_oxr::xr_input::interactions::{Touched, XRInteractable, XRInteractableState};
@@ -29,8 +30,8 @@ pub fn setup_scene(
// plane // plane
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()), mesh: meshes.add(Plane3d::new(Vec3::Y).mesh()),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
transform: Transform::from_xyz(0.0, ground_height, 0.0), transform: Transform::from_xyz(0.0, ground_height, 0.0),
..default() ..default()
}, },
@@ -41,8 +42,8 @@ pub fn setup_scene(
// cube // cube
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1))),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
transform: Transform::from_xyz(0.0, 1.0, 0.0), transform: Transform::from_xyz(0.0, 1.0, 0.0),
..default() ..default()
}, },

View File

@@ -1,9 +1,10 @@
use bevy::diagnostic::LogDiagnosticsPlugin; use bevy::diagnostic::LogDiagnosticsPlugin;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::render_asset::RenderAssetUsages;
use bevy::transform::components::Transform; use bevy::transform::components::Transform;
use bevy_oxr::graphics::XrAppInfo; 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;
use bevy_oxr::xr_input::interactions::{ use bevy_oxr::xr_input::interactions::{
InteractionEvent, XRDirectInteractor, XRInteractorState, XRRayInteractor, XRSocketInteractor, InteractionEvent, XRDirectInteractor, XRInteractorState, XRRayInteractor, XRSocketInteractor,
}; };
@@ -31,7 +32,6 @@ fn main() {
.add_systems(Update, (proto_locomotion, pull_to_ground).chain()) .add_systems(Update, (proto_locomotion, pull_to_ground).chain())
.insert_resource(PrototypeLocomotionConfig::default()) .insert_resource(PrototypeLocomotionConfig::default())
.add_systems(Startup, spawn_controllers_example) .add_systems(Startup, spawn_controllers_example)
.add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer) .add_plugins(HandInputDebugRenderer)
.add_event::<InteractionEvent>() .add_event::<InteractionEvent>()
.run(); .run();
@@ -67,6 +67,7 @@ fn uv_debug_texture() -> Image {
TextureDimension::D2, TextureDimension::D2,
&texture_data, &texture_data,
TextureFormat::Rgba8UnormSrgb, TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::RENDER_WORLD,
) )
} }
@@ -81,15 +82,7 @@ fn setup(
let radius = 5.0; let radius = 5.0;
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {
mesh: meshes.add( mesh: meshes.add(Sphere::new(radius)),
shape::UVSphere {
radius,
sectors: 10,
stacks: 10,
}
.try_into()
.unwrap(),
),
material: materials.add(StandardMaterial { material: materials.add(StandardMaterial {
base_color_texture: Some(images.add(uv_debug_texture())), base_color_texture: Some(images.add(uv_debug_texture())),
..default() ..default()
@@ -101,8 +94,8 @@ fn setup(
)); ));
// cube // cube
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
transform: Transform::from_xyz(0.0, 0.5, 0.0), transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default() ..default()
}); });
@@ -172,8 +165,7 @@ fn pull_to_ground(
let (globe_pos, globe) = globe.single(); let (globe_pos, globe) = globe.single();
// Get player position (position of playground + position within playground) // Get player position (position of playground + position within playground)
let v = views.lock().unwrap(); let Some(view) = views.first() else { return };
let Some(view) = v.get(0) else { return };
let mut hmd_translation = view.pose.position.to_vec3(); let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0; hmd_translation.y = 0.0;
let local = root.translation; let local = root.translation;
@@ -188,7 +180,7 @@ fn pull_to_ground(
root.translation += diff * adjustment_rate; root.translation += diff * adjustment_rate;
// Rotate player to be upright on sphere // Rotate player to be upright on sphere
let angle_diff = Quat::from_rotation_arc(root.up(), up); let angle_diff = Quat::from_rotation_arc(*root.up(), up);
let point = root.translation + offset; let point = root.translation + offset;
root.rotate_around(point, Quat::IDENTITY.slerp(angle_diff, adjustment_rate)); root.rotate_around(point, Quat::IDENTITY.slerp(angle_diff, adjustment_rate));
} }

View File

@@ -6,8 +6,9 @@ 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};
use bevy_oxr::xr_init::{xr_only, EndXrSession, StartXrSession, XrSetup};
use bevy_oxr::xr_input::actions::XrActionSets; use bevy_oxr::xr_input::actions::XrActionSets;
use bevy_oxr::xr_input::hands::common::{HandInputDebugRenderer, OpenXrHandInput}; use bevy_oxr::xr_input::hands::common::HandInputDebugRenderer;
use bevy_oxr::xr_input::interactions::{ use bevy_oxr::xr_input::interactions::{
draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions, draw_interaction_gizmos, draw_socket_gizmos, interactions, socket_interactions,
update_interactable_states, InteractionEvent, Touched, XRDirectInteractor, XRInteractable, update_interactable_states, InteractionEvent, Touched, XRDirectInteractor, XRInteractable,
@@ -36,28 +37,53 @@ fn main() {
.add_plugins(LogDiagnosticsPlugin::default()) .add_plugins(LogDiagnosticsPlugin::default())
.add_plugins(FrameTimeDiagnosticsPlugin) .add_plugins(FrameTimeDiagnosticsPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, proto_locomotion) .add_systems(Update, proto_locomotion.run_if(xr_only()))
.insert_resource(PrototypeLocomotionConfig::default()) .insert_resource(PrototypeLocomotionConfig::default())
.add_systems(Startup, spawn_controllers_example) .add_systems(XrSetup, spawn_controllers_example)
.add_plugins(OpenXrHandInput)
.add_plugins(HandInputDebugRenderer) .add_plugins(HandInputDebugRenderer)
.add_systems( .add_systems(
Update, Update,
draw_interaction_gizmos.after(update_interactable_states), 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, draw_socket_gizmos.after(update_interactable_states))
.add_systems(Update, interactions.before(update_interactable_states))
.add_systems( .add_systems(
Update, Update,
socket_interactions.before(update_interactable_states), socket_interactions.before(update_interactable_states),
) )
.add_systems(Update, prototype_interaction_input) .add_systems(Update, prototype_interaction_input.run_if(xr_only()))
.add_systems(Update, update_interactable_states) .add_systems(Update, update_interactable_states)
.add_systems(Update, update_grabbables.after(update_interactable_states)) .add_systems(Update, update_grabbables.after(update_interactable_states))
.add_systems(Update, start_stop_session)
.add_event::<InteractionEvent>() .add_event::<InteractionEvent>()
.run(); .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 /// set up a simple 3D scene
fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
@@ -66,14 +92,14 @@ fn setup(
) { ) {
// plane // plane
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0).into()), mesh: meshes.add(Plane3d::new(Vec3::Y).mesh()),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
..default() ..default()
}); });
// cube // cube
commands.spawn(PbrBundle { commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 0.1 })), mesh: meshes.add(Cuboid::from_size(Vec3::splat(0.1)).mesh()),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), material: materials.add(StandardMaterial::from(Color::rgb(0.8, 0.7, 0.6))),
transform: Transform::from_xyz(0.0, 0.5, 0.0), transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default() ..default()
}); });
@@ -144,13 +170,14 @@ fn spawn_controllers_example(mut commands: Commands) {
)); ));
} }
#[allow(clippy::type_complexity)]
fn prototype_interaction_input( fn prototype_interaction_input(
oculus_controller: Res<OculusController>, oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>, frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>, xr_input: Res<XrInput>,
session: Res<XrSession>, session: Res<XrSession>,
mut right_interactor_query: Query< mut right_interactor_query: Query<
(&mut XRInteractorState), &mut XRInteractorState,
( (
With<XRDirectInteractor>, With<XRDirectInteractor>,
With<OpenXRRightController>, With<OpenXRRightController>,
@@ -158,7 +185,7 @@ fn prototype_interaction_input(
), ),
>, >,
mut left_interactor_query: Query< mut left_interactor_query: Query<
(&mut XRInteractorState), &mut XRInteractorState,
( (
With<XRRayInteractor>, With<XRRayInteractor>,
With<OpenXRLeftController>, With<OpenXRLeftController>,
@@ -167,8 +194,6 @@ fn prototype_interaction_input(
>, >,
action_sets: Res<XrActionSets>, action_sets: Res<XrActionSets>,
) { ) {
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get controller triggers //get controller triggers
@@ -194,8 +219,8 @@ pub struct Grabbable;
pub fn update_grabbables( pub fn update_grabbables(
mut events: EventReader<InteractionEvent>, mut events: EventReader<InteractionEvent>,
mut grabbable_query: Query<(&mut Transform, With<Grabbable>, Without<XRDirectInteractor>)>, mut grabbable_query: Query<&mut Transform, (With<Grabbable>, Without<XRDirectInteractor>)>,
interactor_query: Query<(&GlobalTransform, &XRInteractorState, Without<Grabbable>)>, interactor_query: Query<(&GlobalTransform, &XRInteractorState), Without<Grabbable>>,
) { ) {
//so basically the idea is to try all the events? //so basically the idea is to try all the events?
for event in events.read() { for event in events.read() {
@@ -210,7 +235,7 @@ pub fn update_grabbables(
XRInteractorState::Idle => (), XRInteractorState::Idle => (),
XRInteractorState::Selecting => { XRInteractorState::Selecting => {
// info!("its a direct interactor?"); // info!("its a direct interactor?");
*grabbable_transform.0 = interactor_transform.0.compute_transform(); *grabbable_transform = interactor_transform.0.compute_transform();
} }
} }
} }

View File

@@ -1,16 +1,21 @@
pub mod extensions; pub mod extensions;
mod vulkan; mod vulkan;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use bevy::ecs::query::With;
use bevy::window::RawHandleWrapper; use bevy::ecs::system::{Query, SystemState};
use bevy::ecs::world::World;
use bevy::render::renderer::{
RenderAdapter, RenderAdapterInfo, RenderDevice, RenderInstance, RenderQueue,
};
use bevy::window::{PrimaryWindow, RawHandleWrapper};
use wgpu::Instance; use wgpu::Instance;
use crate::input::XrInput; use crate::input::XrInput;
use crate::passthrough::{Passthrough, PassthroughLayer};
use crate::resources::{ use crate::resources::{
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrPassthrough, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrPassthroughLayer, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews, XrSession, XrSessionRunning, XrSwapchain, XrViews,
}; };
use crate::OXrSessionSetupInfo;
use openxr as xr; use openxr as xr;
@@ -40,20 +45,15 @@ impl Default for XrAppInfo {
} }
} }
pub fn initialize_xr_graphics( pub fn start_xr_session(
window: Option<RawHandleWrapper>, window: Option<RawHandleWrapper>,
reqeusted_extensions: XrExtensions, session_setup_data: &OXrSessionSetupInfo,
prefered_blend_mode: XrPreferdBlendMode, xr_instance: &XrInstance,
app_info: XrAppInfo, render_device: &RenderDevice,
) -> anyhow::Result<( render_adapter: &RenderAdapter,
RenderDevice, wgpu_instance: &Instance,
RenderQueue, ) -> eyre::Result<(
RenderAdapterInfo,
RenderAdapter,
Instance,
XrInstance,
XrSession, XrSession,
XrEnvironmentBlendMode,
XrResolution, XrResolution,
XrFormat, XrFormat,
XrSessionRunning, XrSessionRunning,
@@ -63,13 +63,113 @@ pub fn initialize_xr_graphics(
XrViews, XrViews,
XrFrameState, XrFrameState,
)> { )> {
vulkan::initialize_xr_graphics(window, reqeusted_extensions, prefered_blend_mode, app_info) vulkan::start_xr_session(
window,
session_setup_data,
xr_instance,
render_device,
render_adapter,
wgpu_instance,
)
}
pub fn initialize_xr_instance(
window: Option<RawHandleWrapper>,
reqeusted_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo,
) -> eyre::Result<(
XrInstance,
OXrSessionSetupInfo,
XrEnvironmentBlendMode,
RenderDevice,
RenderQueue,
RenderAdapterInfo,
RenderAdapter,
Instance,
)> {
vulkan::initialize_xr_instance(window, reqeusted_extensions, prefered_blend_mode, app_info)
} }
pub fn xr_entry() -> anyhow::Result<xr::Entry> { pub fn try_full_init(
world: &mut World,
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(
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(wgpu_instance.into()),
))
}
pub fn xr_entry() -> eyre::Result<xr::Entry> {
#[cfg(windows)] #[cfg(windows)]
let entry = Ok(xr::Entry::linked()); let entry = Ok(xr::Entry::linked());
#[cfg(not(windows))] #[cfg(not(windows))]
let entry = unsafe { xr::Entry::load().map_err(|e| anyhow::anyhow!(e)) }; let entry = unsafe { xr::Entry::load().map_err(|e| eyre::eyre!(e)) };
entry entry
} }

View File

@@ -2,54 +2,45 @@ use std::ffi::{c_void, CString};
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use anyhow::Context; // use anyhow::Context;
use ash::vk::{self, Handle}; use ash::vk::{self, Handle};
use bevy::math::uvec2; use bevy::math::uvec2;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use bevy::render::renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use bevy::window::RawHandleWrapper; use bevy::window::RawHandleWrapper;
use eyre::{Context, ContextCompat};
use openxr as xr; use openxr as xr;
use wgpu::Instance; use wgpu::Instance;
use wgpu_hal::{api::Vulkan as V, Api};
use xr::EnvironmentBlendMode; use xr::EnvironmentBlendMode;
use crate::graphics::extensions::XrExtensions; use crate::graphics::extensions::XrExtensions;
use crate::input::XrInput; use crate::input::XrInput;
use crate::passthrough::{Passthrough, PassthroughLayer};
use crate::resources::{ use crate::resources::{
Swapchain, SwapchainInner, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, OXrSessionSetupInfo, Swapchain, SwapchainInner, VulkanOXrSessionSetupInfo,
XrInstance, XrPassthrough, XrPassthroughLayer, XrResolution, XrSession, XrSessionRunning, XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrResolution,
XrSwapchain, XrViews, XrSession, XrSessionRunning, XrSwapchain, XrViews,
}; };
use crate::VIEW_TYPE; use crate::VIEW_TYPE;
use super::{XrAppInfo, XrPreferdBlendMode}; use super::{XrAppInfo, XrPreferdBlendMode};
pub fn initialize_xr_graphics( pub fn initialize_xr_instance(
window: Option<RawHandleWrapper>, window: Option<RawHandleWrapper>,
reqeusted_extensions: XrExtensions, reqeusted_extensions: XrExtensions,
prefered_blend_mode: XrPreferdBlendMode, prefered_blend_mode: XrPreferdBlendMode,
app_info: XrAppInfo, app_info: XrAppInfo,
) -> anyhow::Result<( ) -> eyre::Result<(
XrInstance,
OXrSessionSetupInfo,
XrEnvironmentBlendMode,
RenderDevice, RenderDevice,
RenderQueue, RenderQueue,
RenderAdapterInfo, RenderAdapterInfo,
RenderAdapter, RenderAdapter,
Instance, Instance,
XrInstance,
XrSession,
XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
)> { )> {
use wgpu_hal::{api::Vulkan as V, Api};
let xr_entry = super::xr_entry()?; let xr_entry = super::xr_entry()?;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -139,9 +130,8 @@ pub fn initialize_xr_graphics(
} }
let vk_entry = unsafe { ash::Entry::load() }?; let vk_entry = unsafe { ash::Entry::load() }?;
let flags = wgpu_hal::InstanceFlags::empty(); let flags = wgpu::InstanceFlags::from_build_config();
let extensions = let extensions = <V as Api>::Instance::desired_extensions(&vk_entry, vk_target_version, flags)?;
<V as Api>::Instance::required_extensions(&vk_entry, vk_target_version, flags)?;
let device_extensions = vec![ let device_extensions = vec![
ash::extensions::khr::Swapchain::name(), ash::extensions::khr::Swapchain::name(),
ash::extensions::khr::DrawIndirectCount::name(), ash::extensions::khr::DrawIndirectCount::name(),
@@ -291,8 +281,8 @@ pub fn initialize_xr_graphics(
wgpu_open_device, wgpu_open_device,
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu_features, required_features: wgpu_features,
limits: wgpu::Limits { required_limits: wgpu::Limits {
max_bind_groups: 8, max_bind_groups: 8,
max_storage_buffer_binding_size: wgpu_adapter max_storage_buffer_binding_size: wgpu_adapter
.limits() .limits()
@@ -304,32 +294,75 @@ pub fn initialize_xr_graphics(
None, 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(wgpu_queue.into()),
RenderAdapterInfo(wgpu_adapter.get_info()),
RenderAdapter(wgpu_adapter.into()),
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 { let (session, frame_wait, frame_stream) = unsafe {
xr_instance.create_session::<xr::Vulkan>( xr_instance.create_session::<xr::Vulkan>(
xr_system_id, xr_instance.system(xr::FormFactor::HEAD_MOUNTED_DISPLAY)?,
&xr::vulkan::SessionCreateInfo { &xr::vulkan::SessionCreateInfo {
instance: vk_instance_ptr, instance: setup_info.vk_instance_ptr,
physical_device: vk_physical_device_ptr, physical_device: setup_info.physical_device_ptr,
device: vk_device_ptr, device: setup_info.device_ptr,
queue_family_index, queue_family_index: setup_info.queue_family_index,
queue_index: 0, queue_index: 0,
}, },
) )
}?; }?;
let views = xr_instance.enumerate_view_configuration_views(xr_system_id, VIEW_TYPE)?; let views =
xr_instance.enumerate_view_configuration_views(setup_info.xr_system_id, VIEW_TYPE)?;
let surface = window.map(|wrapper| unsafe { let surface = window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread. // SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle(); let handle = wrapper.get_handle();
wgpu_instance wgpu_instance
.create_surface(&handle) .create_surface(handle)
.expect("Failed to create wgpu surface") .expect("Failed to create wgpu surface")
}); });
let swapchain_format = surface let swapchain_format = surface
.as_ref() .as_ref()
.map(|surface| surface.get_capabilities(&wgpu_adapter).formats[0]) .map(|surface| surface.get_capabilities(wgpu_adapter).formats[0])
.unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb); .unwrap_or(wgpu::TextureFormat::Rgba8UnormSrgb);
// TODO: Log swapchain format // TODO: Log swapchain format
@@ -356,11 +389,13 @@ pub fn initialize_xr_graphics(
mip_count: 1, mip_count: 1,
}) })
.unwrap(); .unwrap();
let images = handle.enumerate_images().unwrap(); let images = handle.enumerate_images().unwrap();
let buffers = images let buffers = images
.into_iter() .into_iter()
.map(|color_image| { .map(|color_image| {
info!("image map swapchain");
let color_image = vk::Image::from_raw(color_image); let color_image = vk::Image::from_raw(color_image);
let wgpu_hal_texture = unsafe { let wgpu_hal_texture = unsafe {
<V as Api>::Device::texture_from_raw( <V as Api>::Device::texture_from_raw(
@@ -409,18 +444,12 @@ pub fn initialize_xr_graphics(
.collect(); .collect();
Ok(( Ok((
wgpu_device.into(), XrSession::Vulkan(session.clone()),
RenderQueue(Arc::new(wgpu_queue)),
RenderAdapterInfo(wgpu_adapter.get_info()),
RenderAdapter(Arc::new(wgpu_adapter)),
wgpu_instance,
xr_instance.clone().into(),
session.clone().into_any_graphics().into(),
blend_mode.into(),
resolution.into(), resolution.into(),
swapchain_format.into(), swapchain_format.into(),
// TODO: this shouldn't be in here
AtomicBool::new(false).into(), AtomicBool::new(false).into(),
Mutex::new(frame_wait).into(), frame_wait.into(),
Swapchain::Vulkan(SwapchainInner { Swapchain::Vulkan(SwapchainInner {
stream: Mutex::new(frame_stream), stream: Mutex::new(frame_stream),
handle: Mutex::new(handle), handle: Mutex::new(handle),
@@ -428,13 +457,14 @@ pub fn initialize_xr_graphics(
image_index: Mutex::new(0), image_index: Mutex::new(0),
}) })
.into(), .into(),
XrInput::new(xr_instance, session.into_any_graphics())?, XrInput::new(xr_instance, &session.into_any_graphics())?,
Mutex::default().into(), Vec::default().into(),
Mutex::new(xr::FrameState { // 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_time: xr::Time::from_nanos(1),
predicted_display_period: xr::Duration::from_nanos(1), predicted_display_period: xr::Duration::from_nanos(1),
should_render: true, should_render: true,
}) }
.into(), .into(),
)) ))
} }

View File

@@ -1,10 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use bevy::prelude::*; use bevy::{prelude::*, render::extract_resource::ExtractResource};
use openxr as xr; use openxr as xr;
use xr::{FrameState, FrameWaiter, ViewConfigurationType}; use xr::{FrameState, FrameWaiter, ViewConfigurationType};
#[derive(Clone, Resource)] #[derive(Clone, Resource, ExtractResource)]
pub struct XrInput { pub struct XrInput {
//pub action_set: xr::ActionSet, //pub action_set: xr::ActionSet,
//pub hand_pose: xr::Action<xr::Posef>, //pub hand_pose: xr::Action<xr::Posef>,
@@ -16,8 +16,8 @@ pub struct XrInput {
impl XrInput { impl XrInput {
pub fn new( pub fn new(
instance: xr::Instance, instance: &xr::Instance,
session: xr::Session<xr::AnyGraphics>, session: &xr::Session<xr::AnyGraphics>,
// frame_state: &FrameState, // frame_state: &FrameState,
) -> xr::Result<Self> { ) -> 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();

View File

@@ -1,17 +1,19 @@
pub mod graphics; pub mod graphics;
pub mod input; pub mod input;
pub mod passthrough; pub mod passthrough;
pub mod prelude;
pub mod resource_macros; pub mod resource_macros;
pub mod resources; pub mod resources;
pub mod xr_init; pub mod xr_init;
pub mod xr_input; pub mod xr_input;
use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicBool;
use crate::xr_init::RenderRestartPlugin; use crate::xr_init::{StartXrSession, XrInitPlugin};
use crate::xr_input::hands::hand_tracking::DisableHandTracking;
use crate::xr_input::oculus_touch::ActionSets; use crate::xr_input::oculus_touch::ActionSets;
use crate::xr_input::trackers::verify_quat;
use bevy::app::{AppExit, PluginGroupBuilder}; use bevy::app::{AppExit, PluginGroupBuilder};
use bevy::core::TaskPoolThreadAssignmentPolicy;
use bevy::ecs::system::SystemState; use bevy::ecs::system::SystemState;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews}; use bevy::render::camera::{ManualTextureView, ManualTextureViewHandle, ManualTextureViews};
@@ -24,14 +26,19 @@ use graphics::extensions::XrExtensions;
use graphics::{XrAppInfo, XrPreferdBlendMode}; use graphics::{XrAppInfo, XrPreferdBlendMode};
use input::XrInput; use input::XrInput;
use openxr as xr; use openxr as xr;
use passthrough::{start_passthrough, supports_passthrough, Passthrough, PassthroughLayer}; use passthrough::{PassthroughPlugin, XrPassthroughLayer, XrPassthroughState};
use resources::*; use resources::*;
use xr::FormFactor; use xr_init::{
use xr_init::{xr_only, XrEnableStatus, XrRenderData}; xr_after_wait_only, xr_only, xr_render_only, CleanupRenderWorld, CleanupXrData,
use xr_input::controllers::XrControllerType; ExitAppOnSessionExit, SetupXrData, StartSessionOnStartup, XrCleanup, XrEarlyInitPlugin,
XrHasWaited, XrPostCleanup, XrShouldRender, XrStatus,
};
use xr_input::actions::XrActionsPlugin;
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::HandTrackingPlugin;
use xr_input::OpenXrInput; use xr_input::hands::HandPlugin;
use xr_input::xr_camera::XrCameraPlugin;
use xr_input::XrInputPlugin;
const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO; const VIEW_TYPE: xr::ViewConfigurationType = xr::ViewConfigurationType::PRIMARY_STEREO;
@@ -46,225 +53,170 @@ pub struct OpenXrPlugin {
app_info: XrAppInfo, app_info: XrAppInfo,
} }
#[derive(Resource)]
pub struct FutureXrResources(
pub Arc<
Mutex<
Option<(
XrInstance,
XrSession,
XrEnvironmentBlendMode,
XrResolution,
XrFormat,
XrSessionRunning,
XrFrameWaiter,
XrSwapchain,
XrInput,
XrViews,
XrFrameState,
bool,
XrPassthrough,
XrPassthroughLayer,
)>,
>,
>,
);
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) {
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> = app.insert_resource(XrSessionRunning::new(AtomicBool::new(false)));
SystemState::new(&mut app.world); app.insert_resource(ExitAppOnSessionExit::default());
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( match graphics::initialize_xr_instance(
primary_window.clone(), SystemState::<Query<&RawHandleWrapper, With<PrimaryWindow>>>::new(&mut app.world)
.get(&app.world)
.get_single()
.ok()
.cloned(),
self.reqeusted_extensions.clone(), self.reqeusted_extensions.clone(),
self.prefered_blend_mode, self.prefered_blend_mode,
self.app_info.clone(), self.app_info.clone(),
) { ) {
Ok(( Ok((
xr_instance,
oxr_session_setup_info,
blend_mode,
device, device,
queue, queue,
adapter_info, adapter_info,
render_adapter, render_adapter,
instance, instance,
xr_instance,
session,
blend_mode,
resolution,
format,
session_running,
frame_waiter,
swapchain,
input,
views,
frame_state,
)) => { )) => {
// std::thread::sleep(Duration::from_secs(5));
debug!("Configured wgpu adapter Limits: {:#?}", device.limits()); debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
debug!("Configured wgpu adapter Features: {:#?}", device.features()); debug!("Configured wgpu adapter Features: {:#?}", device.features());
warn!("Starting with OpenXR Instance");
app.insert_resource(xr_instance.clone()); app.insert_resource(xr_instance.clone());
app.insert_resource(session.clone()); app.insert_resource(blend_mode);
app.insert_resource(blend_mode.clone());
app.insert_resource(resolution.clone());
app.insert_resource(format.clone());
app.insert_resource(session_running.clone());
app.insert_resource(frame_waiter.clone());
app.insert_resource(swapchain.clone());
app.insert_resource(input.clone());
app.insert_resource(views.clone());
app.insert_resource(frame_state.clone());
// Check if the fb_passthrough extension is available
let fb_passthrough_available = xr_instance.exts().fb_passthrough.is_some();
bevy::log::info!(
"From OpenXrPlugin: fb_passthrough_available: {}",
fb_passthrough_available
);
// Get the system for the head-mounted display
let hmd_system = xr_instance
.system(FormFactor::HEAD_MOUNTED_DISPLAY)
.unwrap();
bevy::log::info!("From OpenXrPlugin: hmd_system: {:?}", hmd_system);
// Check if the system supports passthrough
let passthrough_supported =
supports_passthrough(&xr_instance, hmd_system).is_ok_and(|v| v);
bevy::log::info!(
"From OpenXrPlugin: passthrough_supported: {}",
passthrough_supported
);
// The passthrough variable will be true only if both fb_passthrough is available and the system supports passthrough
let passthrough = fb_passthrough_available && passthrough_supported;
bevy::log::info!("From OpenXrPlugin: passthrough: {}", passthrough);
let mut p: Option<XrPassthrough> = None;
let mut pl: Option<XrPassthroughLayer> = None;
if passthrough {
if let Ok((p, pl)) = start_passthrough(&xr_instance, &session) {
let xr_data = XrRenderData {
xr_instance,
xr_session: session,
xr_blend_mode: blend_mode,
xr_resolution: resolution,
xr_format: format,
xr_session_running: session_running,
xr_frame_waiter: frame_waiter,
xr_swapchain: swapchain,
xr_input: input,
xr_views: views,
xr_frame_state: frame_state,
xr_passthrough_active: true,
xr_passthrough: XrPassthrough::new(Mutex::new(p)),
xr_passthrough_layer: XrPassthroughLayer::new(Mutex::new(pl)),
};
bevy::log::info!("Passthrough is supported!");
app.insert_resource(xr_data);
app.insert_resource(ClearColor(Color::rgba(0.0, 0.0, 0.0, 0.0)));
}
if !app.world.contains_resource::<ClearColor>() {
info!("ClearColor!");
}
}
app.insert_resource(ActionSets(vec![])); 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(instance.into());
app.insert_resource(render_instance.clone());
app.add_plugins(RenderPlugin { app.add_plugins(RenderPlugin {
render_creation: RenderCreation::Manual( render_creation: RenderCreation::Manual(
device, device,
queue, queue,
adapter_info, adapter_info,
render_adapter, render_adapter,
RenderInstance(Arc::new(instance)), render_instance,
), ),
// Expose this? if yes we also have to set this in the non xr case
synchronous_pipeline_compilation: true,
}); });
app.insert_resource(XrEnableStatus::Enabled); app.insert_resource(XrStatus::Disabled);
// app.world.send_event(StartXrSession);
} }
Err(err) => { Err(err) => {
warn!("OpenXR Failed to initialize: {}", err); warn!("OpenXR Instance Failed to initialize: {}", err);
app.add_plugins(RenderPlugin::default()); app.add_plugins(RenderPlugin::default());
app.insert_resource(XrEnableStatus::Disabled); app.insert_resource(XrStatus::NoInstance);
} }
} }
// app.add_systems(PreUpdate, mr_test);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
app.add_plugins(RenderPlugin::default()); app.add_plugins(RenderPlugin::default());
app.insert_resource(XrEnableStatus::Disabled); app.insert_resource(XrStatus::Disabled);
} }
} app.add_systems(XrPostCleanup, clean_resources);
app.add_systems(XrPostCleanup, || info!("Main World Post Cleanup!"));
fn ready(&self, app: &App) -> bool { app.add_systems(
app.world PreUpdate,
.get_resource::<XrEnableStatus>() xr_poll_events.run_if(|status: Res<XrStatus>| *status != XrStatus::NoInstance),
.map(|frr| *frr != XrEnableStatus::Waiting) );
.unwrap_or(true) app.add_systems(
} PreUpdate,
(
fn finish(&self, app: &mut App) { xr_reset_per_frame_resources,
// TODO: Split this up into the indevidual resources xr_wait_frame.run_if(xr_only()),
if let Some(data) = app.world.get_resource::<XrRenderData>().cloned() { locate_views.run_if(xr_only()),
let hands = data.xr_instance.exts().ext_hand_tracking.is_some() apply_deferred,
&& data
.xr_instance
.supports_hand_tracking(
data.xr_instance
.system(FormFactor::HEAD_MOUNTED_DISPLAY)
.unwrap(),
) )
.is_ok_and(|v| v); .chain()
if hands { .after(xr_poll_events),
app.insert_resource(HandTrackingData::new(&data.xr_session).unwrap()); );
} else {
app.insert_resource(DisableHandTracking::Both);
}
let (left, right) = data.xr_swapchain.get_render_views();
let left = ManualTextureView {
texture_view: left.into(),
size: *data.xr_resolution,
format: *data.xr_format,
};
let right = ManualTextureView {
texture_view: right.into(),
size: *data.xr_resolution,
format: *data.xr_format,
};
app.add_systems(PreUpdate, xr_begin_frame.run_if(xr_only()));
let mut manual_texture_views = app.world.resource_mut::<ManualTextureViews>();
manual_texture_views.insert(LEFT_XR_TEXTURE_HANDLE, left);
manual_texture_views.insert(RIGHT_XR_TEXTURE_HANDLE, right);
drop(manual_texture_views);
let render_app = app.sub_app_mut(RenderApp); let render_app = app.sub_app_mut(RenderApp);
render_app.insert_resource(data.xr_instance.clone());
render_app.insert_resource(data.xr_session.clone());
render_app.insert_resource(data.xr_blend_mode.clone());
render_app.insert_resource(data.xr_resolution.clone());
render_app.insert_resource(data.xr_format.clone());
render_app.insert_resource(data.xr_session_running.clone());
render_app.insert_resource(data.xr_frame_waiter.clone());
render_app.insert_resource(data.xr_swapchain.clone());
render_app.insert_resource(data.xr_input.clone());
render_app.insert_resource(data.xr_views.clone());
render_app.insert_resource(data.xr_frame_state.clone());
render_app.insert_resource(data.xr_passthrough.clone());
render_app.insert_resource(data.xr_passthrough_layer.clone());
render_app.insert_resource(XrEnableStatus::Enabled);
render_app.add_systems( render_app.add_systems(
Render, Render,
( xr_pre_frame
post_frame
.run_if(xr_only()) .run_if(xr_only())
.run_if(xr_after_wait_only())
.run_if(xr_render_only())
.before(render_system) .before(render_system)
.after(RenderSet::ExtractCommands), .after(RenderSet::ExtractCommands),
end_frame.run_if(xr_only()).after(render_system),
),
); );
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),
);
}
}
fn clean_resources_render(mut 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(mut 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 {
Swapchain::Vulkan(swap) => {
swap.stream
.lock()
.unwrap()
.end(
xr_frame_state.predicted_display_time,
**environment_blend_mode,
&[],
)
.unwrap();
} }
} }
} }
@@ -280,17 +232,35 @@ impl PluginGroup for DefaultXrPlugins {
fn build(self) -> PluginGroupBuilder { fn build(self) -> PluginGroupBuilder {
DefaultPlugins DefaultPlugins
.build() .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::<PipelinedRenderingPlugin>()
.disable::<RenderPlugin>() .disable::<RenderPlugin>()
.disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(OpenXrPlugin { .add_before::<RenderPlugin, _>(OpenXrPlugin {
prefered_blend_mode: self.prefered_blend_mode, prefered_blend_mode: self.prefered_blend_mode,
reqeusted_extensions: self.reqeusted_extensions, reqeusted_extensions: self.reqeusted_extensions,
app_info: self.app_info.clone(), app_info: self.app_info.clone(),
}) })
.add_after::<OpenXrPlugin, _>(OpenXrInput::new(XrControllerType::OculusTouch)) .add_after::<OpenXrPlugin, _>(XrInitPlugin)
.add_before::<OpenXrPlugin, _>(RenderRestartPlugin) .add(XrInputPlugin)
.add(HandEmulationPlugin) .add(XrActionsPlugin)
.add(XrCameraPlugin)
.add_before::<OpenXrPlugin, _>(XrEarlyInitPlugin)
.add(HandPlugin)
.add(HandTrackingPlugin) .add(HandTrackingPlugin)
.add(HandEmulationPlugin)
.add(PassthroughPlugin)
.add(XrResourcePlugin)
.add(StartSessionOnStartup)
.set(WindowPlugin { .set(WindowPlugin {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
primary_window: Some(Window { primary_window: Some(Window {
@@ -310,18 +280,25 @@ impl PluginGroup for DefaultXrPlugins {
} }
} }
pub fn xr_begin_frame( fn xr_reset_per_frame_resources(
instance: Res<XrInstance>, mut should: ResMut<XrShouldRender>,
session: Res<XrSession>, mut waited: ResMut<XrHasWaited>,
session_running: Res<XrSessionRunning>,
frame_state: Res<XrFrameState>,
frame_waiter: Res<XrFrameWaiter>,
swapchain: Res<XrSwapchain>,
views: Res<XrViews>,
input: Res<XrInput>,
mut app_exit: EventWriter<AppExit>,
) { ) {
{ **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"); let _span = info_span!("xr_poll_events");
while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() { while let Some(event) = instance.poll_event(&mut Default::default()).unwrap() {
use xr::Event::*; use xr::Event::*;
@@ -332,22 +309,38 @@ pub fn xr_begin_frame(
info!("entered XR state {:?}", e.state()); info!("entered XR state {:?}", e.state());
match e.state() { match e.state() {
xr::SessionState::READY => { xr::SessionState::READY => {
info!("Calling Session begin :3");
session.begin(VIEW_TYPE).unwrap(); session.begin(VIEW_TYPE).unwrap();
setup_xr.send_default();
session_running.store(true, std::sync::atomic::Ordering::Relaxed); session_running.store(true, std::sync::atomic::Ordering::Relaxed);
} }
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); cleanup_xr.send_default();
} }
xr::SessionState::EXITING | xr::SessionState::LOSS_PENDING => { xr::SessionState::EXITING => {
app_exit.send(AppExit); if *exit_type == ExitAppOnSessionExit::Always
return; || *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(_) => return, InstanceLossPending(_) => {
app_exit.send_default();
}
EventsLost(e) => { EventsLost(e) => {
warn!("lost {} XR events", e.lost_event_count()); warn!("lost {} XR events", e.lost_event_count());
} }
@@ -355,34 +348,43 @@ pub fn xr_begin_frame(
} }
} }
} }
}
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(); let _span = info_span!("xr_wait_frame").entered();
*frame_state.lock().unwrap() = match frame_waiter.lock().unwrap().wait() {
Ok(a) => a, *world.get_resource_mut::<XrFrameState>().unwrap() = match frame_waiter.wait() {
Ok(a) => a.into(),
Err(e) => { Err(e) => {
warn!("error: {}", e); warn!("error: {}", e);
return; return;
} }
}; };
let should_render = world.get_resource::<XrFrameState>().unwrap().should_render;
let mut frame_state = world.resource_mut::<XrFrameState>();
frame_state.predicted_display_time = xr::Time::from_nanos(
frame_state.predicted_display_time.as_nanos()
+ frame_state.predicted_display_period.as_nanos(),
);
**world.get_resource_mut::<XrShouldRender>().unwrap() = should_render;
**world.get_resource_mut::<XrHasWaited>().unwrap() = true;
} }
{ world
let _span = info_span!("xr_begin_frame").entered(); .get_resource::<XrSwapchain>()
swapchain.begin().unwrap()
}
{
let _span = info_span!("xr_locate_views").entered();
*views.lock().unwrap() = session
.locate_views(
VIEW_TYPE,
frame_state.lock().unwrap().predicted_display_time,
&input.stage,
)
.unwrap() .unwrap()
.1; .begin()
} .unwrap();
} }
pub fn post_frame( pub fn xr_pre_frame(
resolution: Res<XrResolution>, resolution: Res<XrResolution>,
format: Res<XrFormat>, format: Res<XrFormat>,
swapchain: Res<XrSwapchain>, swapchain: Res<XrSwapchain>,
@@ -414,7 +416,8 @@ pub fn post_frame(
} }
} }
pub fn end_frame( #[allow(clippy::too_many_arguments)]
pub fn xr_end_frame(
xr_frame_state: Res<XrFrameState>, xr_frame_state: Res<XrFrameState>,
views: Res<XrViews>, views: Res<XrViews>,
input: Res<XrInput>, input: Res<XrInput>,
@@ -422,6 +425,7 @@ pub fn end_frame(
resolution: Res<XrResolution>, resolution: Res<XrResolution>,
environment_blend_mode: Res<XrEnvironmentBlendMode>, environment_blend_mode: Res<XrEnvironmentBlendMode>,
passthrough_layer: Option<Res<XrPassthroughLayer>>, passthrough_layer: Option<Res<XrPassthroughLayer>>,
passthrough_state: Option<Res<XrPassthroughState>>,
) { ) {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
{ {
@@ -436,18 +440,17 @@ pub fn end_frame(
} }
{ {
let _span = info_span!("xr_end_frame").entered(); let _span = info_span!("xr_end_frame").entered();
// bevy::log::info!( let pass_layer = match passthrough_state.as_deref() {
// "passthrough_layer.is_some(): {:?}", Some(XrPassthroughState::Running) => passthrough_layer.as_deref(),
// passthrough_layer.is_some() _ => None,
// ); };
let result = swapchain.end( let result = swapchain.end(
xr_frame_state.lock().unwrap().predicted_display_time, xr_frame_state.predicted_display_time,
&views.lock().unwrap(), &views,
&input.stage, &input.stage,
**resolution, **resolution,
**environment_blend_mode, **environment_blend_mode,
passthrough_layer.map(|p| PassthroughLayer(*p.lock().unwrap())), pass_layer,
); );
match result { match result {
Ok(_) => {} Ok(_) => {}
@@ -457,22 +460,37 @@ pub fn end_frame(
} }
pub fn locate_views( pub fn locate_views(
views: Res<XrViews>, mut views: ResMut<XrViews>,
input: Res<XrInput>, input: Res<XrInput>,
session: Res<XrSession>, session: Res<XrSession>,
xr_frame_state: Res<XrFrameState>, xr_frame_state: Res<XrFrameState>,
) { ) {
let _span = info_span!("xr_locate_views").entered(); let _span = info_span!("xr_locate_views").entered();
*views.lock().unwrap() = match session.locate_views( **views = match session.locate_views(
VIEW_TYPE, VIEW_TYPE,
xr_frame_state.lock().unwrap().predicted_display_time, xr_frame_state.predicted_display_time,
&input.stage, &input.stage,
) { ) {
Ok(this) => this, 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) => { Err(err) => {
warn!("error: {}", err); warn!("error: {}", err);
return; return;
} }
} }
.1;
} }

View File

@@ -1,20 +1,135 @@
use bevy::prelude::*; use bevy::render::extract_resource::ExtractResource;
use bevy::{prelude::*, render::extract_resource::ExtractResourcePlugin};
use std::{marker::PhantomData, mem, ptr}; use std::{marker::PhantomData, mem, ptr};
use crate::xr_init::XrRenderData; use crate::resources::XrSession;
use crate::{
resources::XrInstance,
xr_arc_resource_wrapper,
xr_init::{XrCleanup, XrSetup},
};
use openxr as xr; use openxr as xr;
use xr::{ use xr::{
sys::{ sys::{Space, SystemPassthroughProperties2FB},
PassthroughCreateInfoFB, PassthroughFB, PassthroughLayerFB, Space, CompositionLayerBase, CompositionLayerFlags, FormFactor, Graphics,
SystemPassthroughProperties2FB, PassthroughCapabilityFlagsFB,
},
CompositionLayerBase, CompositionLayerFlags, Graphics, PassthroughCapabilityFlagsFB,
}; };
use crate::resources::XrInstance; #[derive(
use crate::resources::XrSession; Clone, Copy, Default, Debug, Resource, PartialEq, PartialOrd, Ord, Eq, Reflect, ExtractResource,
pub struct PassthroughLayer(pub xr::sys::PassthroughLayerFB); )]
pub struct Passthrough(pub xr::sys::PassthroughFB); 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.set_a(0.0);
clear_color.set_r(0.0);
clear_color.set_g(0.0);
clear_color.set_b(0.0);
*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_a(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> { fn cvt(x: xr::sys::Result) -> xr::Result<xr::sys::Result> {
if x.into_raw() >= 0 { if x.into_raw() >= 0 {
Ok(x) Ok(x)
@@ -38,14 +153,14 @@ impl<'a, G: Graphics> std::ops::Deref for CompositionLayerPassthrough<'a, G> {
} }
impl<'a, G: xr::Graphics> CompositionLayerPassthrough<'a, G> { impl<'a, G: xr::Graphics> CompositionLayerPassthrough<'a, G> {
pub(crate) fn from_xr_passthrough_layer(layer: &PassthroughLayer) -> Self { pub(crate) fn from_xr_passthrough_layer(layer: &XrPassthroughLayer) -> Self {
Self { Self {
inner: xr::sys::CompositionLayerPassthroughFB { inner: xr::sys::CompositionLayerPassthroughFB {
ty: xr::sys::CompositionLayerPassthroughFB::TYPE, ty: xr::sys::CompositionLayerPassthroughFB::TYPE,
next: ptr::null(), next: ptr::null(),
flags: CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA, flags: CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA,
space: Space::NULL, space: Space::NULL,
layer_handle: layer.0, layer_handle: *layer.inner(),
}, },
_marker: PhantomData, _marker: PhantomData,
} }
@@ -77,103 +192,32 @@ pub fn supports_passthrough(instance: &XrInstance, system: xr::SystemId) -> xr::
} }
#[inline] #[inline]
pub fn start_passthrough( pub fn create_passthrough(
instance: &XrInstance,
xr_session: &XrSession, xr_session: &XrSession,
) -> xr::Result<(xr::sys::PassthroughFB, xr::sys::PassthroughLayerFB)> { ) -> xr::Result<(xr::Passthrough, xr::PassthroughLayer)> {
unsafe { let passthrough = match xr_session {
// Create feature XrSession::Vulkan(session) => {
let mut passthrough_feature = xr::sys::PassthroughFB::NULL; session.create_passthrough(xr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION)
let mut passthrough_create_info = xr::sys::PassthroughCreateInfoFB { }
ty: xr::sys::StructureType::PASSTHROUGH_CREATE_INFO_FB, // XR_TYPE_PASSTHROUGH_CREATE_INFO_FB }?;
next: ptr::null(), let passthrough_layer = match xr_session {
flags: xr::sys::PassthroughFlagsFB::IS_RUNNING_AT_CREATION, XrSession::Vulkan(session) => session.create_passthrough_layer(
}; &passthrough,
// bevy::log::info!("xr_session.as_raw(): {:?}", xr_session.as_raw()); xr::PassthroughFlagsFB::IS_RUNNING_AT_CREATION,
// bevy::log::info!("&passthrough_create_info: {:?}", &passthrough_create_info); xr::PassthroughLayerPurposeFB::RECONSTRUCTION,
// bevy::log::info!("&mut passthrough_feature: {:?}", &mut passthrough_feature);
// bevy::log::info!(
// "instance.exts().fb_passthrough.unwrap(): {:?}",
// instance.exts().fb_passthrough.is_some()
// );
cvt(
(instance.exts().fb_passthrough.unwrap().create_passthrough)(
xr_session.as_raw(),
&passthrough_create_info as *const _,
&mut passthrough_feature as *mut _,
), ),
)?; }?;
// bevy::log::info!("Created passthrough feature"); Ok((passthrough, passthrough_layer))
// Create layer
let mut passthrough_layer = xr::sys::PassthroughLayerFB::NULL;
let mut layer_create_info: xr::sys::PassthroughLayerCreateInfoFB =
xr::sys::PassthroughLayerCreateInfoFB {
ty: xr::sys::StructureType::PASSTHROUGH_LAYER_CREATE_INFO_FB, // XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB
next: ptr::null(),
passthrough: passthrough_feature, // XR_PASSTHROUGH_HANDLE
flags: xr::sys::PassthroughFlagsFB::IS_RUNNING_AT_CREATION, // XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB
purpose: xr::sys::PassthroughLayerPurposeFB::RECONSTRUCTION, // XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB
};
cvt((instance
.exts()
.fb_passthrough
.unwrap()
.create_passthrough_layer)(
xr_session.as_raw(),
&layer_create_info as *const _,
&mut passthrough_layer as *mut _,
))?;
// bevy::log::info!("Created passthrough layer");
// // Start layer
// bevy::log::info!("passthrough_feature: {:?}", passthrough_feature);
// // cvt((instance.exts().fb_passthrough.unwrap().passthrough_start)(
// // passthrough_feature,
// // ))?;
// bevy::log::info!("Started passthrough layer");
// bevy::log::info!("Passed everything in start");
Ok((passthrough_feature, passthrough_layer))
}
} }
#[inline] /// Enable Passthrough on xr startup
pub fn passthrough_layer_resume(mut xr_data_resource: ResMut<XrRenderData>) -> xr::Result<()> { /// just sends the [`ResumePassthrough`] event in [`XrSetup`]
unsafe { pub struct EnablePassthroughStartup;
let passthrough_layer = &xr_data_resource.xr_passthrough_layer;
{
let passthrough_layer_locked = passthrough_layer.lock().unwrap();
cvt((xr_data_resource
.xr_instance
.exts()
.fb_passthrough
.unwrap()
.passthrough_layer_resume)(
*passthrough_layer_locked
))?;
}
xr_data_resource.xr_passthrough_active = true;
bevy::log::info!("Resumed passthrough layer");
Ok(())
}
}
#[inline] impl Plugin for EnablePassthroughStartup {
pub fn passthrough_layer_pause(mut xr_data_resource: ResMut<XrRenderData>) -> xr::Result<()> { fn build(&self, app: &mut App) {
unsafe { app.add_systems(XrSetup, |mut e: EventWriter<ResumePassthrough>| {
let passthrough_layer = &xr_data_resource.xr_passthrough_layer; e.send_default();
{ });
let passthrough_layer_locked = passthrough_layer.lock().unwrap();
cvt((xr_data_resource
.xr_instance
.exts()
.fb_passthrough
.unwrap()
.passthrough_layer_pause)(
*passthrough_layer_locked
))?;
}
xr_data_resource.xr_passthrough_active = false;
bevy::log::info!("Paused passthrough layer");
Ok(())
} }
} }

15
src/prelude.rs Normal file
View File

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

View File

@@ -1,7 +1,13 @@
#[macro_export] #[macro_export]
macro_rules! xr_resource_wrapper { macro_rules! xr_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => { ($wrapper_type:ident, $xr_type:ty) => {
#[derive(Clone, bevy::prelude::Resource)] #[derive(
Clone,
bevy::prelude::Resource,
bevy::prelude::Deref,
bevy::prelude::DerefMut,
bevy::render::extract_resource::ExtractResource,
)]
pub struct $wrapper_type($xr_type); pub struct $wrapper_type($xr_type);
impl $wrapper_type { impl $wrapper_type {
@@ -10,13 +16,13 @@ macro_rules! xr_resource_wrapper {
} }
} }
impl std::ops::Deref for $wrapper_type { // impl std::ops::Deref for $wrapper_type {
type Target = $xr_type; // type Target = $xr_type;
//
fn deref(&self) -> &Self::Target { // fn deref(&self) -> &Self::Target {
&self.0 // &self.0
} // }
} // }
impl From<$xr_type> for $wrapper_type { impl From<$xr_type> for $wrapper_type {
fn from(value: $xr_type) -> Self { fn from(value: $xr_type) -> Self {
@@ -26,10 +32,50 @@ macro_rules! xr_resource_wrapper {
}; };
} }
#[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_export]
macro_rules! xr_arc_resource_wrapper { macro_rules! xr_arc_resource_wrapper {
($wrapper_type:ident, $xr_type:ty) => { ($wrapper_type:ident, $xr_type:ty) => {
#[derive(Clone, bevy::prelude::Resource)] #[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>); pub struct $wrapper_type(std::sync::Arc<$xr_type>);
impl $wrapper_type { impl $wrapper_type {
@@ -38,13 +84,41 @@ macro_rules! xr_arc_resource_wrapper {
} }
} }
impl std::ops::Deref for $wrapper_type { // impl std::ops::Deref for $wrapper_type {
type Target = $xr_type; // type Target = $xr_type;
//
// fn deref(&self) -> &Self::Target {
// self.0.as_ref()
// }
// }
fn deref(&self) -> &Self::Target { impl From<$xr_type> for $wrapper_type {
self.0.as_ref() 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 { impl From<$xr_type> for $wrapper_type {
fn from(value: $xr_type) -> Self { fn from(value: $xr_type) -> Self {
@@ -55,4 +129,5 @@ macro_rules! xr_arc_resource_wrapper {
} }
pub use xr_arc_resource_wrapper; pub use xr_arc_resource_wrapper;
pub use xr_no_clone_resource_wrapper;
pub use xr_resource_wrapper; pub use xr_resource_wrapper;

View File

@@ -1,26 +1,75 @@
use std::ffi::c_void;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::Mutex; use std::sync::Mutex;
use crate::passthrough::{Passthrough, PassthroughLayer}; use crate::input::XrInput;
use crate::passthrough::{CompositionLayerPassthrough, XrPassthroughLayer};
use crate::resource_macros::*; use crate::resource_macros::*;
use crate::xr::sys::CompositionLayerPassthroughFB; use crate::xr::sys::CompositionLayerPassthroughFB;
use crate::xr::{CompositionLayerBase, CompositionLayerFlags}; use crate::xr::{CompositionLayerBase, CompositionLayerFlags};
use crate::{resource_macros::*, xr_resource_wrapper_copy};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::prelude::*;
use bevy::render::extract_component::ExtractComponent;
use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin};
use core::ptr; use core::ptr;
use openxr as xr; use openxr as xr;
xr_resource_wrapper!(XrInstance, xr::Instance); xr_resource_wrapper!(XrInstance, xr::Instance);
xr_resource_wrapper!(XrSession, xr::Session<xr::AnyGraphics>); xr_resource_wrapper_copy!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode);
xr_resource_wrapper!(XrEnvironmentBlendMode, xr::EnvironmentBlendMode); xr_resource_wrapper_copy!(XrResolution, UVec2);
xr_resource_wrapper!(XrResolution, UVec2); xr_resource_wrapper_copy!(XrFormat, wgpu::TextureFormat);
xr_resource_wrapper!(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!(XrSessionRunning, AtomicBool);
xr_arc_resource_wrapper!(XrFrameWaiter, Mutex<xr::FrameWaiter>);
xr_arc_resource_wrapper!(XrSwapchain, Swapchain); xr_arc_resource_wrapper!(XrSwapchain, Swapchain);
xr_arc_resource_wrapper!(XrFrameState, Mutex<xr::FrameState>); xr_no_clone_resource_wrapper!(XrFrameWaiter, xr::FrameWaiter);
xr_arc_resource_wrapper!(XrViews, Mutex<Vec<xr::View>>);
xr_arc_resource_wrapper!(XrPassthrough, Mutex<xr::sys::PassthroughFB>); #[derive(Clone, Resource, ExtractResource)]
xr_arc_resource_wrapper!(XrPassthroughLayer, Mutex<xr::sys::PassthroughLayerFB>); pub enum XrSession {
Vulkan(xr::Session<xr::Vulkan>),
}
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 {
XrSession::Vulkan(sess) => std::mem::transmute(sess),
}
}
}
}
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,
}
pub enum OXrSessionSetupInfo {
Vulkan(VulkanOXrSessionSetupInfo),
}
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 { pub enum Swapchain {
Vulkan(SwapchainInner<xr::Vulkan>), Vulkan(SwapchainInner<xr::Vulkan>),
@@ -64,7 +113,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<PassthroughLayer>, passthrough_layer: Option<&XrPassthroughLayer>,
) -> xr::Result<()> { ) -> xr::Result<()> {
match self { match self {
Swapchain::Vulkan(swapchain) => swapchain.end( Swapchain::Vulkan(swapchain) => swapchain.end(
@@ -85,6 +134,14 @@ pub struct SwapchainInner<G: xr::Graphics> {
pub(crate) buffers: Vec<wgpu::Texture>, pub(crate) buffers: Vec<wgpu::Texture>,
pub(crate) image_index: Mutex<usize>, 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> { impl<G: xr::Graphics> SwapchainInner<G> {
fn begin(&self) -> xr::Result<()> { fn begin(&self) -> xr::Result<()> {
@@ -133,7 +190,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<PassthroughLayer>, 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 },
@@ -143,7 +200,7 @@ impl<G: xr::Graphics> SwapchainInner<G> {
}, },
}; };
let swapchain = self.handle.lock().unwrap(); let swapchain = self.handle.lock().unwrap();
if views.len() == 0 { if views.is_empty() {
warn!("views are len of 0"); warn!("views are len of 0");
return Ok(()); return Ok(());
} }
@@ -151,21 +208,11 @@ impl<G: xr::Graphics> SwapchainInner<G> {
Some(pass) => { Some(pass) => {
//bevy::log::info!("Rendering with pass through"); //bevy::log::info!("Rendering with pass through");
let passthrough_layer = xr::sys::CompositionLayerPassthroughFB {
ty: CompositionLayerPassthroughFB::TYPE,
next: ptr::null(),
flags: CompositionLayerFlags::UNPREMULTIPLIED_ALPHA,
space: xr::sys::Space::NULL,
layer_handle: pass.0,
};
self.stream.lock().unwrap().end( self.stream.lock().unwrap().end(
predicted_display_time, predicted_display_time,
environment_blend_mode, environment_blend_mode,
&[ &[
unsafe { &CompositionLayerPassthrough::from_xr_passthrough_layer(pass),
&*(&passthrough_layer as *const _ as *const CompositionLayerBase<G>)
},
&xr::CompositionLayerProjection::new() &xr::CompositionLayerProjection::new()
.layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) .layer_flags(CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.space(stage) .space(stage)
@@ -194,7 +241,7 @@ impl<G: xr::Graphics> SwapchainInner<G> {
} }
None => { None => {
bevy::log::info!("Rendering without pass through"); // bevy::log::info!("Rendering without pass through");
self.stream.lock().unwrap().end( self.stream.lock().unwrap().end(
predicted_display_time, predicted_display_time,
environment_blend_mode, environment_blend_mode,

View File

@@ -1,352 +0,0 @@
// Just a lot of code that is meant for something way more complex but hey.
// maybe will work on that soon
use std::sync::Arc;
use bevy::{
ecs::schedule::{ExecutorKind, ScheduleLabel},
prelude::*,
render::{
extract_resource::{ExtractResource, ExtractResourcePlugin},
renderer::{self, RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue},
settings::WgpuSettings,
},
window::RawHandleWrapper,
};
use wgpu::Instance;
use crate::{
input::XrInput,
passthrough::{Passthrough, PassthroughLayer},
resources::{
XrEnvironmentBlendMode, XrFormat, XrFrameState, XrFrameWaiter, XrInstance, XrPassthrough,
XrPassthroughLayer, XrResolution, XrSession, XrSessionRunning, XrSwapchain, XrViews,
},
};
#[derive(Resource, Clone)]
pub struct RenderCreationData {
pub device: RenderDevice,
pub queue: RenderQueue,
pub adapter_info: RenderAdapterInfo,
pub render_adapter: RenderAdapter,
pub instance: Arc<Instance>,
}
#[derive(Resource, Clone, ExtractResource)]
pub struct XrRenderData {
pub xr_instance: XrInstance,
pub xr_session: XrSession,
pub xr_blend_mode: XrEnvironmentBlendMode,
pub xr_resolution: XrResolution,
pub xr_format: XrFormat,
pub xr_session_running: XrSessionRunning,
pub xr_frame_waiter: XrFrameWaiter,
pub xr_swapchain: XrSwapchain,
pub xr_input: XrInput,
pub xr_views: XrViews,
pub xr_frame_state: XrFrameState,
pub xr_passthrough_active: bool,
pub xr_passthrough: XrPassthrough,
pub xr_passthrough_layer: XrPassthroughLayer,
}
#[derive(Event, Clone, Copy, Debug)]
pub enum XrEnableRequest {
TryEnable,
TryDisable,
}
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq)]
pub enum XrEnableStatus {
Enabled,
Disabled,
Waiting,
}
#[derive(Resource, Event, Copy, Clone, PartialEq, Eq, Debug)]
pub enum XrNextEnabledState {
Enabled,
Disabled,
}
pub struct RenderRestartPlugin;
#[derive(Resource)]
pub struct ForceMain;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPrePostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostRenderUpdate;
pub fn xr_only() -> impl FnMut(Option<Res<'_, XrEnableStatus>>) -> bool {
resource_exists_and_equals(XrEnableStatus::Enabled)
}
impl Plugin for RenderRestartPlugin {
fn build(&self, app: &mut App) {
add_schedules(app);
app.add_plugins(ExtractResourcePlugin::<XrRenderData>::default())
.insert_resource(ForceMain)
.add_event::<XrEnableRequest>()
.add_event::<XrEnableStatus>()
.add_systems(PostStartup, setup_xr.run_if(xr_only()))
.add_systems(
PostUpdate,
update_xr_stuff.run_if(on_event::<XrEnableRequest>()),
)
.add_systems(XrPreRenderUpdate, decide_next_xr_state)
.add_systems(XrPostRenderUpdate, clear_events)
.add_systems(
XrRenderUpdate,
(
cleanup_xr.run_if(resource_exists_and_equals(XrNextEnabledState::Disabled)),
// handle_xr_enable_requests,
apply_deferred,
setup_xr, /* .run_if(resource_exists_and_equals(XrEnableStatus::Enabled)) */
)
.chain(),
)
.add_systems(XrCleanup, cleanup_oxr_session);
}
}
fn clear_events(mut events: ResMut<Events<XrEnableRequest>>) {
events.clear();
}
fn add_schedules(app: &mut App) {
let schedules = [
Schedule::new(XrPreSetup),
Schedule::new(XrSetup),
Schedule::new(XrPrePostSetup),
Schedule::new(XrPostSetup),
Schedule::new(XrPreRenderUpdate),
Schedule::new(XrRenderUpdate),
Schedule::new(XrPostRenderUpdate),
Schedule::new(XrPreCleanup),
Schedule::new(XrCleanup),
Schedule::new(XrPostCleanup),
];
for mut schedule in schedules {
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
schedule.set_apply_final_deferred(true);
app.add_schedule(schedule);
}
}
pub fn setup_xr(world: &mut World) {
world.run_schedule(XrPreSetup);
world.run_schedule(XrSetup);
world.run_schedule(XrPrePostSetup);
world.run_schedule(XrPostSetup);
}
fn cleanup_xr(world: &mut World) {
world.run_schedule(XrPreCleanup);
world.run_schedule(XrCleanup);
world.run_schedule(XrPostCleanup);
}
fn cleanup_oxr_session(xr_status: Option<Res<XrEnableStatus>>, session: Option<ResMut<XrSession>>) {
if let (Some(XrEnableStatus::Disabled), Some(s)) = (xr_status.map(|v| v.into_inner()), session)
{
s.into_inner().request_exit().unwrap();
}
}
pub fn update_xr_stuff(world: &mut World) {
world.run_schedule(XrPreRenderUpdate);
world.run_schedule(XrRenderUpdate);
world.run_schedule(XrPostRenderUpdate);
}
// fn handle_xr_enable_requests(
// primary_window: Query<&RawHandleWrapper, With<PrimaryWindow>>,
// mut commands: Commands,
// next_state: Res<XrNextEnabledState>,
// on_main: Option<NonSend<ForceMain>>,
// ) {
// // Just to force this system onto the main thread because of unsafe code
// let _ = on_main;
//
// commands.insert_resource(XrEnableStatus::Waiting);
// let (creation_data, xr_data) = match next_state.into_inner() {
// XrNextEnabledState::Enabled => {
// let (
// device,
// queue,
// adapter_info,
// render_adapter,
// instance,
// xr_instance,
// session,
// blend_mode,
// resolution,
// format,
// session_running,
// frame_waiter,
// swapchain,
// input,
// views,
// frame_state,
// ) = graphics::initialize_xr_graphics(primary_window.get_single().ok().cloned())
// .unwrap();
//
// commands.insert_resource(XrEnableStatus::Enabled);
// (
// RenderCreationData {
// device,
// queue,
// adapter_info,
// render_adapter,
// instance: Arc::new(instance),
// },
// Some(XrRenderData {
// xr_instance,
// xr_session: session,
// xr_blend_mode: blend_mode,
// xr_resolution: resolution,
// xr_format: format,
// xr_session_running: session_running,
// xr_frame_waiter: frame_waiter,
// xr_swapchain: swapchain,
// xr_input: input,
// xr_views: views,
// xr_frame_state: frame_state,
// }),
// )
// }
// XrNextEnabledState::Disabled => (
// init_non_xr_graphics(primary_window.get_single().ok().cloned()),
// None,
// ),
// };
//
// commands.insert_resource(creation_data.device);
// commands.insert_resource(creation_data.queue);
// commands.insert_resource(RenderInstance(creation_data.instance));
// commands.insert_resource(creation_data.adapter_info);
// commands.insert_resource(creation_data.render_adapter);
//
// if let Some(xr_data) = xr_data {
// // TODO: Remove this lib.rs:144
// commands.insert_resource(xr_data.clone());
//
// commands.insert_resource(xr_data.xr_instance);
// commands.insert_resource(xr_data.xr_session);
// commands.insert_resource(xr_data.xr_blend_mode);
// commands.insert_resource(xr_data.xr_resolution);
// commands.insert_resource(xr_data.xr_format);
// commands.insert_resource(xr_data.xr_session_running);
// commands.insert_resource(xr_data.xr_frame_waiter);
// commands.insert_resource(xr_data.xr_input);
// commands.insert_resource(xr_data.xr_views);
// commands.insert_resource(xr_data.xr_frame_state);
// commands.insert_resource(xr_data.xr_swapchain);
// } else {
// commands.remove_resource::<XrRenderData>();
//
// commands.remove_resource::<XrInstance>();
// commands.remove_resource::<XrSession>();
// commands.remove_resource::<XrEnvironmentBlendMode>();
// commands.remove_resource::<XrResolution>();
// commands.remove_resource::<XrFormat>();
// commands.remove_resource::<XrSessionRunning>();
// commands.remove_resource::<XrFrameWaiter>();
// commands.remove_resource::<XrSwapchain>();
// commands.remove_resource::<XrInput>();
// commands.remove_resource::<XrViews>();
// commands.remove_resource::<XrFrameState>();
// }
// }
fn decide_next_xr_state(
mut commands: Commands,
mut events: EventReader<XrEnableRequest>,
xr_status: Option<Res<XrEnableStatus>>,
) {
info!("hm");
let request = match events.read().next() {
Some(v) => v,
None => return,
};
info!("ok");
match (request, xr_status.as_deref()) {
(XrEnableRequest::TryEnable, Some(XrEnableStatus::Enabled)) => {
info!("Xr Already Enabled! ignoring request");
return;
}
(XrEnableRequest::TryDisable, Some(XrEnableStatus::Disabled)) => {
info!("Xr Already Disabled! ignoring request");
return;
}
(_, Some(XrEnableStatus::Waiting)) => {
info!("Already Handling Request! ignoring request");
return;
}
_ => {}
}
let r = match request {
XrEnableRequest::TryEnable => XrNextEnabledState::Enabled,
XrEnableRequest::TryDisable => XrNextEnabledState::Disabled,
};
info!("{:#?}", r);
commands.insert_resource(r);
}
pub fn init_non_xr_graphics(primary_window: Option<RawHandleWrapper>) -> RenderCreationData {
let settings = WgpuSettings::default();
let async_renderer = async move {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
// Probably a bad idea unwraping here but on the other hand no backends
backends: settings.backends.unwrap(),
dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
});
let surface = primary_window.map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle();
instance
.create_surface(&handle)
.expect("Failed to create wgpu surface")
});
let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: settings.power_preference,
compatible_surface: surface.as_ref(),
..Default::default()
};
let (device, queue, adapter_info, render_adapter) =
renderer::initialize_renderer(&instance, &settings, &request_adapter_options).await;
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
debug!("Configured wgpu adapter Features: {:#?}", device.features());
RenderCreationData {
device,
queue,
adapter_info,
render_adapter,
instance: Arc::new(instance),
}
};
// No need for wasm in bevy_oxr web xr would be a different crate
futures_lite::future::block_on(async_renderer)
}

246
src/xr_init/mod.rs Normal file
View File

@@ -0,0 +1,246 @@
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: Res<XrInstance>,
primary_window: Query<&RawHandleWrapper, With<PrimaryWindow>>,
setup_info: NonSend<OXrSessionSetupInfo>,
render_device: Res<RenderDevice>,
render_adapter: Res<RenderAdapter>,
render_instance: 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 (
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;
}

54
src/xr_init/schedules.rs Normal file
View File

@@ -0,0 +1,54 @@
use bevy::{
app::App,
ecs::schedule::{ExecutorKind, Schedule, ScheduleLabel},
};
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPrePostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostSetup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostCleanup;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPreRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrRenderUpdate;
#[derive(Debug, ScheduleLabel, Clone, Copy, Hash, PartialEq, Eq)]
pub struct XrPostRenderUpdate;
pub(super) fn add_schedules(app: &mut App) {
let schedules = [
Schedule::new(XrPreSetup),
Schedule::new(XrSetup),
Schedule::new(XrPrePostSetup),
Schedule::new(XrPostSetup),
Schedule::new(XrPreRenderUpdate),
Schedule::new(XrRenderUpdate),
Schedule::new(XrPostRenderUpdate),
Schedule::new(XrPreCleanup),
Schedule::new(XrCleanup),
Schedule::new(XrPostCleanup),
];
for mut schedule in schedules {
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
schedule.set_apply_final_deferred(true);
app.add_schedule(schedule);
}
}

View File

@@ -6,21 +6,36 @@ use xr::{Action, Binding, Haptic, Posef, Vector2f};
use crate::{ use crate::{
resources::{XrInstance, XrSession}, resources::{XrInstance, XrSession},
xr_init::XrPrePostSetup, xr_init::{xr_only, XrCleanup, XrPrePostSetup, XrPreSetup},
}; };
use super::oculus_touch::ActionSets; use super::oculus_touch::ActionSets;
pub use xr::sys::NULL_PATH; pub use xr::sys::NULL_PATH;
pub struct OpenXrActionsPlugin; pub struct XrActionsPlugin;
impl Plugin for OpenXrActionsPlugin { impl Plugin for XrActionsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(SetupActionSets { 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(), sets: HashMap::new(),
}); });
app.add_systems(XrPrePostSetup, setup_oxr_actions);
} }
fn clean_actions(mut cmds: Commands) {
cmds.remove_resource::<ActionSets>();
cmds.remove_resource::<XrActionSets>();
} }
#[inline(always)] #[inline(always)]
@@ -47,7 +62,6 @@ pub fn setup_oxr_actions(world: &mut World) {
let right_path = instance.string_to_path("/user/hand/right").unwrap(); let right_path = instance.string_to_path("/user/hand/right").unwrap();
let hands = [left_path, right_path]; let hands = [left_path, right_path];
let mut oxr_action_sets = Vec::new();
let mut action_sets = XrActionSets { sets: default() }; 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, Vec<xr::Path>> = HashMap::new();
let mut action_bindings: HashMap< let mut action_bindings: HashMap<
@@ -91,11 +105,11 @@ pub fn setup_oxr_actions(world: &mut World) {
} }
} }
} }
oxr_action_sets.push(oxr_action_set); // oxr_action_sets.push(oxr_action_set);
action_sets.sets.insert( action_sets.sets.insert(
set_name, set_name,
ActionSet { ActionSet {
// oxr_action_set, oxr_action_set,
actions, actions,
enabled: true, enabled: true,
}, },
@@ -142,10 +156,15 @@ pub fn setup_oxr_actions(world: &mut World) {
.expect("Unable to suggest interaction bindings!"); .expect("Unable to suggest interaction bindings!");
} }
session session
.attach_action_sets(&oxr_action_sets.iter().collect::<Vec<_>>()) .attach_action_sets(
&action_sets
.sets
.values()
.map(|set| &set.oxr_action_set)
.collect::<Vec<_>>(),
)
.expect("Unable to attach action sets!"); .expect("Unable to attach action sets!");
world.insert_resource(ActionSets(oxr_action_sets));
world.insert_resource(action_sets); world.insert_resource(action_sets);
} }
@@ -206,7 +225,7 @@ impl SetupActionSet {
for binding in bindings { for binding in bindings {
self.actions self.actions
.get_mut(binding.action) .get_mut(binding.action)
.ok_or(anyhow::anyhow!("Missing Action: {}", binding.action)) .ok_or(eyre::eyre!("Missing Action: {}", binding.action))
.unwrap() .unwrap()
.bindings .bindings
.entry(device_path) .entry(device_path)
@@ -257,6 +276,7 @@ pub struct ActionSet {
// add functionality to enable/disable action sets // add functionality to enable/disable action sets
enabled: bool, enabled: bool,
actions: HashMap<&'static str, TypedAction>, actions: HashMap<&'static str, TypedAction>,
oxr_action_set: xr::ActionSet,
} }
#[derive(Resource)] #[derive(Resource)]
@@ -370,3 +390,20 @@ impl XrActionSets {
} }
} }
} }
pub fn sync_actions(action_sets: Res<XrActionSets>, session: Res<XrSession>) {
let active_sets = action_sets
.sets
.values()
.filter_map(|set| {
if set.enabled {
Some(xr::ActiveActionSet::new(&set.oxr_action_set))
} else {
None
}
})
.collect::<Vec<_>>();
if let Err(err) = session.sync_actions(&active_sets) {
warn!("OpenXR action sync error: {}", err);
}
}

View File

@@ -8,7 +8,3 @@ pub struct Handed<T> {
pub left: T, pub left: T,
pub right: T, pub right: T,
} }
#[derive(Copy, Clone)]
pub enum XrControllerType {
OculusTouch,
}

View File

@@ -1,5 +1,6 @@
use bevy::ecs::schedule::IntoSystemConfigs; use bevy::ecs::schedule::IntoSystemConfigs;
use bevy::log::{debug, info}; use bevy::log::{debug, info};
use bevy::math::primitives::Direction3d;
use bevy::prelude::{ use bevy::prelude::{
Color, Gizmos, GlobalTransform, Plugin, Quat, Query, Res, Transform, Update, Vec2, Vec3, With, Color, Gizmos, GlobalTransform, Plugin, Quat, Query, Res, Transform, Update, Vec2, Vec3, With,
Without, Without,
@@ -91,7 +92,7 @@ pub fn draw_gizmos(
// } // }
// } // }
//lock frame //lock frame
let frame_state = *frame_state.lock().unwrap(); // let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
let root = tracking_root_query.get_single(); let root = tracking_root_query.get_single();
@@ -104,7 +105,7 @@ pub fn draw_gizmos(
y: 0.01, y: 0.01,
z: 0.0, z: 0.0,
}, },
Vec3::Y, Direction3d::Y,
0.2, 0.2,
Color::RED, Color::RED,
); );
@@ -168,7 +169,7 @@ fn draw_hand_gizmo(
//draw face //draw face
gizmos.circle( gizmos.circle(
face_translation_vec3, face_translation_vec3,
face_quat_normal, Direction3d::new_unchecked(face_quat_normal),
0.04, 0.04,
Color::YELLOW_GREEN, Color::YELLOW_GREEN,
); );
@@ -185,7 +186,12 @@ fn draw_hand_gizmo(
let b_offset_quat = face_quat; let b_offset_quat = face_quat;
let b_translation_vec3 = let b_translation_vec3 =
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(0.025, -0.01, 0.0)); face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(0.025, -0.01, 0.0));
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color); gizmos.circle(
b_translation_vec3,
Direction3d::new_unchecked(face_quat_normal),
0.0075,
b_color,
);
//button a //button a
let mut a_color = off_color; let mut a_color = off_color;
@@ -199,7 +205,12 @@ fn draw_hand_gizmo(
let a_offset_quat = face_quat; let a_offset_quat = face_quat;
let a_translation_vec3 = let a_translation_vec3 =
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(0.025, 0.01, 0.0)); face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(0.025, 0.01, 0.0));
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color); gizmos.circle(
a_translation_vec3,
Direction3d::new_unchecked(face_quat_normal),
0.0075,
a_color,
);
//joystick //joystick
let joystick_offset_quat = face_quat; let joystick_offset_quat = face_quat;
@@ -211,7 +222,12 @@ fn draw_hand_gizmo(
} }
//base //base
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color); gizmos.circle(
joystick_base_vec,
Direction3d::new_unchecked(face_quat_normal),
0.014,
joystick_color,
);
let stick = controller.thumbstick(Hand::Left); let stick = controller.thumbstick(Hand::Left);
let input = Vec3::new(stick.x, -stick.y, 0.0); let input = Vec3::new(stick.x, -stick.y, 0.0);
@@ -219,7 +235,12 @@ fn draw_hand_gizmo(
+ joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, -0.01)) + joystick_offset_quat.mul_vec3(Vec3::new(-0.02, 0.0, -0.01))
+ joystick_offset_quat.mul_vec3(input * 0.01); + joystick_offset_quat.mul_vec3(input * 0.01);
//top //top
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color); gizmos.circle(
joystick_top_vec,
Direction3d::new_unchecked(face_quat_normal),
0.005,
joystick_color,
);
//trigger //trigger
let trigger_state = controller.trigger(Hand::Left); let trigger_state = controller.trigger(Hand::Left);
@@ -277,7 +298,7 @@ fn draw_hand_gizmo(
//draw face //draw face
gizmos.circle( gizmos.circle(
face_translation_vec3, face_translation_vec3,
face_quat_normal, Direction3d::new_unchecked(face_quat_normal),
0.04, 0.04,
Color::YELLOW_GREEN, Color::YELLOW_GREEN,
); );
@@ -294,7 +315,12 @@ fn draw_hand_gizmo(
let b_offset_quat = face_quat; let b_offset_quat = face_quat;
let b_translation_vec3 = let b_translation_vec3 =
face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(-0.025, -0.01, 0.0)); face_translation_vec3 + b_offset_quat.mul_vec3(Vec3::new(-0.025, -0.01, 0.0));
gizmos.circle(b_translation_vec3, face_quat_normal, 0.0075, b_color); gizmos.circle(
b_translation_vec3,
Direction3d::new_unchecked(face_quat_normal),
0.0075,
b_color,
);
//button a //button a
let mut a_color = off_color; let mut a_color = off_color;
@@ -308,7 +334,12 @@ fn draw_hand_gizmo(
let a_offset_quat = face_quat; let a_offset_quat = face_quat;
let a_translation_vec3 = let a_translation_vec3 =
face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(-0.025, 0.01, 0.0)); face_translation_vec3 + a_offset_quat.mul_vec3(Vec3::new(-0.025, 0.01, 0.0));
gizmos.circle(a_translation_vec3, face_quat_normal, 0.0075, a_color); gizmos.circle(
a_translation_vec3,
Direction3d::new_unchecked(face_quat_normal),
0.0075,
a_color,
);
//joystick time //joystick time
let joystick_offset_quat = face_quat; let joystick_offset_quat = face_quat;
@@ -320,7 +351,12 @@ fn draw_hand_gizmo(
} }
//base //base
gizmos.circle(joystick_base_vec, face_quat_normal, 0.014, joystick_color); gizmos.circle(
joystick_base_vec,
Direction3d::new_unchecked(face_quat_normal),
0.014,
joystick_color,
);
let stick = controller.thumbstick(Hand::Right); let stick = controller.thumbstick(Hand::Right);
let input = Vec3::new(stick.x, -stick.y, 0.0); let input = Vec3::new(stick.x, -stick.y, 0.0);
@@ -328,7 +364,12 @@ fn draw_hand_gizmo(
+ joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, -0.01)) + joystick_offset_quat.mul_vec3(Vec3::new(0.02, 0.0, -0.01))
+ joystick_offset_quat.mul_vec3(input * 0.01); + joystick_offset_quat.mul_vec3(input * 0.01);
//top //top
gizmos.circle(joystick_top_vec, face_quat_normal, 0.005, joystick_color); gizmos.circle(
joystick_top_vec,
Direction3d::new_unchecked(face_quat_normal),
0.005,
joystick_color,
);
//trigger //trigger
let trigger_state = controller.trigger(Hand::Right); let trigger_state = controller.trigger(Hand::Right);

View File

@@ -136,8 +136,7 @@ pub fn get_simulated_open_hand_transforms(hand: Hand) -> [Transform; 26] {
z: 0.01, z: 0.01,
}, },
]; ];
let result = bones_to_transforms(test_hand_bones, hand); bones_to_transforms(test_hand_bones, hand)
return result;
} }
fn bones_to_transforms(hand_bones: [Vec3; 26], hand: Hand) -> [Transform; 26] { fn bones_to_transforms(hand_bones: [Vec3; 26], hand: Hand) -> [Transform; 26] {

View File

@@ -1,6 +1,10 @@
use bevy::prelude::{ use bevy::{
core::Name,
prelude::{
default, Color, Commands, Component, Deref, DerefMut, Entity, Gizmos, Plugin, PostUpdate, default, Color, Commands, Component, Deref, DerefMut, Entity, Gizmos, Plugin, PostUpdate,
Query, Resource, SpatialBundle, Startup, Transform, Query, Resource, SpatialBundle, Startup, Transform,
},
transform::components::GlobalTransform,
}; };
use crate::xr_input::{trackers::OpenXRTracker, Hand}; use crate::xr_input::{trackers::OpenXRTracker, Hand};
@@ -8,14 +12,14 @@ use crate::xr_input::{trackers::OpenXRTracker, Hand};
use super::{BoneTrackingStatus, HandBone}; use super::{BoneTrackingStatus, HandBone};
/// add debug renderer for controllers /// add debug renderer for controllers
#[derive(Default)] // #[derive(Default)]
pub struct OpenXrHandInput; // pub struct OpenXrHandInput;
//
impl Plugin for OpenXrHandInput { // impl Plugin for OpenXrHandInput {
fn build(&self, app: &mut bevy::prelude::App) { // fn build(&self, app: &mut bevy::prelude::App) {
app.add_systems(Startup, spawn_hand_entities); // app.add_systems(Startup, spawn_hand_entities);
} // }
} // }
/// add debug renderer for controllers /// add debug renderer for controllers
#[derive(Default)] #[derive(Default)]
@@ -161,75 +165,46 @@ pub fn spawn_hand_entities(mut commands: Commands) {
for bone in bones.iter() { for bone in bones.iter() {
let boneid = commands let boneid = commands
.spawn(( .spawn((
Name::new(format!("{:?} {:?}", hand, bone)),
SpatialBundle::default(), SpatialBundle::default(),
bone.clone(), *bone,
OpenXRTracker, OpenXRTracker,
hand.clone(), *hand,
BoneTrackingStatus::Emulated, BoneTrackingStatus::Tracked,
HandBoneRadius(0.1), HandBoneRadius(0.1),
)) ))
.id(); .id();
match hand { let hand_res = match hand {
Hand::Left => match bone { Hand::Left => &mut hand_resource.left,
HandBone::Palm => hand_resource.left.palm = boneid, Hand::Right => &mut hand_resource.right,
HandBone::Wrist => hand_resource.left.wrist = boneid, };
HandBone::ThumbMetacarpal => hand_resource.left.thumb.metacarpal = boneid, match bone {
HandBone::ThumbProximal => hand_resource.left.thumb.proximal = boneid, HandBone::Palm => hand_res.palm = boneid,
HandBone::ThumbDistal => hand_resource.left.thumb.distal = boneid, HandBone::Wrist => hand_res.wrist = boneid,
HandBone::ThumbTip => hand_resource.left.thumb.tip = boneid, HandBone::ThumbMetacarpal => hand_res.thumb.metacarpal = boneid,
HandBone::IndexMetacarpal => hand_resource.left.index.metacarpal = boneid, HandBone::ThumbProximal => hand_res.thumb.proximal = boneid,
HandBone::IndexProximal => hand_resource.left.index.proximal = boneid, HandBone::ThumbDistal => hand_res.thumb.distal = boneid,
HandBone::IndexIntermediate => hand_resource.left.index.intermediate = boneid, HandBone::ThumbTip => hand_res.thumb.tip = boneid,
HandBone::IndexDistal => hand_resource.left.index.distal = boneid, HandBone::IndexMetacarpal => hand_res.index.metacarpal = boneid,
HandBone::IndexTip => hand_resource.left.index.tip = boneid, HandBone::IndexProximal => hand_res.index.proximal = boneid,
HandBone::MiddleMetacarpal => hand_resource.left.middle.metacarpal = boneid, HandBone::IndexIntermediate => hand_res.index.intermediate = boneid,
HandBone::MiddleProximal => hand_resource.left.middle.proximal = boneid, HandBone::IndexDistal => hand_res.index.distal = boneid,
HandBone::MiddleIntermediate => hand_resource.left.middle.intermediate = boneid, HandBone::IndexTip => hand_res.index.tip = boneid,
HandBone::MiddleDistal => hand_resource.left.middle.distal = boneid, HandBone::MiddleMetacarpal => hand_res.middle.metacarpal = boneid,
HandBone::MiddleTip => hand_resource.left.middle.tip = boneid, HandBone::MiddleProximal => hand_res.middle.proximal = boneid,
HandBone::RingMetacarpal => hand_resource.left.ring.metacarpal = boneid, HandBone::MiddleIntermediate => hand_res.middle.intermediate = boneid,
HandBone::RingProximal => hand_resource.left.ring.proximal = boneid, HandBone::MiddleDistal => hand_res.middle.distal = boneid,
HandBone::RingIntermediate => hand_resource.left.ring.intermediate = boneid, HandBone::MiddleTip => hand_res.middle.tip = boneid,
HandBone::RingDistal => hand_resource.left.ring.distal = boneid, HandBone::RingMetacarpal => hand_res.ring.metacarpal = boneid,
HandBone::RingTip => hand_resource.left.ring.tip = boneid, HandBone::RingProximal => hand_res.ring.proximal = boneid,
HandBone::LittleMetacarpal => hand_resource.left.little.metacarpal = boneid, HandBone::RingIntermediate => hand_res.ring.intermediate = boneid,
HandBone::LittleProximal => hand_resource.left.little.proximal = boneid, HandBone::RingDistal => hand_res.ring.distal = boneid,
HandBone::LittleIntermediate => hand_resource.left.little.intermediate = boneid, HandBone::RingTip => hand_res.ring.tip = boneid,
HandBone::LittleDistal => hand_resource.left.little.distal = boneid, HandBone::LittleMetacarpal => hand_res.little.metacarpal = boneid,
HandBone::LittleTip => hand_resource.left.little.tip = boneid, HandBone::LittleProximal => hand_res.little.proximal = boneid,
}, HandBone::LittleIntermediate => hand_res.little.intermediate = boneid,
Hand::Right => match bone { HandBone::LittleDistal => hand_res.little.distal = boneid,
HandBone::Palm => hand_resource.right.palm = boneid, HandBone::LittleTip => hand_res.little.tip = boneid,
HandBone::Wrist => hand_resource.right.wrist = boneid,
HandBone::ThumbMetacarpal => hand_resource.right.thumb.metacarpal = boneid,
HandBone::ThumbProximal => hand_resource.right.thumb.proximal = boneid,
HandBone::ThumbDistal => hand_resource.right.thumb.distal = boneid,
HandBone::ThumbTip => hand_resource.right.thumb.tip = boneid,
HandBone::IndexMetacarpal => hand_resource.right.index.metacarpal = boneid,
HandBone::IndexProximal => hand_resource.right.index.proximal = boneid,
HandBone::IndexIntermediate => hand_resource.right.index.intermediate = boneid,
HandBone::IndexDistal => hand_resource.right.index.distal = boneid,
HandBone::IndexTip => hand_resource.right.index.tip = boneid,
HandBone::MiddleMetacarpal => hand_resource.right.middle.metacarpal = boneid,
HandBone::MiddleProximal => hand_resource.right.middle.proximal = boneid,
HandBone::MiddleIntermediate => {
hand_resource.right.middle.intermediate = boneid
}
HandBone::MiddleDistal => hand_resource.right.middle.distal = boneid,
HandBone::MiddleTip => hand_resource.right.middle.tip = boneid,
HandBone::RingMetacarpal => hand_resource.right.ring.metacarpal = boneid,
HandBone::RingProximal => hand_resource.right.ring.proximal = boneid,
HandBone::RingIntermediate => hand_resource.right.ring.intermediate = boneid,
HandBone::RingDistal => hand_resource.right.ring.distal = boneid,
HandBone::RingTip => hand_resource.right.ring.tip = boneid,
HandBone::LittleMetacarpal => hand_resource.right.little.metacarpal = boneid,
HandBone::LittleProximal => hand_resource.right.little.proximal = boneid,
HandBone::LittleIntermediate => {
hand_resource.right.little.intermediate = boneid
}
HandBone::LittleDistal => hand_resource.right.little.distal = boneid,
HandBone::LittleTip => hand_resource.right.little.tip = boneid,
},
} }
} }
} }
@@ -241,16 +216,12 @@ pub struct HandBoneRadius(pub f32);
pub fn draw_hand_entities( pub fn draw_hand_entities(
mut gizmos: Gizmos, mut gizmos: Gizmos,
query: Query<(&Transform, &HandBone, &HandBoneRadius)>, query: Query<(&GlobalTransform, &HandBone, &HandBoneRadius)>,
) { ) {
for (transform, hand_bone, hand_bone_radius) in query.iter() { for (transform, hand_bone, hand_bone_radius) in query.iter() {
let (_, color) = get_bone_gizmo_style(hand_bone); let (_, color) = get_bone_gizmo_style(hand_bone);
gizmos.sphere( let (_, rotation, translation) = transform.to_scale_rotation_translation();
transform.translation, gizmos.sphere(translation, rotation, hand_bone_radius.0, color);
transform.rotation,
hand_bone_radius.0,
color,
);
} }
} }

View File

@@ -92,8 +92,6 @@ fn setup_hand_emulation_action_set(mut action_sets: ResMut<SetupActionSets>) {
suggest_oculus_touch_profile(action_set); suggest_oculus_touch_profile(action_set);
} }
pub struct EmulatedHandPoseData {}
fn suggest_oculus_touch_profile(action_set: &mut SetupActionSet) { fn suggest_oculus_touch_profile(action_set: &mut SetupActionSet) {
action_set.suggest_binding( action_set.suggest_binding(
"/interaction_profiles/oculus/touch_controller", "/interaction_profiles/oculus/touch_controller",
@@ -131,7 +129,6 @@ pub(crate) fn update_hand_skeleton_from_emulated(
action_sets: Res<XrActionSets>, action_sets: Res<XrActionSets>,
left_controller_transform: Query<&Transform, With<OpenXRLeftController>>, left_controller_transform: Query<&Transform, With<OpenXRLeftController>>,
right_controller_transform: Query<&Transform, With<OpenXRRightController>>, right_controller_transform: Query<&Transform, With<OpenXRRightController>>,
tracking_root_transform: Query<&Transform, With<OpenXRTrackingRoot>>,
mut bones: Query< mut bones: Query<
( (
&mut Transform, &mut Transform,
@@ -226,7 +223,6 @@ pub(crate) fn update_hand_skeleton_from_emulated(
}, },
} }
} }
let trt = tracking_root_transform.single();
for (mut t, bone, hand, status, mut radius) in bones.iter_mut() { for (mut t, bone, hand, status, mut radius) in bones.iter_mut() {
match status { match status {
BoneTrackingStatus::Emulated => {} BoneTrackingStatus::Emulated => {}
@@ -238,9 +234,9 @@ pub(crate) fn update_hand_skeleton_from_emulated(
Hand::Left => 0, Hand::Left => 0,
Hand::Right => 1, Hand::Right => 1,
}][bone.get_index_from_bone()]; }][bone.get_index_from_bone()];
*t = t.with_scale(trt.scale); // *t = t.with_scale(trt.scale);
*t = t.with_rotation(trt.rotation * t.rotation); // *t = t.with_rotation(trt.rotation * t.rotation);
*t = t.with_translation(trt.transform_point(t.translation)); // *t = t.with_translation(trt.transform_point(t.translation));
} }
} }
pub fn update_hand_bones_emulated( pub fn update_hand_bones_emulated(

View File

@@ -6,7 +6,7 @@ use crate::{
input::XrInput, input::XrInput,
resources::{XrFrameState, XrSession}, resources::{XrFrameState, XrSession},
xr_init::xr_only, xr_init::xr_only,
xr_input::{hands::HandBone, trackers::OpenXRTrackingRoot, Hand, QuatConv, Vec3Conv}, xr_input::{hands::HandBone, Hand, QuatConv, Vec3Conv},
}; };
use super::BoneTrackingStatus; use super::BoneTrackingStatus;
@@ -63,6 +63,7 @@ pub struct HandJoint {
pub radius: f32, pub radius: f32,
} }
#[derive(Debug)]
pub struct HandJoints { pub struct HandJoints {
inner: [HandJoint; 26], inner: [HandJoint; 26],
} }
@@ -87,7 +88,7 @@ impl<'a> HandTrackingRef<'a> {
Hand::Left => &self.tracking.left_hand, Hand::Left => &self.tracking.left_hand,
Hand::Right => &self.tracking.right_hand, Hand::Right => &self.tracking.right_hand,
}, },
self.frame_state.lock().unwrap().predicted_display_time, self.frame_state.predicted_display_time,
) )
.unwrap() .unwrap()
.map(|joints| { .map(|joints| {
@@ -158,7 +159,6 @@ pub fn update_hand_bones(
hand_tracking: Option<Res<HandTrackingData>>, hand_tracking: Option<Res<HandTrackingData>>,
xr_input: Res<XrInput>, xr_input: Res<XrInput>,
xr_frame_state: Res<XrFrameState>, xr_frame_state: Res<XrFrameState>,
root_query: Query<(&Transform, With<OpenXRTrackingRoot>, Without<HandBone>)>,
mut bones: Query<( mut bones: Query<(
&mut Transform, &mut Transform,
&Hand, &Hand,
@@ -174,9 +174,12 @@ pub fn update_hand_bones(
return; return;
} }
}; };
let (root_transform, _, _) = root_query.get_single().unwrap();
let left_hand_data = hand_ref.get_poses(Hand::Left); let left_hand_data = hand_ref.get_poses(Hand::Left);
let right_hand_data = hand_ref.get_poses(Hand::Right); 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 bones
.par_iter_mut() .par_iter_mut()
.for_each(|(mut transform, hand, bone, mut radius, mut status)| { .for_each(|(mut transform, hand, bone, mut radius, mut status)| {
@@ -194,7 +197,7 @@ pub fn update_hand_bones(
let bone_data = match (hand, &left_hand_data, &right_hand_data) { let bone_data = match (hand, &left_hand_data, &right_hand_data) {
(Hand::Left, Some(data), _) => data.get_joint(*bone), (Hand::Left, Some(data), _) => data.get_joint(*bone),
(Hand::Right, _, Some(data)) => data.get_joint(*bone), (Hand::Right, _, Some(data)) => data.get_joint(*bone),
_ => { (hand, left_data, right_data) => {
*status = BoneTrackingStatus::Emulated; *status = BoneTrackingStatus::Emulated;
return; return;
} }
@@ -203,8 +206,7 @@ pub fn update_hand_bones(
*status = BoneTrackingStatus::Tracked; *status = BoneTrackingStatus::Tracked;
} }
radius.0 = bone_data.radius; radius.0 = bone_data.radius;
*transform = transform transform.translation = bone_data.position;
.with_translation(root_transform.transform_point(bone_data.position)) transform.rotation = bone_data.orientation;
.with_rotation(root_transform.rotation * bone_data.orientation)
}); });
} }

View File

@@ -1,19 +1,64 @@
use bevy::{app::PluginGroupBuilder, prelude::*}; use bevy::prelude::*;
use openxr::FormFactor;
use self::{emulated::HandEmulationPlugin, hand_tracking::HandTrackingPlugin}; 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 common;
pub mod emulated; pub mod emulated;
pub mod hand_tracking; pub mod hand_tracking;
pub struct XrHandPlugins; pub struct HandPlugin;
impl PluginGroup for XrHandPlugins { impl Plugin for HandPlugin {
fn build(self) -> PluginGroupBuilder { fn build(&self, app: &mut App) {
PluginGroupBuilder::start::<Self>() app.add_systems(XrPreSetup, check_for_handtracking);
.add(HandTrackingPlugin) app.add_systems(XrSetup, spawn_hand_entities);
.add(HandEmulationPlugin) app.add_systems(XrCleanup, despawn_hand_entities);
.build() }
}
#[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);
} }
} }
@@ -54,21 +99,17 @@ pub enum HandBone {
} }
impl HandBone { impl HandBone {
pub fn is_finger(&self) -> bool { pub fn is_finger(&self) -> bool {
match &self { !matches!(self, HandBone::Wrist | HandBone::Palm)
HandBone::Wrist => false,
HandBone::Palm => false,
_ => true,
}
} }
pub fn is_metacarpal(&self) -> bool { pub fn is_metacarpal(&self) -> bool {
match &self { matches!(
HandBone::ThumbMetacarpal => true, self,
HandBone::IndexMetacarpal => true, HandBone::ThumbMetacarpal
HandBone::MiddleMetacarpal => true, | HandBone::IndexMetacarpal
HandBone::RingMetacarpal => true, | HandBone::MiddleMetacarpal
HandBone::LittleTip => true, | HandBone::RingMetacarpal
_ => false, | HandBone::LittleTip
} )
} }
pub const fn get_all_bones() -> [HandBone; 26] { pub const fn get_all_bones() -> [HandBone; 26] {
[ [

View File

@@ -1,6 +1,6 @@
use std::f32::consts::PI; use std::f32::consts::PI;
use bevy::log::info; use bevy::log::{info, warn};
use bevy::prelude::{ use bevy::prelude::{
Color, Component, Entity, Event, EventReader, EventWriter, Gizmos, GlobalTransform, Quat, Color, Component, Entity, Event, EventReader, EventWriter, Gizmos, GlobalTransform, Quat,
Query, Transform, Vec3, With, Without, Query, Transform, Vec3, With, Without,
@@ -93,9 +93,12 @@ pub fn draw_interaction_gizmos(
), ),
Without<XRInteractable>, Without<XRInteractable>,
>, >,
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>, tracking_root_query: Query<&mut Transform, With<OpenXRTrackingRoot>>,
) { ) {
let root = tracking_root_query.get_single().unwrap().0; 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() { for (global_transform, interactable_state) in interactable_query.iter() {
let transform = global_transform.compute_transform(); let transform = global_transform.compute_transform();
let color = match interactable_state { let color = match interactable_state {
@@ -137,7 +140,7 @@ pub fn draw_interaction_gizmos(
}; };
gizmos.ray( gizmos.ray(
root.translation + root.rotation.mul_vec3(aim.0.translation), root.translation + root.rotation.mul_vec3(aim.0.translation),
root.rotation.mul_vec3(aim.0.forward()), root.rotation.mul_vec3(*aim.0.forward()),
color, color,
); );
} }
@@ -229,7 +232,7 @@ pub fn interactions(
), ),
Without<XRInteractable>, Without<XRInteractable>,
>, >,
tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>, tracking_root_query: Query<&mut Transform, With<OpenXRTrackingRoot>>,
mut writer: EventWriter<InteractionEvent>, mut writer: EventWriter<InteractionEvent>,
) { ) {
for (xr_interactable_global_transform, interactable_entity) in interactable_query.iter() { for (xr_interactable_global_transform, interactable_entity) in interactable_query.iter() {
@@ -280,12 +283,12 @@ pub fn interactions(
let center = sphere_transform.translation; let center = sphere_transform.translation;
let radius: f32 = 0.1; let radius: f32 = 0.1;
//I hate this but the aim pose needs the root for now //I hate this but the aim pose needs the root for now
let root = tracking_root_query.get_single().unwrap().0; let root = tracking_root_query.get_single().unwrap();
match aim { match aim {
Some(aim) => { Some(aim) => {
let ray_origin = let ray_origin =
root.translation + root.rotation.mul_vec3(aim.0.translation); root.translation + root.rotation.mul_vec3(aim.0.translation);
let ray_dir = root.rotation.mul_vec3(aim.0.forward()); let ray_dir = root.rotation.mul_vec3(*aim.0.forward());
if ray_sphere_intersection( if ray_sphere_intersection(
center, center,

View File

@@ -10,124 +10,92 @@ pub mod trackers;
pub mod xr_camera; pub mod xr_camera;
use crate::resources::{XrInstance, XrSession}; use crate::resources::{XrInstance, XrSession};
use crate::xr_begin_frame; use crate::xr_init::{xr_only, XrCleanup, XrPostSetup, XrPreSetup, XrSetup};
use crate::xr_init::{xr_only, XrPostSetup, XrSetup, XrPreSetup};
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};
use crate::{locate_views, xr_wait_frame};
use bevy::app::{App, PostUpdate, Startup}; use bevy::app::{App, PostUpdate, Startup};
use bevy::ecs::entity::Entity; use bevy::ecs::entity::Entity;
use bevy::ecs::query::With; use bevy::ecs::query::With;
use bevy::ecs::system::Query; use bevy::ecs::system::Query;
use bevy::hierarchy::DespawnRecursiveExt;
use bevy::log::{info, warn}; use bevy::log::{info, warn};
use bevy::math::Vec2; use bevy::math::Vec2;
use bevy::prelude::{BuildChildren, Component, Deref, DerefMut, IntoSystemConfigs, Resource}; use bevy::prelude::{BuildChildren, Component, Deref, DerefMut, IntoSystemConfigs, Resource};
use bevy::prelude::{Commands, Plugin, PreUpdate, Quat, Res, SpatialBundle, Update, Vec3}; use bevy::prelude::{Commands, Plugin, PreUpdate, Quat, Res, SpatialBundle, Update, Vec3};
use bevy::render::camera::CameraProjectionPlugin; use bevy::render::camera::CameraProjectionPlugin;
use bevy::render::extract_component::ExtractComponentPlugin;
use bevy::render::view::{update_frusta, VisibilitySystems}; use bevy::render::view::{update_frusta, VisibilitySystems};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy::utils::HashMap; use bevy::utils::HashMap;
use openxr::Binding; use openxr::Binding;
use self::actions::{setup_oxr_actions, OpenXrActionsPlugin}; use self::actions::{setup_oxr_actions, XrActionsPlugin};
use self::oculus_touch::{post_action_setup_oculus_controller, ActionSets, init_subaction_path}; use self::oculus_touch::{
init_subaction_path, post_action_setup_oculus_controller, ActionSets, OculusController,
};
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,
}; };
use self::xr_camera::{/* GlobalTransformExtract, TransformExtract, */ XrCamera};
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct OpenXrInput { pub struct XrInputPlugin;
pub controller_type: XrControllerType,
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Component)] #[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Component)]
pub enum Hand { pub enum Hand {
Left, Left,
Right, Right,
} }
impl OpenXrInput { impl Plugin for XrInputPlugin {
pub fn new(controller_type: XrControllerType) -> Self {
Self { controller_type }
}
}
impl Plugin for OpenXrInput {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(CameraProjectionPlugin::<XRProjection>::default());
app.add_plugins(OpenXrActionsPlugin);
app.add_systems(XrPostSetup, post_action_setup_oculus_controller); app.add_systems(XrPostSetup, post_action_setup_oculus_controller);
match self.controller_type {
XrControllerType::OculusTouch => {
app.add_systems(XrSetup, setup_oculus_controller); app.add_systems(XrSetup, setup_oculus_controller);
} app.add_systems(XrCleanup, cleanup_oculus_controller);
}
//adopt any new trackers //adopt any new trackers
app.add_systems(PreUpdate, adopt_open_xr_trackers.run_if(xr_only())); app.add_systems(PreUpdate, adopt_open_xr_trackers.run_if(xr_only()));
app.add_systems(PreUpdate, action_set_system.run_if(xr_only())); // app.add_systems(PreUpdate, action_set_system.run_if(xr_only()));
app.add_systems(
PreUpdate,
xr_camera_head_sync.run_if(xr_only()).after(xr_begin_frame),
);
//update controller trackers //update controller trackers
app.add_systems(Update, update_open_xr_controllers.run_if(xr_only())); app.add_systems(Update, update_open_xr_controllers.run_if(xr_only()));
app.add_systems(
PostUpdate,
update_frusta::<XRProjection>
.after(TransformSystem::TransformPropagate)
.before(VisibilitySystems::UpdatePerspectiveFrusta),
);
app.add_systems(XrPreSetup, init_subaction_path); app.add_systems(XrPreSetup, init_subaction_path);
app.add_systems(XrSetup, setup_xr_cameras); app.add_systems(XrSetup, setup_xr_root);
app.add_systems(XrCleanup, cleanup_xr_root);
} }
} }
#[derive(Deref, DerefMut, Resource)] fn cleanup_oculus_controller(mut commands: Commands) {
pub struct InteractionProfileBindings(pub HashMap<&'static str, Vec<Binding<'static>>>); commands.remove_resource::<OculusController>();
fn setup_binding_recommendations(
mut commands: Commands,
instance: Res<XrInstance>,
bindings: Res<InteractionProfileBindings>,
) {
commands.remove_resource::<InteractionProfileBindings>();
} }
fn setup_xr_cameras( fn cleanup_xr_root(
mut commands: Commands, mut commands: Commands,
tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>, tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
) { ) {
//this needs to do the whole xr tracking volume not just cameras for e in &tracking_root_query {
//get the root? commands.entity(e).despawn_recursive();
}
let tracking_root = match tracking_root_query.get_single() { }
Ok(e) => e, fn setup_xr_root(
Err(_) => commands mut commands: Commands,
.spawn((SpatialBundle::default(), OpenXRTrackingRoot)) tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
.id(), ) {
}; if tracking_root_query.get_single().is_err() {
let right = commands info!("Creating XrTrackingRoot!");
.spawn((XrCameraBundle::new(Eye::Right), OpenXRRightEye)) commands.spawn((SpatialBundle::default(), OpenXRTrackingRoot));
.id(); }
let left = commands
.spawn((XrCameraBundle::new(Eye::Left), OpenXRLeftEye))
.id();
commands.entity(tracking_root).push_children(&[right, left]);
} }
pub fn action_set_system(action_sets: Res<ActionSets>, session: Res<XrSession>) { // pub fn action_set_system(action_sets: Res<ActionSets>, session: Res<XrSession>) {
let mut active_action_sets = vec![]; // let mut active_action_sets = vec![];
for i in &action_sets.0 { // for i in &action_sets.0 {
active_action_sets.push(openxr::ActiveActionSet::new(i)); // active_action_sets.push(openxr::ActiveActionSet::new(i));
} // }
//info!("action sets: {:#?}", action_sets.0.len()); // //info!("action sets: {:#?}", action_sets.0.len());
match session.sync_actions(&active_action_sets) { // if let Err(err) = session.sync_actions(&active_action_sets) {
Err(err) => { // warn!("{}", err);
warn!("{}", err); // }
} // }
_ => {}
}
}
pub trait Vec2Conv { pub trait Vec2Conv {
fn to_vec2(&self) -> Vec2; fn to_vec2(&self) -> Vec2;

View File

@@ -371,7 +371,7 @@ pub struct OculusController {
pub aim_space: Option<Handed<Space>>, pub aim_space: Option<Handed<Space>>,
} }
impl OculusController { impl OculusController {
pub fn new(mut action_sets: ResMut<SetupActionSets>) -> anyhow::Result<Self> { pub fn new(mut action_sets: ResMut<SetupActionSets>) -> eyre::Result<Self> {
let action_set = let action_set =
action_sets.add_action_set("oculus_input", "Oculus Touch Controller Input".into(), 0); action_sets.add_action_set("oculus_input", "Oculus Touch Controller Input".into(), 0);
action_set.new_action( action_set.new_action(

View File

@@ -59,28 +59,23 @@ impl Default for PrototypeLocomotionConfig {
pub fn proto_locomotion( pub fn proto_locomotion(
time: Res<Time>, time: Res<Time>,
mut tracking_root_query: Query<(&mut Transform, With<OpenXRTrackingRoot>)>, mut tracking_root_query: Query<&mut Transform, With<OpenXRTrackingRoot>>,
oculus_controller: Res<OculusController>, oculus_controller: Res<OculusController>,
frame_state: Res<XrFrameState>, frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>, xr_input: Res<XrInput>,
instance: Res<XrInstance>,
session: Res<XrSession>, session: Res<XrSession>,
views: ResMut<XrViews>, views: ResMut<XrViews>,
mut gizmos: Gizmos, mut gizmos: Gizmos,
config_option: Option<ResMut<PrototypeLocomotionConfig>>, config_option: Option<ResMut<PrototypeLocomotionConfig>>,
action_sets: Res<XrActionSets>, action_sets: Res<XrActionSets>,
) { ) {
match config_option { let mut config = match config_option {
Some(_) => (), Some(c) => c,
None => { None => {
info!("no locomotion config"); info!("no locomotion config");
return; return;
} }
} };
//i hate this but im too tired to think
let mut config = config_option.unwrap();
//lock frame
let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
let root = tracking_root_query.get_single_mut(); let root = tracking_root_query.get_single_mut();
@@ -88,12 +83,11 @@ pub fn proto_locomotion(
Ok(mut position) => { Ok(mut position) => {
//get the stick input and do some maths //get the stick input and do some maths
let stick = controller.thumbstick(Hand::Left); let stick = controller.thumbstick(Hand::Left);
let input = stick.x * position.0.right() + stick.y * position.0.forward(); let input = stick.x * *position.right() + stick.y * *position.forward();
let reference_quat; let reference_quat;
match config.locomotion_type { match config.locomotion_type {
LocomotionType::Head => { LocomotionType::Head => {
let v = views.lock().unwrap(); let views = views.first();
let views = v.get(0);
match views { match views {
Some(view) => { Some(view) => {
reference_quat = view.pose.orientation.to_quat(); reference_quat = view.pose.orientation.to_quat();
@@ -107,10 +101,9 @@ pub fn proto_locomotion(
} }
} }
let (yaw, _pitch, _roll) = reference_quat.to_euler(EulerRot::YXZ); let (yaw, _pitch, _roll) = reference_quat.to_euler(EulerRot::YXZ);
let reference_quat = Quat::from_axis_angle(position.0.up(), yaw); let reference_quat = Quat::from_axis_angle(*position.up(), yaw);
let locomotion_vec = reference_quat.mul_vec3(input); let locomotion_vec = reference_quat.mul_vec3(input);
position.0.translation += position.translation += locomotion_vec * config.locomotion_speed * time.delta_seconds();
locomotion_vec * config.locomotion_speed * time.delta_seconds();
//now time for rotation //now time for rotation
@@ -123,20 +116,19 @@ pub fn proto_locomotion(
return; return;
} }
let smoth_rot = Quat::from_axis_angle( let smoth_rot = Quat::from_axis_angle(
position.0.up(), *position.up(),
rot_input * config.smooth_rotation_speed * time.delta_seconds(), rot_input * config.smooth_rotation_speed * time.delta_seconds(),
); );
//apply rotation //apply rotation
let v = views.lock().unwrap(); let views = views.first();
let views = v.get(0);
match views { match views {
Some(view) => { Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3(); let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0; hmd_translation.y = 0.0;
let local = position.0.translation; let local = position.translation;
let global = position.0.rotation.mul_vec3(hmd_translation) + local; let global = position.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN); gizmos.circle(global, position.up(), 0.1, Color::GREEN);
position.0.rotate_around(global, smoth_rot); position.rotate_around(global, smoth_rot);
} }
None => return, None => return,
} }
@@ -157,18 +149,18 @@ pub fn proto_locomotion(
false => -1.0, false => -1.0,
}; };
let smoth_rot = let smoth_rot =
Quat::from_axis_angle(position.0.up(), config.snap_angle * dir); Quat::from_axis_angle(*position.up(), config.snap_angle * dir);
//apply rotation //apply rotation
let v = views.lock().unwrap(); let v = views;
let views = v.get(0); let views = v.first();
match views { match views {
Some(view) => { Some(view) => {
let mut hmd_translation = view.pose.position.to_vec3(); let mut hmd_translation = view.pose.position.to_vec3();
hmd_translation.y = 0.0; hmd_translation.y = 0.0;
let local = position.0.translation; let local = position.translation;
let global = position.0.rotation.mul_vec3(hmd_translation) + local; let global = position.rotation.mul_vec3(hmd_translation) + local;
gizmos.circle(global, position.0.up(), 0.1, Color::GREEN); gizmos.circle(global, position.up(), 0.1, Color::GREEN);
position.0.rotate_around(global, smoth_rot); position.rotate_around(global, smoth_rot);
} }
None => return, None => return,
} }

View File

@@ -1,4 +1,6 @@
use bevy::hierarchy::Parent;
use bevy::log::{debug, info}; use bevy::log::{debug, info};
use bevy::math::Quat;
use bevy::prelude::{ use bevy::prelude::{
Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, Without, Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, Without,
}; };
@@ -30,44 +32,45 @@ pub struct OpenXRController;
pub struct AimPose(pub Transform); pub struct AimPose(pub Transform);
pub fn adopt_open_xr_trackers( pub fn adopt_open_xr_trackers(
query: Query<Entity, Added<OpenXRTracker>>, query: Query<Entity, (With<OpenXRTracker>, Without<Parent>)>,
mut commands: Commands, mut commands: Commands,
tracking_root_query: Query<(Entity, With<OpenXRTrackingRoot>)>, tracking_root_query: Query<Entity, With<OpenXRTrackingRoot>>,
) { ) {
let root = tracking_root_query.get_single(); let root = tracking_root_query.get_single();
match root { match root {
Ok(thing) => { Ok(root) => {
// info!("root is"); // info!("root is");
for tracker in query.iter() { for tracker in query.iter() {
info!("we got a new tracker"); info!("we got a new tracker");
commands.entity(thing.0).add_child(tracker); commands.entity(root).add_child(tracker);
} }
} }
Err(_) => info!("root isnt spawned yet?"), 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( pub fn update_open_xr_controllers(
oculus_controller: Res<OculusController>, oculus_controller: Res<OculusController>,
mut left_controller_query: Query<( mut left_controller_query: Query<
&mut Transform, (&mut Transform, Option<&mut AimPose>),
Option<&mut AimPose>, (With<OpenXRLeftController>, Without<OpenXRRightController>),
With<OpenXRLeftController>, >,
Without<OpenXRRightController>, mut right_controller_query: Query<
)>, (&mut Transform, Option<&mut AimPose>),
mut right_controller_query: Query<( (With<OpenXRRightController>, Without<OpenXRLeftController>),
&mut Transform, >,
Option<&mut AimPose>,
With<OpenXRRightController>,
Without<OpenXRLeftController>,
)>,
frame_state: Res<XrFrameState>, frame_state: Res<XrFrameState>,
xr_input: Res<XrInput>, xr_input: Res<XrInput>,
session: Res<XrSession>, session: Res<XrSession>,
action_sets: Res<XrActionSets>, action_sets: Res<XrActionSets>,
) { ) {
//lock dat frame?
let frame_state = *frame_state.lock().unwrap();
//get controller //get controller
let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets); let controller = oculus_controller.get_ref(&session, &frame_state, &xr_input, &action_sets);
//get left controller //get left controller
@@ -82,7 +85,7 @@ pub fn update_open_xr_controllers(
Some(mut pose) => { Some(mut pose) => {
*pose = AimPose(Transform { *pose = AimPose(Transform {
translation: left_aim_space.0.pose.position.to_vec3(), translation: left_aim_space.0.pose.position.to_vec3(),
rotation: left_aim_space.0.pose.orientation.to_quat(), rotation: verify_quat(left_aim_space.0.pose.orientation.to_quat()),
scale: Vec3::splat(1.0), scale: Vec3::splat(1.0),
}); });
} }
@@ -100,7 +103,7 @@ pub fn update_open_xr_controllers(
let left_rotataion = left_controller_query.get_single_mut(); let left_rotataion = left_controller_query.get_single_mut();
match left_rotataion { match left_rotataion {
Ok(mut left_entity) => { Ok(mut left_entity) => {
left_entity.0.rotation = left_grip_space.0.pose.orientation.to_quat() left_entity.0.rotation = verify_quat(left_grip_space.0.pose.orientation.to_quat())
} }
Err(_) => (), Err(_) => (),
} }
@@ -115,7 +118,7 @@ pub fn update_open_xr_controllers(
Some(mut pose) => { Some(mut pose) => {
*pose = AimPose(Transform { *pose = AimPose(Transform {
translation: right_aim_space.0.pose.position.to_vec3(), translation: right_aim_space.0.pose.position.to_vec3(),
rotation: right_aim_space.0.pose.orientation.to_quat(), rotation: verify_quat(right_aim_space.0.pose.orientation.to_quat()),
scale: Vec3::splat(1.0), scale: Vec3::splat(1.0),
}); });
} }
@@ -133,7 +136,7 @@ pub fn update_open_xr_controllers(
let right_rotataion = right_controller_query.get_single_mut(); let right_rotataion = right_controller_query.get_single_mut();
match right_rotataion { match right_rotataion {
Ok(mut right_entity) => { Ok(mut right_entity) => {
right_entity.0.rotation = right_grip_space.0.pose.orientation.to_quat() right_entity.0.rotation = verify_quat(right_grip_space.0.pose.orientation.to_quat())
} }
Err(_) => (), Err(_) => (),
} }

View File

@@ -1,12 +1,94 @@
use crate::prelude::XrSystems;
use crate::xr_init::{xr_only, XrCleanup, XrSetup};
use crate::xr_input::{QuatConv, Vec3Conv}; use crate::xr_input::{QuatConv, Vec3Conv};
use crate::{LEFT_XR_TEXTURE_HANDLE, RIGHT_XR_TEXTURE_HANDLE}; 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::core_pipeline::tonemapping::{DebandDither, Tonemapping};
use bevy::ecs::system::lifetimeless::Read;
use bevy::math::Vec3A; use bevy::math::Vec3A;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::camera::{CameraProjection, CameraRenderGraph, RenderTarget}; use bevy::render::camera::{
CameraMainTextureUsages, CameraProjection, CameraProjectionPlugin, CameraRenderGraph,
RenderTarget,
};
use bevy::render::extract_component::{ExtractComponent, ExtractComponentPlugin};
use bevy::render::primitives::Frustum; use bevy::render::primitives::Frustum;
use bevy::render::view::{ColorGrading, VisibleEntities}; use bevy::render::view::{
update_frusta, ColorGrading, ExtractedView, VisibilitySystems, VisibleEntities,
};
use bevy::render::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem;
use openxr::Fovf; use openxr::Fovf;
use wgpu::TextureUsages;
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::UpdatePerspectiveFrusta),
);
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)] #[derive(Bundle)]
pub struct XrCamerasBundle { pub struct XrCamerasBundle {
@@ -40,13 +122,61 @@ pub struct XrCameraBundle {
pub tonemapping: Tonemapping, pub tonemapping: Tonemapping,
pub dither: DebandDither, pub dither: DebandDither,
pub color_grading: ColorGrading, pub color_grading: ColorGrading,
pub xr_camera_type: XrCameraType, pub main_texture_usages: CameraMainTextureUsages,
pub xr_camera_type: XrCamera,
pub root_transform: RootTransform,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Component)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Component, ExtractComponent)]
pub enum XrCameraType { pub struct XrCamera(Eye);
Xr(Eye),
Flatscreen, #[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)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Eye { pub enum Eye {
@@ -66,7 +196,7 @@ impl XrCameraBundle {
viewport: None, viewport: None,
..default() ..default()
}, },
camera_render_graph: CameraRenderGraph::new(bevy::core_pipeline::core_3d::graph::NAME), camera_render_graph: CameraRenderGraph::new(Core3d),
xr_projection: Default::default(), xr_projection: Default::default(),
visible_entities: Default::default(), visible_entities: Default::default(),
frustum: Default::default(), frustum: Default::default(),
@@ -76,12 +206,18 @@ impl XrCameraBundle {
tonemapping: Default::default(), tonemapping: Default::default(),
dither: DebandDither::Enabled, dither: DebandDither::Enabled,
color_grading: Default::default(), color_grading: Default::default(),
xr_camera_type: XrCameraType::Xr(eye), 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)] #[derive(Debug, Clone, Component, Reflect, ExtractComponent)]
#[reflect(Component, Default)] #[reflect(Component, Default)]
pub struct XRProjection { pub struct XRProjection {
pub near: f32, pub near: f32,
@@ -240,23 +376,35 @@ impl CameraProjection for XRProjection {
} }
pub fn xr_camera_head_sync( pub fn xr_camera_head_sync(
views: ResMut<crate::resources::XrViews>, views: Res<crate::resources::XrViews>,
mut query: Query<(&mut Transform, &XrCameraType, &mut XRProjection)>, mut query: Query<(&mut Transform, &XrCamera, &mut XRProjection)>,
) { ) {
let mut f = || -> Option<()> {
//TODO calculate HMD position //TODO calculate HMD position
for (mut transform, camera_type, mut xr_projection) in query.iter_mut() { for (mut transform, camera_type, mut xr_projection) in query.iter_mut() {
let view_idx = match camera_type { let view_idx = camera_type.0 as usize;
XrCameraType::Xr(eye) => *eye as usize, let view = match views.get(view_idx) {
XrCameraType::Flatscreen => return None, Some(views) => views,
None => continue,
}; };
let v = views.lock().unwrap();
let view = v.get(view_idx)?;
xr_projection.fov = view.fov; xr_projection.fov = view.fov;
transform.rotation = view.pose.orientation.to_quat(); transform.rotation = view.pose.orientation.to_quat();
transform.translation = view.pose.position.to_vec3(); transform.translation = view.pose.position.to_vec3();
} }
Some(()) }
};
let _ = f(); 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.transform = root.mul_transform(transform);
}
} }