use std::f32::consts::{FRAC_PI_2, PI}; use bevy::prelude::*; use bevy_cef::prelude::*; use bevy_mod_openxr::prelude::*; use bevy_mod_xr::session::XrSessionCreated; use bevy_pkv::{PersistentResourceAppExtensions, PkvStore}; use openxr::Path; use serde::{Deserialize, Serialize}; use crate::{ vrcontrollerplugin::{ LeftController, LeftControllerActions, RightController, RightControllerActions, }, vrplugin::{Headset, MainCamera, create_view_space}, }; #[derive(Component)] pub struct LookedAt; #[derive(Component)] pub struct Kneeboard; #[derive(Resource, Serialize, Deserialize)] pub struct KneeboardPosition { position: Vec3, rotation: Quat, } impl Default for KneeboardPosition { fn default() -> Self { Self { position: Vec3::new(0.15, -0.25, -0.40), rotation: Default::default(), } } } pub struct KneeboardPlugin; impl Plugin for KneeboardPlugin { fn build(&self, app: &mut App) { app.add_plugins(CefPlugin { command_line_config: CommandLineConfig { switches: ["--no-zygote", "--no-sandbox"].to_vec(), ..Default::default() }, ..Default::default() }); app.insert_resource(PkvStore::new("Avii", "Kneeboard")) .init_persistent_resource::(); app.add_systems(XrSessionCreated, spawn_kneeboard.after(create_view_space)); app.add_systems(Update, gaze.run_if(openxr_session_running)); app.add_systems(Update, move_keyboard.run_if(openxr_session_running)); app.add_systems(Update, position_kneeboard.after(move_keyboard)); app.add_systems(Update, sync_camera_with_kneeboard.after(position_kneeboard)); app.add_systems(Update, scroll_handler.run_if(openxr_session_running)); } } fn sync_camera_with_kneeboard( kneeboard: Query<&Transform, With>, mut cameras: Query<&mut Transform, (With, Without)>, ) { let Ok(kneeboard) = kneeboard.single() else { return; }; let Ok(mut camera) = cameras.single_mut() else { return; }; let mut transform = *kneeboard; transform.translation -= kneeboard.back().normalize() * 0.35; transform.rotation *= Quat::from_axis_angle(Dir3::X.normalize(), PI) * Quat::from_axis_angle(Dir3::Z.normalize(), PI); *camera = transform; } fn scroll_handler( session: Res, right: Res, browsers: NonSend, webviews: Query>, ) { let Ok(thumbstick) = right.thumbstick.state(&session, Path::NULL) else { return; }; let delta = Vec2::new(thumbstick.current_state.x, thumbstick.current_state.y) * 12.0; for webview in webviews.iter() { browsers.send_mouse_wheel(&webview, Vec2::ZERO, delta); } } #[allow(clippy::type_complexity)] fn position_kneeboard( mut transform: Query<(&mut Transform, Option<&LookedAt>), (With, Without)>, kneeboard: Res, head: Query<&Transform, (With, Without)>, ) { let Ok((mut transform, looked_at)) = transform.single_mut() else { return; }; let head = head.single().expect("a head to exist"); transform.translation = kneeboard.position; transform.rotation = kneeboard.rotation; if looked_at.is_some() { transform.translation = head.translation; transform.rotation = head.rotation; } } fn move_keyboard( session: Res, left: Res, right: Res, left_transform: Query<&Transform, (With, Without)>, right_transform: Query<&Transform, (With, Without)>, mut kneeboard: ResMut, ) { let rot_offset: Quat = Quat::from_axis_angle(Vec3::new(1.0, 0.0, 0.0), FRAC_PI_2) * Quat::from_axis_angle(Vec3::new(0.0, 0.0, 1.0), PI); if let Ok(trigger_state) = left.squeeze_click.state(&session, Path::NULL) && trigger_state.current_state { let Ok(transform) = left_transform.single() else { return; }; let pos_offset = transform.up().normalize() * -0.1; kneeboard.position = transform.translation + pos_offset; kneeboard.rotation = transform.rotation * rot_offset; } if let Ok(trigger_state) = right.squeeze_click.state(&session, Path::NULL) && trigger_state.current_state { let Ok(transform) = right_transform.single() else { return; }; let pos_offset = transform.up().normalize() * -0.1; kneeboard.position = transform.translation + pos_offset; kneeboard.rotation = transform.rotation * rot_offset; } } fn spawn_kneeboard( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, position: Res, ) { commands.spawn(( WebviewSource::new("http://localhost:7878/"), // i want this to become dynamic.... from ui or something WebviewSize(Vec2::new(210.0 * 3.5, 279.0 * 3.5)), // this can also be dynamic... kinda like rotating phone Mesh3d(meshes.add(Cuboid::new(0.210, 0.279, 0.01))), MeshMaterial3d(materials.add(WebviewExtendStandardMaterial { base: StandardMaterial { unlit: true, ..default() }, ..Default::default() })), Transform::from_translation(position.position).rotate(position.rotation), Kneeboard, )); } fn gaze( mut commands: Commands, position: Res, kneeboard: Query>, head: Query<&Transform, With>, ) { let head = head.single().expect("a head to exist"); let kneeboard = kneeboard.single().expect("a kneeboard to exist"); let facing = head.forward().normalize(); let to_target = (position.position - head.translation).normalize(); let dot = facing.dot(to_target); let cos_fov_angle: f32 = (35.0f32 / 2.0f32).to_radians().cos(); commands.entity(kneeboard).remove::(); if dot < cos_fov_angle { commands.entity(kneeboard).insert(LookedAt); } }