Files
bevy_kneeboard/src/kneeboardplugin.rs

198 lines
6.2 KiB
Rust

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::<KneeboardPosition>();
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<Kneeboard>>,
mut cameras: Query<&mut Transform, (With<MainCamera>, Without<Kneeboard>)>,
) {
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<OxrSession>,
right: Res<RightControllerActions>,
browsers: NonSend<Browsers>,
webviews: Query<Entity, With<WebviewSource>>,
) {
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<Kneeboard>, Without<Headset>)>,
kneeboard: Res<KneeboardPosition>,
head: Query<&Transform, (With<Headset>, Without<Kneeboard>)>,
) {
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<OxrSession>,
left: Res<LeftControllerActions>,
right: Res<RightControllerActions>,
left_transform: Query<&Transform, (With<LeftController>, Without<RightController>)>,
right_transform: Query<&Transform, (With<RightController>, Without<LeftController>)>,
mut kneeboard: ResMut<KneeboardPosition>,
) {
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<Assets<Mesh>>,
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
position: Res<KneeboardPosition>,
) {
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<KneeboardPosition>,
kneeboard: Query<Entity, With<Kneeboard>>,
head: Query<&Transform, With<Headset>>,
) {
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::<LookedAt>();
if dot < cos_fov_angle {
commands.entity(kneeboard).insert(LookedAt);
}
}