diff --git a/Cargo.lock b/Cargo.lock index 219646a..651a34f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -1120,7 +1120,6 @@ checksum = "7ef8e4b7e61dfe7719bb03c884dc270cd46a82efb40f93e9933b990c5c190c59" [[package]] name = "bevy_mod_openxr" version = "0.5.0" -source = "git+https://git.avii.nl/Avii/bevy_oxr.git#7936b53a024b993c53fc7427b5b290bf210978a9" dependencies = [ "android_system_properties", "ash", @@ -1145,7 +1144,6 @@ dependencies = [ [[package]] name = "bevy_mod_xr" version = "0.5.0" -source = "git+https://git.avii.nl/Avii/bevy_oxr.git#7936b53a024b993c53fc7427b5b290bf210978a9" dependencies = [ "bevy_app", "bevy_camera", @@ -2506,9 +2504,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -2536,6 +2534,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "directories" version = "6.0.0" @@ -2545,6 +2549,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -3186,7 +3199,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows 0.58.0", + "windows 0.54.0", ] [[package]] @@ -3656,9 +3669,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" dependencies = [ "once_cell", "wasm-bindgen", @@ -3690,9 +3703,12 @@ dependencies = [ "bevy_mod_openxr", "bevy_mod_xr", "bevy_pkv", + "crossbeam-channel", "openxr", + "otd-ipc", "rdev", "serde", + "triple_buffer", ] [[package]] @@ -4467,6 +4483,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "otd-ipc" +version = "0.1.0" +dependencies = [ + "bytes", + "dirs", + "serde", + "serde_bytes", + "slug", +] + [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -5025,7 +5052,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5157,6 +5184,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -5252,6 +5289,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -5392,9 +5439,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -5720,6 +5767,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "triple_buffer" +version = "8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420466259f9fa5decc654c490b9ab538400e5420df8237f84ecbe20368bcf72b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -5941,9 +5997,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" dependencies = [ "cfg-if", "once_cell", @@ -5954,9 +6010,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" dependencies = [ "cfg-if", "futures-util", @@ -5968,9 +6024,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5978,9 +6034,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" dependencies = [ "bumpalo", "proc-macro2", @@ -5991,9 +6047,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" dependencies = [ "unicode-ident", ] @@ -6142,9 +6198,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" dependencies = [ "js-sys", "wasm-bindgen", @@ -6654,15 +6710,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index d03f949..9d1f015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,17 @@ license = "MIT/Apache-2.0" [dependencies] bevy = { version = "0.18", features = ["debug"] } bevy_cef = { path = "../bevy_cef" } -bevy_mod_xr = { git = "https://git.avii.nl/Avii/bevy_oxr.git", version = "0.5.0" } -bevy_mod_openxr = { git = "https://git.avii.nl/Avii/bevy_oxr.git", version = "0.5.0" } +# bevy_mod_xr = { git = "https://git.avii.nl/Avii/bevy_oxr.git", version = "0.5.0" } +# bevy_mod_openxr = { git = "https://git.avii.nl/Avii/bevy_oxr.git", version = "0.5.0" } + +bevy_mod_xr = { path = "../bevy_oxr/crates/bevy_xr" } +bevy_mod_openxr = { path = "../bevy_oxr/crates/bevy_openxr" } + bevy_pkv = "0.15.0" # bevy_xr_utils.workspace = true openxr = "0.21.1" rdev = { version = "0.5.3", features = ["unstable_grab"] } serde = { version = "1.0.228", features = ["derive"] } +otd-ipc = { path = "../../otd-ipc-client-rs" } +crossbeam-channel = "0.5.15" +triple_buffer = "8.1.1" diff --git a/src/kneeboardplugin.rs b/src/kneeboardplugin.rs index 76cbae3..fb26ce1 100644 --- a/src/kneeboardplugin.rs +++ b/src/kneeboardplugin.rs @@ -9,6 +9,7 @@ use openxr::Path; use serde::{Deserialize, Serialize}; use crate::{ + MyProcGenImage, vrcontrollerplugin::{ LeftController, LeftControllerActions, RightController, RightControllerActions, }, @@ -158,10 +159,12 @@ fn spawn_kneeboard( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + image_handle: Res, position: Res, ) { commands.spawn(( WebviewSource::new("http://localhost:7878/"), // i want this to become dynamic.... from ui or something + // WebviewSource::new("https://public.avii.nl/test.html"), // 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 { @@ -169,7 +172,10 @@ fn spawn_kneeboard( unlit: true, ..default() }, - ..Default::default() + extension: WebviewMaterial { + surface: None, + overlay: Some(image_handle.0.clone()), + }, })), Transform::from_translation(position.position).rotate(position.rotation), Kneeboard, diff --git a/src/main.rs b/src/main.rs index 947de3d..02e42eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,24 @@ //! A simple 3D scene with light shining over a cube sitting on a plane. mod kneeboardplugin; +mod otdipcplugin; mod vrcontrollerplugin; mod vrplugin; -use bevy_cef::prelude::{Browsers, MeshAabb, WebviewSize, WebviewSource}; -use bevy_mod_openxr::openxr_session_running; -use bevy_mod_xr::camera::XrCamera; +use bevy_cef::prelude::WebviewExtendStandardMaterial; use vrplugin::VrPlugin; -use bevy::prelude::*; +use bevy::{ + asset::RenderAssetUsages, + color::palettes::css, + prelude::*, + render::render_resource::{Extent3d, TextureDimension, TextureFormat}, +}; +use crate::otdipcplugin::OtdIpcPlugin; use crate::{ - kneeboardplugin::{Kneeboard, KneeboardPlugin, LookedAt}, + kneeboardplugin::KneeboardPlugin, + otdipcplugin::{PenButtons, PenPosition, PenPressure}, vrcontrollerplugin::VrControllersPlugin, }; @@ -20,73 +26,421 @@ fn main() { App::new() .add_plugins((VrPlugin, MeshPickingPlugin)) .add_plugins(VrControllersPlugin) + .add_plugins(OtdIpcPlugin) .add_plugins(KneeboardPlugin) .insert_resource(ClearColor(Color::NONE)) - .add_systems(Update, head_pointer.run_if(openxr_session_running)) + .insert_resource(LastPenPos(None)) + .insert_resource(PenSize(5.0)) + .add_systems(Startup, setup) + .add_systems(Update, (cursor, draw, plot).chain()) .run(); } -#[allow(clippy::type_complexity)] -fn head_pointer( - browsers: NonSend, - webviews: Query<(Entity, &WebviewSize), With>, - headset: Query<&GlobalTransform, With>, - kneeboard: Query<(&GlobalTransform, Option<&LookedAt>), With>, - aabb: MeshAabb, -) { - let Ok((webview, size)) = webviews.single() else { - return; - }; +#[derive(Resource)] +struct MyProcGenImage(Handle); - let tex_size = size.0; +#[derive(Resource)] +struct PenSize(f32); - if let Some(gt) = headset.into_iter().next() { - let Ok((plane_tf, looked_at)) = kneeboard.single() else { - return; - }; +const IMAGE_WIDTH: u32 = (210.0 * 3.5) as u32; +const IMAGE_HEIGHT: u32 = (279.0 * 3.5) as u32; - if looked_at.is_some() { - // this is inverted for some reason wtf - return; - } +fn setup(mut commands: Commands, mut images: ResMut>) { + let image = Image::new_fill( + Extent3d { + width: IMAGE_WIDTH, + height: IMAGE_HEIGHT, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + // Initialize it with a beige color + &(Srgba::new(1.0, 1.0, 1.0, 0.0).to_u8_array()), + // Use the same encoding as the color we set + TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD, + ); - let (min, max) = aabb.calculate_local(webview); - let plane_size = Vec2::new(max.x - min.x, max.y - min.y); + let handle = images.add(image); + commands.insert_resource(MyProcGenImage(handle)); + commands.insert_resource(CursorBuffer( + [0; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4], + )); + commands.insert_resource(DrawingBuffer( + [0; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4], + )); +} - let ray = Ray3d::new(gt.translation(), gt.forward()); +#[derive(Resource)] +struct CursorBuffer([u8; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4]); - let n = plane_tf.forward().as_vec3(); - let Some(t) = ray.intersect_plane( - plane_tf.translation(), - InfinitePlane3d::new(plane_tf.forward()), - ) else { - return; - }; - let hit_world = ray.origin + ray.direction * t; - let local_hit = plane_tf.affine().inverse().transform_point(hit_world); - let local_normal = plane_tf.affine().inverse().transform_vector3(n).normalize(); - let abs_normal = local_normal.abs(); - let (u_coord, v_coord) = if abs_normal.z > abs_normal.x && abs_normal.z > abs_normal.y { - (local_hit.x, local_hit.y) - } else if abs_normal.y > abs_normal.x { - (local_hit.x, local_hit.z) - } else { - (local_hit.y, local_hit.z) - }; +impl Drawing for CursorBuffer { + fn set_color_at(&mut self, x: u32, y: u32, color: Color) { + let index = ((x + (IMAGE_WIDTH * y)) * 4) as usize; + let srgba = Srgba::from(color); - let w = plane_size.x; - let h = plane_size.y; - let u = (u_coord + w * 0.5) / w; - let v = (v_coord + h * 0.5) / h; - if !(0.0..=1.0).contains(&u) || !(0.0..=1.0).contains(&v) { - // outside plane bounds - return; - } - let px = (1.0 - u) * tex_size.x; - let py = (1.0 - v) * tex_size.y; + self.0[index] = (srgba.red * u8::MAX as f32) as u8; + self.0[index + 1] = (srgba.green * u8::MAX as f32) as u8; + self.0[index + 2] = (srgba.blue * u8::MAX as f32) as u8; + self.0[index + 3] = (srgba.alpha * u8::MAX as f32) as u8; + } - let pos = Vec2::new(px, py); - - browsers.send_mouse_move(&webview, &[], pos, false); + fn clear(&mut self) { + self.0.fill(0); } } + +#[derive(Resource)] +struct DrawingBuffer([u8; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4]); + +#[derive(Resource)] +pub struct LastPenPos(pub Option<(u32, u32)>); + +impl Drawing for DrawingBuffer { + fn set_color_at(&mut self, x: u32, y: u32, color: Color) { + let index = ((x + (IMAGE_WIDTH * y)) * 4) as usize; + let srgba = Srgba::from(color); + + self.0[index] = (srgba.red * u8::MAX as f32) as u8; + self.0[index + 1] = (srgba.green * u8::MAX as f32) as u8; + self.0[index + 2] = (srgba.blue * u8::MAX as f32) as u8; + self.0[index + 3] = (srgba.alpha * u8::MAX as f32) as u8; + } + + fn clear(&mut self) { + self.0.fill(0); + } +} + +fn cursor( + mut buffer: ResMut, + mut pen_size: ResMut, + mut pen_buttons: MessageReader, + mut pen_position: MessageReader, + mut pen_pressure: MessageReader, +) { + let mut size: f32 = 4.0; + + for penpres in pen_pressure.read() { + size += penpres.pressure * 2.; + } + + if let Some(buttons) = pen_buttons.read().next() + && (buttons.state & 2 == 2) + { + size = 40.; + }; + + pen_size.0 = size; + + let color = css::BLACK.into(); + + for penpos in pen_position.read() { + let x = (penpos.position.x * (IMAGE_WIDTH as f32)) as i32; + let y = (penpos.position.y * (IMAGE_HEIGHT as f32)) as i32; + + buffer.clear(); + draw_circle(&mut buffer, x, y, (size) as i32, 3.0, color); + } +} + +fn draw( + pen_size: Res, + mut lastloc: ResMut, + mut buffer: ResMut, + mut pen_buttons: MessageReader, + mut pen_position: MessageReader, +) { + let mut color = css::TURQUOISE.into(); + let mut is_down = false; + + if let Some(buttons) = pen_buttons.read().next() { + is_down = buttons.state & 1 == 1; + if buttons.state >= 2 { + color = Color::linear_rgba(1.0, 1.0, 1.0, 0.0); + } + }; + + for penpos in pen_position.read() { + let x = (penpos.position.x * (IMAGE_WIDTH as f32)) as u32; + let y = (penpos.position.y * (IMAGE_HEIGHT as f32)) as u32; + + let Some(ll) = lastloc.0 else { + lastloc.0 = Some((x, y)); + return; + }; + + if is_down { + let c = if color.alpha() == 0.0 { 2.0 } else { 1.0 }; + draw_line(&mut buffer, ll.0, ll.1, x, y, pen_size.0 * c, color); + } + + lastloc.0 = Some((x, y)); + } +} + +trait Drawing { + fn set_color_at(&mut self, px: u32, py: u32, color: Color); + fn clear(&mut self); +} + +fn draw_line( + buffer: &mut ResMut, + x1: u32, + y1: u32, + x2: u32, + y2: u32, + thickness: f32, + color: Color, +) { + let (mut x0, mut y0) = (x1 as i32, y1 as i32); + let (x1, y1) = (x2 as i32, y2 as i32); + + let dx = (x1 - x0).abs(); + let dy = -(y1 - y0).abs(); + let sx = if x0 < x1 { 1 } else { -1 }; + let sy = if y0 < y1 { 1 } else { -1 }; + let mut err = dx + dy; + + let half_t = (thickness / 2.0).ceil() as i32; + + loop { + draw_thick_point(buffer, x0, y0, half_t, color); + + if x0 == x1 && y0 == y1 { + break; + } + + let e2 = 2 * err; + if e2 >= dy { + err += dy; + x0 += sx; + } + if e2 <= dx { + err += dx; + y0 += sy; + } + } +} + +// Draw a small square brush (fast, branch-free inner loop) +fn draw_thick_point( + buffer: &mut ResMut, + cx: i32, + cy: i32, + radius: i32, + color: Color, +) { + draw_filled_circle(buffer, cx, cy, radius, color); +} + +fn draw_filled_circle( + buffer: &mut ResMut, + cx: i32, + cy: i32, + radius: i32, + color: Color, +) { + let r2 = radius * radius; + + for dy in -radius..=radius { + let y = cy + dy; + if y < 0 { + continue; + } + + let dx_limit = ((r2 - dy * dy) as f32).sqrt() as i32; + + let start_x = cx - dx_limit; + let end_x = cx + dx_limit; + + for x in start_x..=end_x { + if x >= 0 { + buffer.set_color_at(x as u32, y as u32, color); + } + } + } +} + +fn draw_circle( + buffer: &mut ResMut, + cx: i32, + cy: i32, + radius: i32, + thickness: f32, + color: Color, +) { + if radius <= 0 { + return; + } + + let half_t = (thickness / 2.0).max(0.5); + let outer_r = radius as f32 + half_t; + let inner_r = (radius as f32 - half_t).max(0.0); + + let outer_r2 = (outer_r * outer_r) as i32; + let inner_r2 = (inner_r * inner_r) as i32; + + let max_r = outer_r.ceil() as i32; + + for dy in -max_r..=max_r { + let y = cy + dy; + if y < 0 { + continue; + } + + let dy2 = dy * dy; + if dy2 > outer_r2 { + continue; + } + + let outer_dx = ((outer_r2 - dy2) as f32).sqrt() as i32; + let inner_dx = if dy2 < inner_r2 { + ((inner_r2 - dy2) as f32).sqrt() as i32 + } else { + -1 + }; + + let left_outer = cx - outer_dx; + let right_outer = cx + outer_dx; + + if inner_dx >= 0 { + let left_inner = cx - inner_dx; + let right_inner = cx + inner_dx; + + // left segment + for x in left_outer..left_inner { + if x >= 0 { + buffer.set_color_at(x as u32, y as u32, color); + } + } + + // right segment + for x in (right_inner + 1)..=right_outer { + if x >= 0 { + buffer.set_color_at(x as u32, y as u32, color); + } + } + } else { + // fully filled span (very thin or small radius) + for x in left_outer..=right_outer { + if x >= 0 { + buffer.set_color_at(x as u32, y as u32, color); + } + } + } + } +} + +fn plot( + cursor: Res, + buffer: Res, + mut images: ResMut>, + mut materials: ResMut>, + webviews: Query<&MeshMaterial3d>, + my_handle: Res, +) { + let image = images.get_mut(&my_handle.0).expect("Image not found"); + image.clear(&[0, 0, 0, 0]); + + for (i, c) in buffer.0.chunks(4).enumerate() { + let x = i as u32 % IMAGE_WIDTH; + let y = i as u32 / IMAGE_WIDTH; + + let red: u8 = c[0]; + let green = c[1]; + let blue = c[2]; + let alpha = c[3]; + + if alpha > 0 { + let color = Color::srgba_u8(red, green, blue, alpha); + image.set_color_at(x, y, color).unwrap(); + } + } + + for (i, c) in cursor.0.chunks(4).enumerate() { + let x = i as u32 % IMAGE_WIDTH; + let y = i as u32 / IMAGE_WIDTH; + + let red: u8 = c[0]; + let green = c[1]; + let blue = c[2]; + let alpha = c[3]; + + if alpha > 0 { + let color = Color::srgba_u8(red, green, blue, alpha); + image.set_color_at(x, y, color).unwrap(); + } + } + + // Poke redraw + for handle in webviews { + if let Some(material) = materials.get_mut(handle.id()) { + material.extension.overlay = Some(my_handle.0.clone()); + } + } +} + +// #[allow(clippy::type_complexity)] +// fn head_pointer( +// browsers: NonSend, +// webviews: Query<(Entity, &WebviewSize), With>, +// headset: Query<&GlobalTransform, With>, +// kneeboard: Query<(&GlobalTransform, Option<&LookedAt>), With>, +// aabb: MeshAabb, +// ) { +// let Ok((webview, size)) = webviews.single() else { +// return; +// }; + +// let tex_size = size.0; + +// if let Some(gt) = headset.into_iter().next() { +// let Ok((plane_tf, looked_at)) = kneeboard.single() else { +// return; +// }; + +// if looked_at.is_some() { +// // this is inverted for some reason wtf +// return; +// } + +// let (min, max) = aabb.calculate_local(webview); +// let plane_size = Vec2::new(max.x - min.x, max.y - min.y); + +// let ray = Ray3d::new(gt.translation(), gt.forward()); + +// let n = plane_tf.forward().as_vec3(); +// let Some(t) = ray.intersect_plane( +// plane_tf.translation(), +// InfinitePlane3d::new(plane_tf.forward()), +// ) else { +// return; +// }; +// let hit_world = ray.origin + ray.direction * t; +// let local_hit = plane_tf.affine().inverse().transform_point(hit_world); +// let local_normal = plane_tf.affine().inverse().transform_vector3(n).normalize(); +// let abs_normal = local_normal.abs(); +// let (u_coord, v_coord) = if abs_normal.z > abs_normal.x && abs_normal.z > abs_normal.y { +// (local_hit.x, local_hit.y) +// } else if abs_normal.y > abs_normal.x { +// (local_hit.x, local_hit.z) +// } else { +// (local_hit.y, local_hit.z) +// }; + +// let w = plane_size.x; +// let h = plane_size.y; +// let u = (u_coord + w * 0.5) / w; +// let v = (v_coord + h * 0.5) / h; +// if !(0.0..=1.0).contains(&u) || !(0.0..=1.0).contains(&v) { +// // outside plane bounds +// return; +// } +// let px = (1.0 - u) * tex_size.x; +// let py = (1.0 - v) * tex_size.y; + +// let pos = Vec2::new(px, py); + +// browsers.send_mouse_move(&webview, &[], pos, false); +// } +// } diff --git a/src/otdipcplugin.rs b/src/otdipcplugin.rs new file mode 100644 index 0000000..29718cc --- /dev/null +++ b/src/otdipcplugin.rs @@ -0,0 +1,100 @@ +use bevy::{ + app::{App, Plugin, PreUpdate}, + ecs::{message::MessageWriter, resource::Resource, system::ResMut}, + math::Vec2, +}; +use otd_ipc::{Message, OtdIpc}; +use triple_buffer::{Output, triple_buffer}; + +#[derive(Resource)] +struct OtdChannel(Output>); + +#[derive(Resource)] +struct TabletSize(Option); + +#[derive(Resource)] +struct MaxPenPressure(Option); + +#[derive(bevy::ecs::message::Message)] +pub struct PenPosition { + pub position: Vec2, +} + +#[derive(bevy::ecs::message::Message)] +pub struct PenButtons { + pub state: u32, +} + +#[derive(bevy::ecs::message::Message)] +pub struct PenPressure { + pub pressure: f32, +} + +pub struct OtdIpcPlugin; + +impl Plugin for OtdIpcPlugin { + fn build(&self, app: &mut App) { + let (mut tx, rx) = triple_buffer(&None); + std::thread::spawn(move || { + let otd_ipc = OtdIpc::new("Kneeboard", "master").unwrap(); + + for msg in otd_ipc { + tx.write(Some(msg)); + } + }); + app.add_message::(); + app.add_message::(); + app.add_message::(); + app.insert_resource(OtdChannel(rx)); + app.insert_resource(MaxPenPressure(None)); + app.insert_resource(TabletSize(None)); + app.add_systems(PreUpdate, reader); + } +} + +#[allow(clippy::too_many_arguments)] +fn reader( + mut channel: ResMut, + mut size: ResMut, + mut pressure: ResMut, + mut pressure_writer: MessageWriter, + mut position_writer: MessageWriter, + mut button_writer: MessageWriter, +) { + let Some(msg) = channel.0.read() else { + return; + }; + + match msg { + otd_ipc::Message::DeviceInfo(info) => { + size.0 = Some(Vec2::new(info.max_x, info.max_y)); + pressure.0 = Some(info.max_pressure); + } + otd_ipc::Message::State(state) => { + let Some(size) = size.0 else { + return; + }; + + let Some(pressure) = pressure.0 else { + return; + }; + + button_writer.write(PenButtons { + state: state.pen_buttons(), + }); + + let p = state.pressure() as f32 / pressure as f32; + + pressure_writer.write(PenPressure { pressure: p }); + + // Make rotation configurable with some resource enum or something + let y = (size.x - state.x()) / size.x; + let x = state.y() / size.y; + + let loc = Vec2::new(x, y); + + position_writer.write(PenPosition { position: loc }); + } + _ => {} + } +} diff --git a/src/vrcontrollerplugin.rs b/src/vrcontrollerplugin.rs index baf75e5..7a98d7d 100644 --- a/src/vrcontrollerplugin.rs +++ b/src/vrcontrollerplugin.rs @@ -46,11 +46,17 @@ impl Plugin for VrControllersPlugin { .run_if(openxr_session_running), ); - app.add_systems(OxrSendActionBindings, suggest_action_bindings_left); - app.add_systems(OxrSendActionBindings, suggest_action_bindings_right); - app.add_systems(Startup, create_actions_left.run_if(session_available)); app.add_systems(Startup, create_actions_right.run_if(session_available)); + + app.add_systems( + OxrSendActionBindings, + suggest_action_bindings_left.after(create_actions_left), + ); + app.add_systems( + OxrSendActionBindings, + suggest_action_bindings_right.after(create_actions_right), + ); } } diff --git a/src/vrplugin.rs b/src/vrplugin.rs index 8698cf9..e8fcfa4 100644 --- a/src/vrplugin.rs +++ b/src/vrplugin.rs @@ -1,5 +1,6 @@ use bevy::{ prelude::*, + render::render_resource::TextureFormat, window::{PresentMode, WindowResolution}, }; use bevy_mod_openxr::prelude::*; @@ -41,14 +42,9 @@ impl Plugin for VrPlugin { ); app.insert_resource(OxrSessionConfig { - blend_mode_preference: { - vec![ - EnvironmentBlendMode::ALPHA_BLEND, - EnvironmentBlendMode::ADDITIVE, - EnvironmentBlendMode::OPAQUE, - ] - }, - ..OxrSessionConfig::default() + blend_mode_preference: { vec![EnvironmentBlendMode::ALPHA_BLEND] }, + formats: Some(vec![TextureFormat::Rgba8UnormSrgb]), + ..Default::default() }); app.add_systems(XrSessionCreated, create_view_space);