//! 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::WebviewExtendStandardMaterial; use vrplugin::VrPlugin; use bevy::{ asset::RenderAssetUsages, color::palettes::css, prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, }; use crate::otdipcplugin::OtdIpcPlugin; use crate::{ kneeboardplugin::KneeboardPlugin, otdipcplugin::{PenButtons, PenPosition, PenPressure}, vrcontrollerplugin::VrControllersPlugin, }; fn main() { App::new() .add_plugins((VrPlugin, MeshPickingPlugin)) .add_plugins(VrControllersPlugin) .add_plugins(OtdIpcPlugin) .add_plugins(KneeboardPlugin) .insert_resource(ClearColor(Color::NONE)) .insert_resource(LastPenPos(None)) .insert_resource(PenColor(css::BLACK.into())) .insert_resource(PenSize(5.0)) .add_systems(Startup, setup) .add_systems(Update, (color_changer, cursor, draw, plot).chain()) .run(); } #[derive(Resource)] struct MyProcGenImage(Handle); #[derive(Resource)] struct PenSize(f32); #[derive(Resource)] struct PenColor(Color); const IMAGE_WIDTH: i32 = (210.0 * 3.5) as i32; const IMAGE_HEIGHT: i32 = (279.0 * 3.5) as i32; fn setup(mut commands: Commands, mut images: ResMut>) { let image = Image::new_fill( Extent3d { width: IMAGE_WIDTH as u32, height: IMAGE_HEIGHT as u32, 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 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], )); } #[derive(Resource)] struct CursorBuffer([u8; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4]); impl Drawing for CursorBuffer { fn set_color_at(&mut self, x: i32, y: i32, color: Color) { if !(0..=IMAGE_WIDTH).contains(&x) || !(0..=IMAGE_HEIGHT).contains(&y) { return; } let index = ((x + (IMAGE_WIDTH * y)) * 4) as usize; let srgba = Srgba::from(color); if index >= self.0.len() { return; } 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); } } #[derive(Resource)] struct DrawingBuffer([u8; IMAGE_WIDTH as usize * IMAGE_HEIGHT as usize * 4]); #[derive(Resource)] pub struct LastPenPos(pub Option<(i32, i32)>); impl Drawing for DrawingBuffer { fn set_color_at(&mut self, x: i32, y: i32, color: Color) { if !(0..=IMAGE_WIDTH).contains(&x) || !(0..=IMAGE_HEIGHT).contains(&y) { return; } let index = ((x + (IMAGE_WIDTH * y)) * 4) as usize; let srgba = Srgba::from(color); if index >= self.0.len() { return; } 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 color_changer(mut pen_color: ResMut, mut pen_buttons: MessageReader) { let Some(buttons) = pen_buttons.read().next() else { return; }; pen_color.0 = css::BLACK.into(); if (buttons.state & 2) == 2 { let mut t = LinearRgba::from(pen_color.0); t.alpha = 0.0; pen_color.0 = t.into(); } if (buttons.state & 4) == 4 { let mut t = LinearRgba::from(css::DARK_BLUE); t.alpha = 0.5; pen_color.0 = t.into(); } } fn cursor( pen_color: Res, mut buffer: ResMut, mut pen_size: ResMut, mut pen_buttons: MessageReader, mut pen_position: MessageReader, mut pen_pressure: MessageReader, ) { let mut size: f32 = 1.0; let mut offset = 20.0; let c = pen_color.0; if let Some(buttons) = pen_buttons.read().next() && (buttons.state & 2 == 2) { offset *= 4.; }; for penpres in pen_pressure.read() { // this needs log scaling let _in = penpres.pressure; let _out = penpres.pressure.powi(4); size *= 1. + (_out * offset); } pen_size.0 = size.clamp(1.0, 200.0); let s = pen_size.0 as i32; let b = css::BLACK.into(); let cs = 10; let cd = 3; 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_line(&mut buffer, x - (cs + cd) - s, y, x - (cd) - s, y, 4.0, b); draw_line(&mut buffer, x - (cs + cd) - s, y, x - (cd) - s, y, 1.0, c); draw_line(&mut buffer, x + (cd) + s, y, x + (cs + cd) + s, y, 4.0, b); draw_line(&mut buffer, x + (cd) + s, y, x + (cs + cd) + s, y, 1.0, c); draw_line(&mut buffer, x, y - (cs + cd) - s, x, y - (cd) - s, 4.0, b); draw_line(&mut buffer, x, y - (cs + cd) - s, x, y - (cd) - s, 1.0, c); draw_line(&mut buffer, x, y + (cs + cd) + s, x, y + (cd) + s, 4.0, b); draw_line(&mut buffer, x, y + (cs + cd) + s, x, y + (cd) + s, 1.0, c); draw_filled_circle(&mut buffer, x, y, (pen_size.0 / 2.) as i32, c) } } fn draw( pen_size: Res, pen_color: Res, mut lastloc: ResMut, mut buffer: ResMut, mut pen_buttons: MessageReader, mut pen_position: MessageReader, ) { let Some(buttons) = pen_buttons.read().next() else { return; }; 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; let Some(ll) = lastloc.0 else { lastloc.0 = Some((x, y)); return; }; if buttons.state & 1 == 1 { draw_line(&mut buffer, ll.0, ll.1, x, y, pen_size.0, pen_color.0); } lastloc.0 = Some((x, y)); } } trait Drawing { fn set_color_at(&mut self, px: i32, py: i32, color: Color); fn clear(&mut self); } fn draw_line( buffer: &mut ResMut, x1: i32, y1: i32, x2: i32, y2: i32, thickness: f32, color: Color, ) { let (mut x0, mut y0) = (x1, y1); let (x1, y1) = (x2, y2); 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, y, 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, y, color); } } // right segment for x in (right_inner + 1)..=right_outer { if x >= 0 { buffer.set_color_at(x, y, 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, y, 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 i32 % IMAGE_WIDTH; let y = i as i32 / 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 as u32, y as u32, color).unwrap(); } } for (i, c) in cursor.0.chunks(4).enumerate() { let x = i as i32 % IMAGE_WIDTH; let y = i as i32 / 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 as u32, y as u32, 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); // } // }