diff --git a/examples/xr.rs b/examples/xr.rs index 36e835c..e8252f4 100644 --- a/examples/xr.rs +++ b/examples/xr.rs @@ -5,13 +5,13 @@ use bevy_openxr::input::XrInput; use bevy_openxr::resources::{XrFrameState, XrInstance, XrSession}; use bevy_openxr::xr_input::debug_gizmos::OpenXrDebugRenderer; use bevy_openxr::xr_input::interactions::{ - draw_interaction_gizmos, hover_interaction, XRDirectInteractor, XRInteractable, - XRInteractableState, XRInteractorState, + draw_interaction_gizmos, direct_interaction, XRDirectInteractor, XRInteractable, + XRInteractableState, XRInteractorState, XRRayInteractor, ray_interaction, }; use bevy_openxr::xr_input::oculus_touch::OculusController; use bevy_openxr::xr_input::prototype_locomotion::{proto_locomotion, PrototypeLocomotionConfig}; use bevy_openxr::xr_input::trackers::{ - OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker, + OpenXRController, OpenXRLeftController, OpenXRRightController, OpenXRTracker, AimPose, }; use bevy_openxr::xr_input::Hand; use bevy_openxr::DefaultXrPlugins; @@ -30,7 +30,8 @@ fn main() { .add_systems(Startup, spawn_controllers_example) .insert_resource(PrototypeLocomotionConfig::default()) .add_systems(Update, draw_interaction_gizmos) - .add_systems(Update, hover_interaction) + .add_systems(Update, direct_interaction) + .add_systems(Update, ray_interaction) .add_systems(Update, prototype_interaction_input) .run(); } @@ -95,6 +96,8 @@ fn spawn_controllers_example(mut commands: Commands) { OpenXRTracker, SpatialBundle::default(), XRDirectInteractor, + XRRayInteractor, + AimPose(Transform::default()), XRInteractorState::default(), )); //right hand diff --git a/src/xr_input/interactions.rs b/src/xr_input/interactions.rs index 2fa5c35..a36d832 100644 --- a/src/xr_input/interactions.rs +++ b/src/xr_input/interactions.rs @@ -1,11 +1,17 @@ use std::f32::consts::PI; use bevy::prelude::{ - info, Color, Component, Gizmos, GlobalTransform, Quat, Query, Vec3, With, Without, + info, Color, Component, Gizmos, GlobalTransform, Quat, Query, Transform, Vec3, With, Without, }; +use super::trackers::{AimPose, OpenXRTrackingRoot}; + #[derive(Component)] pub struct XRDirectInteractor; + +#[derive(Component)] +pub struct XRRayInteractor; + #[derive(Component)] pub enum XRInteractableState { Idle, @@ -40,10 +46,18 @@ pub fn draw_interaction_gizmos( (With, Without), >, interactor_query: Query< - (&GlobalTransform, &XRInteractorState), - (With, Without), + ( + &GlobalTransform, + &XRInteractorState, + Option<&XRDirectInteractor>, + Option<&XRRayInteractor>, + Option<&AimPose>, + ), + (Without), >, + tracking_root_query: Query<(&mut Transform, With)>, ) { + let root = tracking_root_query.get_single().unwrap().0; for (global_transform, interactable_state) in interactable_query.iter() { let transform = global_transform.compute_transform(); let color = match interactable_state { @@ -54,25 +68,49 @@ pub fn draw_interaction_gizmos( gizmos.sphere(transform.translation, transform.rotation, 0.1, color); } - for (interactor_global_transform, interactor_state) in interactor_query.iter() { - let mut transform = interactor_global_transform.compute_transform(); - transform.scale = Vec3::splat(0.1); - let quat = Quat::from_euler( - bevy::prelude::EulerRot::XYZ, - 45.0 * (PI / 180.0), - 0.0, - 45.0 * (PI / 180.0), - ); - transform.rotation = quat; - let color = match interactor_state { - XRInteractorState::Idle => Color::BLUE, - XRInteractorState::Selecting => Color::PURPLE, - }; - gizmos.cuboid(transform, color); + for (interactor_global_transform, interactor_state, direct, ray, aim) in interactor_query.iter() + { + let transform = interactor_global_transform.compute_transform(); + match direct { + Some(_) => { + let mut local = transform.clone(); + local.scale = Vec3::splat(0.1); + let quat = Quat::from_euler( + bevy::prelude::EulerRot::XYZ, + 45.0 * (PI / 180.0), + 0.0, + 45.0 * (PI / 180.0), + ); + local.rotation = quat; + let color = match interactor_state { + XRInteractorState::Idle => Color::BLUE, + XRInteractorState::Selecting => Color::PURPLE, + }; + gizmos.cuboid(local, color); + } + None => (), + } + match ray { + Some(_) => match aim { + Some(aim) => { + let color = match interactor_state { + XRInteractorState::Idle => Color::BLUE, + XRInteractorState::Selecting => Color::PURPLE, + }; + gizmos.ray( + root.translation + root.rotation.mul_vec3(aim.0.translation), + root.rotation.mul_vec3(aim.0.forward()), + color, + ); + } + None => todo!(), + }, + None => (), + } } } -pub fn hover_interaction( +pub fn direct_interaction( mut interactable_query: Query< (&GlobalTransform, &mut XRInteractableState), (With, Without), @@ -82,9 +120,7 @@ pub fn hover_interaction( (With, Without), >, ) { - for (xr_interactable_global_transform, mut state) in - interactable_query.iter_mut() - { + for (xr_interactable_global_transform, mut state) in interactable_query.iter_mut() { let mut hovered = false; let mut selected = false; for (interactor_global_transform, interactor_state) in interactor_query.iter() { @@ -100,7 +136,6 @@ pub fn hover_interaction( ) < (size * size) * 2.0 { - //check for selections first match interactor_state { XRInteractorState::Idle => hovered = true, @@ -121,3 +156,79 @@ pub fn hover_interaction( } } } + +pub fn ray_interaction( + mut interactable_query: Query< + (&GlobalTransform, &mut XRInteractableState), + (With, Without), + >, + interactor_query: Query< + (&GlobalTransform, &XRInteractorState, Option<&AimPose>), + (With, Without), + >, + tracking_root_query: Query<(&mut Transform, With)>, +) { + for (xr_interactable_global_transform, mut state) in interactable_query.iter_mut() { + let mut hovered = false; + let mut selected = false; + for (interactor_global_transform, interactor_state, aim) in interactor_query.iter() { + //check for ray-sphere intersection + let sphere_transform = xr_interactable_global_transform.compute_transform(); + let center = sphere_transform.translation; + let radius: f32 = 0.1; + //I hate this but the aim pose needs the root for now + let root = tracking_root_query.get_single().unwrap().0; + match aim { + Some(aim) => { + let ray_origin = root.translation + root.rotation.mul_vec3(aim.0.translation); + let ray_dir = root.rotation.mul_vec3(aim.0.forward()); + + if ray_sphere_intersection( + center, + radius, + ray_origin, + ray_dir.normalize_or_zero(), + ) { + //check for selections first + match interactor_state { + XRInteractorState::Idle => hovered = true, + XRInteractorState::Selecting => { + selected = true; + } + } + } + } + None => info!("no aim pose"), + } + } + //check what we found + //also i dont like this + if selected { + *state = XRInteractableState::Select; + } else if hovered { + *state = XRInteractableState::Hover; + } else { + *state = XRInteractableState::Idle; + } + } +} + +fn ray_sphere_intersection(center: Vec3, radius: f32, ray_origin: Vec3, ray_dir: Vec3) -> bool { + let l = center - ray_origin; + let adj = l.dot(ray_dir); + let d2 = l.dot(l) - (adj * adj); + let radius2 = radius * radius; + if d2 > radius2 { + return false; + } + let thc = (radius2 - d2).sqrt(); + let t0 = adj - thc; + let t1 = adj + thc; + + if t0 < 0.0 && t1 < 0.0 { + return false; + } + + // let distance = if t0 < t1 { t0 } else { t1 }; + return true; +} diff --git a/src/xr_input/trackers.rs b/src/xr_input/trackers.rs index f33e6ee..cbd4697 100644 --- a/src/xr_input/trackers.rs +++ b/src/xr_input/trackers.rs @@ -1,8 +1,14 @@ -use bevy::prelude::{Added, BuildChildren, Commands, Entity, Query, With, Res, Transform, Without, Component, info}; +use bevy::prelude::{ + info, Added, BuildChildren, Commands, Component, Entity, Query, Res, Transform, Vec3, With, + Without, +}; -use crate::{resources::{XrFrameState, XrInstance, XrSession}, input::XrInput}; +use crate::{ + input::XrInput, + resources::{XrFrameState, XrInstance, XrSession}, +}; -use super::{oculus_touch::OculusController, Hand, Vec3Conv, QuatConv}; +use super::{oculus_touch::OculusController, Hand, QuatConv, Vec3Conv}; #[derive(Component)] pub struct OpenXRTrackingRoot; @@ -20,6 +26,8 @@ pub struct OpenXRLeftController; pub struct OpenXRRightController; #[derive(Component)] pub struct OpenXRController; +#[derive(Component)] +pub struct AimPose(pub Transform); pub fn adopt_open_xr_trackers( query: Query<(Entity), Added>, @@ -43,11 +51,13 @@ pub fn update_open_xr_controllers( oculus_controller: Res, mut left_controller_query: Query<( &mut Transform, + Option<&mut AimPose>, With, Without, )>, mut right_controller_query: Query<( &mut Transform, + Option<&mut AimPose>, With, Without, )>, @@ -61,8 +71,20 @@ pub fn update_open_xr_controllers( //get controller let controller = oculus_controller.get_ref(&instance, &session, &frame_state, &xr_input); //get left controller - let left = controller.grip_space(Hand::Left); - let left_postion = left.0.pose.position.to_vec3(); + let left_grip_space = controller.grip_space(Hand::Left); + let left_aim_space = controller.aim_space(Hand::Left); + let left_postion = left_grip_space.0.pose.position.to_vec3(); + let aim_pose = left_controller_query.get_single_mut().unwrap().1; + match aim_pose { + Some(mut pose) => { + *pose = AimPose(Transform { + translation: left_aim_space.0.pose.position.to_vec3(), + rotation: left_aim_space.0.pose.orientation.to_quat(), + scale: Vec3::splat(1.0), + }); + } + None => (), + } left_controller_query .get_single_mut() @@ -70,7 +92,8 @@ pub fn update_open_xr_controllers( .0 .translation = left_postion; - left_controller_query.get_single_mut().unwrap().0.rotation = left.0.pose.orientation.to_quat(); + left_controller_query.get_single_mut().unwrap().0.rotation = + left_grip_space.0.pose.orientation.to_quat(); //get right controller let right = controller.grip_space(Hand::Right); let right_postion = right.0.pose.position.to_vec3(); @@ -84,4 +107,3 @@ pub fn update_open_xr_controllers( right_controller_query.get_single_mut().unwrap().0.rotation = right.0.pose.orientation.to_quat(); } -