Initial Commit
This commit is contained in:
197
src/kneeboardplugin.rs
Normal file
197
src/kneeboardplugin.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use avian3d::{math::FRAC_PI_2, prelude::*};
|
||||
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 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(PhysicsPlugins::default());
|
||||
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) * 8.0;
|
||||
|
||||
for webview in webviews.iter() {
|
||||
browsers.send_mouse_wheel(&webview, Vec2::ZERO, delta);
|
||||
}
|
||||
}
|
||||
|
||||
fn position_kneeboard(
|
||||
mut transform: Query<&mut Transform, With<Kneeboard>>,
|
||||
kneeboard: Res<KneeboardPosition>,
|
||||
) {
|
||||
let Ok(mut transform) = transform.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
transform.translation = kneeboard.position;
|
||||
transform.rotation = kneeboard.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((
|
||||
RigidBody::Static,
|
||||
WebviewSource::new("http://localhost:7878/"),
|
||||
WebviewSize(Vec2::new(210.0 * 3.5, 279.0 * 3.5)),
|
||||
Collider::cuboid(0.210, 0.279, 0.01),
|
||||
CollidingEntities::default(),
|
||||
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(
|
||||
material_handles: Query<&MeshMaterial3d<WebviewExtendStandardMaterial>, With<Kneeboard>>,
|
||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||
position: Res<KneeboardPosition>,
|
||||
head_query: Query<&Transform, With<Headset>>,
|
||||
) {
|
||||
let head = head_query.single().expect("a head 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();
|
||||
|
||||
let mut alpha = 1.0;
|
||||
if dot < cos_fov_angle {
|
||||
alpha = 0.0;
|
||||
}
|
||||
|
||||
for material_handle in material_handles.iter() {
|
||||
if let Some(material) = materials.get_mut(material_handle)
|
||||
&& let Color::LinearRgba(ref mut rgba) = material.base.base_color
|
||||
{
|
||||
rgba.alpha = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user