Compare commits
3 Commits
4296a6488c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b05f29da23 | |||
| 17a0de5903 | |||
| 485ea056a7 |
48
Cargo.lock
generated
48
Cargo.lock
generated
@@ -382,6 +382,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "az"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -2664,6 +2670,29 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
"embedded-graphics-core",
|
||||||
|
"float-cmp",
|
||||||
|
"micromath",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics-core"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encase"
|
name = "encase"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -2839,6 +2868,15 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -3199,7 +3237,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"presser",
|
"presser",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"windows 0.54.0",
|
"windows 0.58.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3704,6 +3742,8 @@ dependencies = [
|
|||||||
"bevy_mod_xr",
|
"bevy_mod_xr",
|
||||||
"bevy_pkv",
|
"bevy_pkv",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"embedded-graphics",
|
||||||
|
"embedded-graphics-core",
|
||||||
"openxr",
|
"openxr",
|
||||||
"otd-ipc",
|
"otd-ipc",
|
||||||
"rdev",
|
"rdev",
|
||||||
@@ -3904,6 +3944,12 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "micromath"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@@ -22,3 +22,5 @@ serde = { version = "1.0.228", features = ["derive"] }
|
|||||||
otd-ipc = { path = "../../otd-ipc-client-rs" }
|
otd-ipc = { path = "../../otd-ipc-client-rs" }
|
||||||
crossbeam-channel = "0.5.15"
|
crossbeam-channel = "0.5.15"
|
||||||
triple_buffer = "8.1.1"
|
triple_buffer = "8.1.1"
|
||||||
|
embedded-graphics = "0.8.2"
|
||||||
|
embedded-graphics-core = "0.4.1"
|
||||||
|
|||||||
233
src/drawingplugin.rs
Normal file
233
src/drawingplugin.rs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
mod drawtarget;
|
||||||
|
mod rgba;
|
||||||
|
mod uiplugin;
|
||||||
|
|
||||||
|
use bevy_cef::prelude::WebviewExtendStandardMaterial;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
asset::RenderAssetUsages,
|
||||||
|
color::palettes::css,
|
||||||
|
prelude::*,
|
||||||
|
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||||
|
};
|
||||||
|
use embedded_graphics::prelude::DrawTarget;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawingplugin::drawtarget::{DrawableLayer, Visible},
|
||||||
|
otdipcplugin::OtdIpcPlugin,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
drawingplugin::uiplugin::UiPlugin,
|
||||||
|
otdipcplugin::{PenButtons, PenPosition, PenPressure},
|
||||||
|
};
|
||||||
|
|
||||||
|
const IMAGE_WIDTH: usize = (210.0 * 3.5) as usize;
|
||||||
|
const IMAGE_HEIGHT: usize = (279.0 * 3.5) as usize;
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct MyProcGenImage(pub(crate) Handle<Image>);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct CursorBuffer;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct DrawingBuffer;
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct PenSize(f32);
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct PenColor(Color);
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct LastPenPos(pub Option<(i32, i32)>);
|
||||||
|
|
||||||
|
pub struct DrawingPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DrawingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(OtdIpcPlugin);
|
||||||
|
app.add_plugins(UiPlugin);
|
||||||
|
|
||||||
|
app.insert_resource(ClearColor(Color::NONE))
|
||||||
|
.insert_resource(LastPenPos(None))
|
||||||
|
.insert_resource(PenColor(css::BLACK.into()))
|
||||||
|
.insert_resource(PenSize(5.0));
|
||||||
|
|
||||||
|
app.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (cursor, draw, plot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
|
||||||
|
let image = Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width: IMAGE_WIDTH as u32,
|
||||||
|
height: IMAGE_HEIGHT as u32,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
&(Srgba::new(1.0, 1.0, 1.0, 0.0).to_u8_array()),
|
||||||
|
TextureFormat::Rgba8UnormSrgb,
|
||||||
|
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||||
|
);
|
||||||
|
|
||||||
|
let handle = images.add(image);
|
||||||
|
commands.insert_resource(MyProcGenImage(handle));
|
||||||
|
commands.spawn((
|
||||||
|
CursorBuffer,
|
||||||
|
DrawableLayer::new(IMAGE_WIDTH, IMAGE_HEIGHT, drawtarget::Layer::Drawing),
|
||||||
|
Visible,
|
||||||
|
));
|
||||||
|
commands.spawn((
|
||||||
|
DrawingBuffer,
|
||||||
|
DrawableLayer::new(IMAGE_WIDTH, IMAGE_HEIGHT, drawtarget::Layer::Cursor),
|
||||||
|
Visible,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn color_changer(mut pen_color: ResMut<PenColor>, mut pen_buttons: MessageReader<PenButtons>) {
|
||||||
|
// let Some(buttons) = pen_buttons.read().next() else {
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// pen_color.0 = css::BLACK.into();
|
||||||
|
|
||||||
|
// if buttons.a() {
|
||||||
|
// let mut t = LinearRgba::from(pen_color.0);
|
||||||
|
// t.alpha = 0.0;
|
||||||
|
// pen_color.0 = t.into();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn cursor(
|
||||||
|
pen_color: Res<PenColor>,
|
||||||
|
mut buffer: Query<&mut DrawableLayer, With<CursorBuffer>>,
|
||||||
|
mut pen_size: ResMut<PenSize>,
|
||||||
|
mut pen_buttons: MessageReader<PenButtons>,
|
||||||
|
mut pen_position: MessageReader<PenPosition>,
|
||||||
|
mut pen_pressure: MessageReader<PenPressure>,
|
||||||
|
) {
|
||||||
|
let Ok(mut buffer) = buffer.single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.a()
|
||||||
|
{
|
||||||
|
offset *= 4.;
|
||||||
|
};
|
||||||
|
|
||||||
|
for penpres in pen_pressure.read() {
|
||||||
|
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.x * (buffer.width as f32)) as i32;
|
||||||
|
let y = (penpos.y * (buffer.height as f32)) as i32;
|
||||||
|
|
||||||
|
buffer.clear(Srgba::new(0.0, 0.0, 0.0, 0.0).into()).ok();
|
||||||
|
buffer.draw_stroke(x - (cs + cd) - s, y, x - (cd) - s, y, 4.0, b);
|
||||||
|
buffer.draw_stroke(x - (cs + cd) - s, y, x - (cd) - s, y, 1.0, c);
|
||||||
|
buffer.draw_stroke(x + (cd) + s, y, x + (cs + cd) + s, y, 4.0, b);
|
||||||
|
buffer.draw_stroke(x + (cd) + s, y, x + (cs + cd) + s, y, 1.0, c);
|
||||||
|
|
||||||
|
buffer.draw_stroke(x, y - (cs + cd) - s, x, y - (cd) - s, 4.0, b);
|
||||||
|
buffer.draw_stroke(x, y - (cs + cd) - s, x, y - (cd) - s, 1.0, c);
|
||||||
|
buffer.draw_stroke(x, y + (cs + cd) + s, x, y + (cd) + s, 4.0, b);
|
||||||
|
buffer.draw_stroke(x, y + (cs + cd) + s, x, y + (cd) + s, 1.0, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
pen_size: Res<PenSize>,
|
||||||
|
pen_color: Res<PenColor>,
|
||||||
|
ui: Query<&Visible, With<uiplugin::UiBuffer>>,
|
||||||
|
mut lastloc: ResMut<LastPenPos>,
|
||||||
|
mut buffer: Query<&mut DrawableLayer, With<DrawingBuffer>>,
|
||||||
|
mut pen_buttons: MessageReader<PenButtons>,
|
||||||
|
mut pen_position: MessageReader<PenPosition>,
|
||||||
|
) {
|
||||||
|
if ui.single().is_ok() {
|
||||||
|
lastloc.0 = None;
|
||||||
|
return; // if the ui layer is visible, dont draw
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(mut buffer) = buffer.single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(buttons) = pen_buttons.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for penpos in pen_position.read() {
|
||||||
|
let x = (penpos.x * (buffer.width as f32)) as i32;
|
||||||
|
let y = (penpos.y * (buffer.height as f32)) as i32;
|
||||||
|
|
||||||
|
let Some(ll) = lastloc.0 else {
|
||||||
|
lastloc.0 = Some((x, y));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if buttons.tip() {
|
||||||
|
buffer.draw_stroke(ll.0, ll.1, x, y, pen_size.0, pen_color.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastloc.0 = Some((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plot(
|
||||||
|
buffers: Query<&DrawableLayer, With<Visible>>,
|
||||||
|
mut images: ResMut<Assets<Image>>,
|
||||||
|
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
||||||
|
webviews: Query<&MeshMaterial3d<WebviewExtendStandardMaterial>>,
|
||||||
|
my_handle: Res<MyProcGenImage>,
|
||||||
|
) {
|
||||||
|
let image = images.get_mut(&my_handle.0).expect("Image not found");
|
||||||
|
image.clear(&[0, 0, 0, 0]);
|
||||||
|
|
||||||
|
let sorted = buffers
|
||||||
|
.iter()
|
||||||
|
.sort_by::<&DrawableLayer>(|a, b| b.order.cmp(&a.order));
|
||||||
|
|
||||||
|
for layer in sorted {
|
||||||
|
for (i, c) in layer.buffer.chunks(4).enumerate() {
|
||||||
|
let x = i % layer.width;
|
||||||
|
let y = i / layer.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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
342
src/drawingplugin/drawtarget.rs
Normal file
342
src/drawingplugin/drawtarget.rs
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
use bevy::{color::Color, ecs::component::Component};
|
||||||
|
use embedded_graphics::{pixelcolor::Rgb888, prelude::*, primitives::*};
|
||||||
|
|
||||||
|
use crate::drawingplugin::rgba::Rgba;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Layer {
|
||||||
|
Drawing,
|
||||||
|
Ui,
|
||||||
|
Cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Visible;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct DrawableLayer {
|
||||||
|
pub order: Layer,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub buffer: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawableLayer {
|
||||||
|
pub fn new(width: usize, height: usize, order: Layer) -> Self {
|
||||||
|
let buffer = vec![0; width * height * 4];
|
||||||
|
Self {
|
||||||
|
order,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
impl DrawableLayer {
|
||||||
|
pub fn draw_stroke(
|
||||||
|
&mut self,
|
||||||
|
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 {
|
||||||
|
self.draw_filled_circle(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_color_at(&mut self, x: i32, y: i32, color: Color) {
|
||||||
|
Pixel(Point::new(x, y), super::rgba::Rgba::from(color.to_srgba()))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, thickness: f32, color: Color) {
|
||||||
|
Line::new(Point::new(x1, y1), Point::new(x2, y2))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(
|
||||||
|
Rgba::from(color.to_srgba()),
|
||||||
|
thickness as u32,
|
||||||
|
))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_circle(&mut self, cx: i32, cy: i32, radius: i32, thickness: f32, color: Color) {
|
||||||
|
Circle::new(Point::new(cx - radius, cy - radius), (radius * 2) as u32)
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(
|
||||||
|
Rgba::from(color.to_srgba()),
|
||||||
|
thickness as u32,
|
||||||
|
))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_filled_circle(&mut self, cx: i32, cy: i32, radius: i32, color: Color) {
|
||||||
|
Circle::new(Point::new(cx - radius, cy - radius), (radius * 2) as u32)
|
||||||
|
.into_styled(PrimitiveStyle::with_fill(Rgba::from(color.to_srgba())))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, thickness: f32, color: Color) {
|
||||||
|
Rectangle::new(Point::new(x, y), Size::new(w, h))
|
||||||
|
.into_styled(PrimitiveStyle::with_stroke(
|
||||||
|
Rgba::from(color.to_srgba()),
|
||||||
|
thickness as u32,
|
||||||
|
))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_filled_rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
|
||||||
|
Rectangle::new(Point::new(x, y), Size::new(w, h))
|
||||||
|
.into_styled(PrimitiveStyle::with_fill(Rgba::from(color.to_srgba())))
|
||||||
|
.draw(self)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dimensions for DrawableLayer {
|
||||||
|
fn bounding_box(&self) -> embedded_graphics::primitives::Rectangle {
|
||||||
|
Rectangle::new(
|
||||||
|
Point::new(0, 0),
|
||||||
|
Size::new(self.width as u32, self.height as u32),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawTarget for DrawableLayer {
|
||||||
|
type Color = Rgba<Rgb888>;
|
||||||
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = embedded_graphics::Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for p in pixels {
|
||||||
|
let x = p.0.x as usize;
|
||||||
|
let y = p.0.y as usize;
|
||||||
|
|
||||||
|
if !(0..=self.width).contains(&x) || !(0..=self.height).contains(&y) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = (x + (self.width * y)) * 4;
|
||||||
|
|
||||||
|
if index >= self.buffer.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer[index] = p.1.r();
|
||||||
|
self.buffer[index + 1] = p.1.g();
|
||||||
|
self.buffer[index + 2] = p.1.b();
|
||||||
|
self.buffer[index + 3] = p.1.a();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn set_color_at(&mut self, x: i32, y: i32, color: Color) {
|
||||||
|
// let x: usize = x as usize;
|
||||||
|
// let y: usize = y as usize;
|
||||||
|
|
||||||
|
// if !(0..=self.width).contains(&x) || !(0..=self.height).contains(&y) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let index = (x + (self.width * y)) * 4;
|
||||||
|
// let srgba = Srgba::from(color);
|
||||||
|
|
||||||
|
// if index >= self.buffer.len() {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// self.buffer[index] = (srgba.red * u8::MAX as f32) as u8;
|
||||||
|
// self.buffer[index + 1] = (srgba.green * u8::MAX as f32) as u8;
|
||||||
|
// self.buffer[index + 2] = (srgba.blue * u8::MAX as f32) as u8;
|
||||||
|
// self.buffer[index + 3] = (srgba.alpha * u8::MAX as f32) as u8;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn clear(&mut self) {
|
||||||
|
// self.buffer.fill(0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn draw_line(&mut self, 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 {
|
||||||
|
// self.draw_thick_point(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)
|
||||||
|
// pub fn draw_thick_point(&mut self, cx: i32, cy: i32, radius: i32, color: Color) {
|
||||||
|
// self.draw_filled_circle(cx, cy, radius, color);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn draw_filled_circle(&mut self, 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 {
|
||||||
|
// self.set_color_at(x, y, color);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn draw_circle(&mut self, 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 {
|
||||||
|
// self.set_color_at(x, y, color);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // right segment
|
||||||
|
// for x in (right_inner + 1)..=right_outer {
|
||||||
|
// if x >= 0 {
|
||||||
|
// self.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 {
|
||||||
|
// self.set_color_at(x, y, color);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn draw_rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, thickness: f32, color: Color) {
|
||||||
|
// let w = w as i32;
|
||||||
|
// let h = h as i32;
|
||||||
|
|
||||||
|
// self.draw_line(x, y, x + w, y, thickness, color);
|
||||||
|
// self.draw_line(x + w, y, x + w, y + h, thickness, color);
|
||||||
|
// self.draw_line(x, y + h, x + w, y + h, thickness, color);
|
||||||
|
// self.draw_line(x, y, x, y + h, thickness, color);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn draw_filled_rectangle(
|
||||||
|
// &mut self,
|
||||||
|
// x: i32,
|
||||||
|
// y: i32,
|
||||||
|
// w: u32,
|
||||||
|
// h: u32,
|
||||||
|
// thickness: f32,
|
||||||
|
// color: Color,
|
||||||
|
// ) {
|
||||||
|
// let w = w as i32;
|
||||||
|
// let h = h as i32;
|
||||||
|
|
||||||
|
// for i in 0..h {
|
||||||
|
// self.draw_line(x, y + i, x + w, y + i, thickness, color);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
94
src/drawingplugin/rgba.rs
Normal file
94
src/drawingplugin/rgba.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use bevy::color::Srgba;
|
||||||
|
use embedded_graphics_core::pixelcolor::*;
|
||||||
|
|
||||||
|
/// Simple RGBA color wrapper.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Rgba<C: RgbColor>(C, u8);
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[inline(always)]
|
||||||
|
fn mul_blend_u8(delta: u32, a: u32) -> u32 {
|
||||||
|
// Exact (delta * a) / 255 using the div255 trick (no slow integer division).
|
||||||
|
// Valid for 0..=65535 inputs; see Hacker's Delight 10-16.
|
||||||
|
let t = delta * a + 128;
|
||||||
|
(t + (t >> 8)) >> 8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
impl<C: RgbColor> Rgba<C> {
|
||||||
|
/// Create a new RGBA color.
|
||||||
|
pub const fn new(color: C, alpha: u8) -> Self {
|
||||||
|
Self(color, alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the color component.
|
||||||
|
pub const fn rgb(&self) -> C {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn r(&self) -> u8 {
|
||||||
|
self.0.r()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn g(&self) -> u8 {
|
||||||
|
self.0.g()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b(&self) -> u8 {
|
||||||
|
self.0.b()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the alpha component (0..=255).
|
||||||
|
pub const fn a(&self) -> u8 {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: RgbColor> PixelColor for Rgba<C> {
|
||||||
|
type Raw = C::Raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub trait Blend<T> {
|
||||||
|
fn blend(&self, bg: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Blend<Rgb888> for Rgba<Rgb888> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn blend(&self, bg: Rgb888) -> Rgb888 {
|
||||||
|
let a = self.a() as u32;
|
||||||
|
if a == 0 {
|
||||||
|
return bg;
|
||||||
|
}
|
||||||
|
if a == 255 {
|
||||||
|
return self.rgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
let fr = self.rgb().r() as u32;
|
||||||
|
let fg = self.rgb().g() as u32;
|
||||||
|
let fb = self.rgb().b() as u32;
|
||||||
|
|
||||||
|
let br = bg.r() as u32;
|
||||||
|
let bgc = bg.g() as u32;
|
||||||
|
let bb = bg.b() as u32;
|
||||||
|
|
||||||
|
let r = (br + mul_blend_u8(fr.wrapping_sub(br), a)) as u8;
|
||||||
|
let g = (bgc + mul_blend_u8(fg.wrapping_sub(bgc), a)) as u8;
|
||||||
|
let b = (bb + mul_blend_u8(fb.wrapping_sub(bb), a)) as u8;
|
||||||
|
|
||||||
|
Rgb888::new(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Srgba> for Rgba<Rgb888> {
|
||||||
|
fn from(value: Srgba) -> Self {
|
||||||
|
Self(
|
||||||
|
Rgb888::new(
|
||||||
|
(value.red * 255.) as u8,
|
||||||
|
(value.green * 255.) as u8,
|
||||||
|
(value.blue * 255.) as u8,
|
||||||
|
),
|
||||||
|
(value.alpha * 255.) as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
270
src/drawingplugin/uiplugin.rs
Normal file
270
src/drawingplugin/uiplugin.rs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
use std::f32;
|
||||||
|
|
||||||
|
use bevy::{color::palettes::css, prelude::*};
|
||||||
|
use embedded_graphics::prelude::DrawTarget;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
drawingplugin::{DrawingBuffer, IMAGE_HEIGHT, IMAGE_WIDTH, PenColor, drawtarget::Visible},
|
||||||
|
otdipcplugin::{PenButtons, PenPosition},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::drawtarget::{DrawableLayer, Layer};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct UiBuffer;
|
||||||
|
|
||||||
|
#[derive(Component, Deref)]
|
||||||
|
pub struct UiPosition(Vec2);
|
||||||
|
|
||||||
|
pub struct UiPlugin;
|
||||||
|
|
||||||
|
impl Plugin for UiPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, setup).add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
show_hide,
|
||||||
|
ui_on_show,
|
||||||
|
ui_on_hide,
|
||||||
|
color_changer,
|
||||||
|
clear,
|
||||||
|
color_picker,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
UiBuffer,
|
||||||
|
DrawableLayer::new(IMAGE_WIDTH, IMAGE_HEIGHT, Layer::Ui),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_hide(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut pen_buttons: MessageReader<PenButtons>,
|
||||||
|
ent: Query<Entity, With<UiBuffer>>,
|
||||||
|
) {
|
||||||
|
let Ok(ent) = ent.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(buttons) = pen_buttons.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if buttons.b() {
|
||||||
|
commands.entity(ent).insert(Visible);
|
||||||
|
} else {
|
||||||
|
commands.entity(ent).remove::<Visible>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_on_show(
|
||||||
|
mut commands: Commands,
|
||||||
|
ent: Query<Entity, Added<Visible>>,
|
||||||
|
mut pen_position: MessageReader<PenPosition>,
|
||||||
|
) {
|
||||||
|
let Ok(ent) = ent.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(pos) = pen_position.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
commands.entity(ent).insert(UiPosition(**pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_on_hide(mut commands: Commands, mut removed: RemovedComponents<Visible>) {
|
||||||
|
let Some(ent) = removed.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
commands
|
||||||
|
.entity(ent)
|
||||||
|
.insert(UiPosition(Vec2::new(-1000., -1000.)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_picker(mut buffer: Query<(&mut DrawableLayer, &UiPosition), With<UiBuffer>>) {
|
||||||
|
let Ok((mut buffer, base_pos)) = buffer.single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let w = buffer.width as f32;
|
||||||
|
let h = buffer.height as f32;
|
||||||
|
let x = (base_pos.x * w) as i32;
|
||||||
|
let y = (base_pos.y * h) as i32;
|
||||||
|
buffer.clear(Srgba::new(0.0, 0.0, 0.0, 0.0).into()).ok();
|
||||||
|
|
||||||
|
color_wheel(&mut buffer, x, y, 100);
|
||||||
|
buffer.draw_circle(x, y, 100, 5.0, css::BLACK.into());
|
||||||
|
buffer.draw_circle(x, y, 125, 3.0, css::BLACK.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn clear(
|
||||||
|
buffer: Query<
|
||||||
|
(&DrawableLayer, &UiPosition),
|
||||||
|
(With<UiBuffer>, With<Visible>, Without<DrawingBuffer>),
|
||||||
|
>,
|
||||||
|
mut draw_buffer: Query<&mut DrawableLayer, (With<DrawingBuffer>, Without<UiBuffer>)>,
|
||||||
|
mut pen_position: MessageReader<PenPosition>,
|
||||||
|
mut pen_buttons: MessageReader<PenButtons>,
|
||||||
|
) {
|
||||||
|
let Ok((buffer, base_pos)) = buffer.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(mut draw_buffer) = draw_buffer.single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(pos) = pen_position.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(buttons) = pen_buttons.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !buttons.tip() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let w = buffer.width as f32;
|
||||||
|
let h = buffer.height as f32;
|
||||||
|
let ui_x = base_pos.x * w;
|
||||||
|
let ui_y = base_pos.y * h;
|
||||||
|
|
||||||
|
let pen_x = pos.x * w;
|
||||||
|
let pen_y = pos.y * h;
|
||||||
|
|
||||||
|
let delta = Vec2::new(pen_x - ui_x, pen_y - ui_y);
|
||||||
|
let radius = 100.;
|
||||||
|
|
||||||
|
let r_sq = radius * radius;
|
||||||
|
let dist_sq = delta.length_squared();
|
||||||
|
|
||||||
|
if dist_sq <= (r_sq * 1.5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_buffer.clear(Srgba::rgba_u8(0, 0, 0, 0).into()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn color_changer(
|
||||||
|
buffer: Query<(&DrawableLayer, &UiPosition), (With<UiBuffer>, With<Visible>)>,
|
||||||
|
mut pen_color: ResMut<PenColor>,
|
||||||
|
mut pen_position: MessageReader<PenPosition>,
|
||||||
|
mut pen_buttons: MessageReader<PenButtons>,
|
||||||
|
) {
|
||||||
|
let Ok((buffer, base_pos)) = buffer.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(pos) = pen_position.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(buttons) = pen_buttons.read().next() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !buttons.tip() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let w = buffer.width as f32;
|
||||||
|
let h = buffer.height as f32;
|
||||||
|
let ui_x = base_pos.x * w;
|
||||||
|
let ui_y = base_pos.y * h;
|
||||||
|
|
||||||
|
let pen_x = pos.x * w;
|
||||||
|
let pen_y = pos.y * h;
|
||||||
|
|
||||||
|
let delta = Vec2::new(pen_x - ui_x, pen_y - ui_y);
|
||||||
|
let radius = 100.;
|
||||||
|
|
||||||
|
let r_sq = radius * radius;
|
||||||
|
let dist_sq = delta.length_squared();
|
||||||
|
|
||||||
|
if dist_sq > (r_sq * 1.5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pen_color.0 = color_from_pos(delta.x, delta.y, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_wheel(buffer: &mut DrawableLayer, cx: i32, cy: i32, radius: i32) {
|
||||||
|
for dy in -radius..=radius {
|
||||||
|
for dx in -radius..=radius {
|
||||||
|
let x = cx + dx;
|
||||||
|
let y = cy + dy;
|
||||||
|
|
||||||
|
buffer.set_color_at(x, y, color_from_pos(dx as f32, dy as f32, radius as f32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_from_pos(x: f32, y: f32, radius: f32) -> Color {
|
||||||
|
let r_sq = radius * radius;
|
||||||
|
let dist_sq = x * x + y * y;
|
||||||
|
|
||||||
|
if dist_sq > r_sq {
|
||||||
|
return Color::srgba(0.0, 0.0, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distance normalized (0.0 → 1.0)
|
||||||
|
let dist = dist_sq.sqrt() / radius;
|
||||||
|
|
||||||
|
// Angle in radians → [0, 1]
|
||||||
|
let angle = y.atan2(x); // -PI..PI
|
||||||
|
let hue = (angle + std::f32::consts::PI) / (2.0 * std::f32::consts::PI);
|
||||||
|
|
||||||
|
let (r, g, b) = hsl_to_rgb(hue, 1.0, dist);
|
||||||
|
|
||||||
|
Color::srgba(r, g, b, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (f32, f32, f32) {
|
||||||
|
if s == 0.0 {
|
||||||
|
return (l, l, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
|
||||||
|
if t < 0.0 {
|
||||||
|
t += 1.0;
|
||||||
|
}
|
||||||
|
if t > 1.0 {
|
||||||
|
t -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if t < 1.0 / 6.0 {
|
||||||
|
return p + (q - p) * 6.0 * t;
|
||||||
|
}
|
||||||
|
if t < 1.0 / 2.0 {
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
if t < 2.0 / 3.0 {
|
||||||
|
return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
let q = if l < 0.5 {
|
||||||
|
l * (1.0 + s)
|
||||||
|
} else {
|
||||||
|
l + s - l * s
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = 2.0 * l - q;
|
||||||
|
|
||||||
|
let r = hue_to_rgb(p, q, h + 1.0 / 3.0);
|
||||||
|
let g = hue_to_rgb(p, q, h);
|
||||||
|
let b = hue_to_rgb(p, q, h - 1.0 / 3.0);
|
||||||
|
|
||||||
|
(r, g, b)
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
use std::f32::consts::{FRAC_PI_2, PI};
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_cef::prelude::*;
|
use bevy_cef::prelude::*;
|
||||||
use bevy_mod_openxr::prelude::*;
|
use bevy_mod_openxr::prelude::*;
|
||||||
use bevy_mod_xr::session::XrSessionCreated;
|
|
||||||
use bevy_pkv::{PersistentResourceAppExtensions, PkvStore};
|
use bevy_pkv::{PersistentResourceAppExtensions, PkvStore};
|
||||||
use openxr::Path;
|
use openxr::Path;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::f32::consts::{FRAC_PI_2, PI};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
MyProcGenImage,
|
MainCamera,
|
||||||
|
drawingplugin::{self, MyProcGenImage},
|
||||||
vrcontrollerplugin::{
|
vrcontrollerplugin::{
|
||||||
LeftController, LeftControllerActions, RightController, RightControllerActions,
|
LeftController, LeftControllerActions, RightController, RightControllerActions,
|
||||||
},
|
},
|
||||||
vrplugin::{Headset, MainCamera, create_view_space},
|
vrplugin::Headset,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// use bevy_mod_xr::session::XrSessionCreated;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct LookedAt;
|
pub struct LookedAt;
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ impl Plugin for KneeboardPlugin {
|
|||||||
});
|
});
|
||||||
app.insert_resource(PkvStore::new("Avii", "Kneeboard"))
|
app.insert_resource(PkvStore::new("Avii", "Kneeboard"))
|
||||||
.init_persistent_resource::<KneeboardPosition>();
|
.init_persistent_resource::<KneeboardPosition>();
|
||||||
app.add_systems(XrSessionCreated, spawn_kneeboard.after(create_view_space));
|
app.add_systems(Startup, spawn_kneeboard.after(drawingplugin::setup));
|
||||||
app.add_systems(Update, gaze.run_if(openxr_session_running));
|
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, move_keyboard.run_if(openxr_session_running));
|
||||||
app.add_systems(Update, position_kneeboard.after(move_keyboard));
|
app.add_systems(Update, position_kneeboard.after(move_keyboard));
|
||||||
@@ -107,7 +108,9 @@ fn position_kneeboard(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let head = head.single().expect("a head to exist");
|
let Ok(head) = head.single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
transform.translation = kneeboard.position;
|
transform.translation = kneeboard.position;
|
||||||
transform.rotation = kneeboard.rotation;
|
transform.rotation = kneeboard.rotation;
|
||||||
@@ -163,7 +166,7 @@ fn spawn_kneeboard(
|
|||||||
position: Res<KneeboardPosition>,
|
position: Res<KneeboardPosition>,
|
||||||
) {
|
) {
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
WebviewSource::new("http://localhost:7878/MOSRPRPETASPCLORWTSURDEP"),
|
// WebviewSource::new("http://localhost:7878/MOSRPRPETASPCLORWTSURDEP"),
|
||||||
WebviewSize(Vec2::new(210.0 * 3.5, 279.0 * 3.5)),
|
WebviewSize(Vec2::new(210.0 * 3.5, 279.0 * 3.5)),
|
||||||
Mesh3d(meshes.add(Cuboid::new(0.210, 0.279, 0.01))),
|
Mesh3d(meshes.add(Cuboid::new(0.210, 0.279, 0.01))),
|
||||||
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
MeshMaterial3d(materials.add(WebviewExtendStandardMaterial {
|
||||||
|
|||||||
515
src/main.rs
515
src/main.rs
@@ -1,502 +1,59 @@
|
|||||||
//! A simple 3D scene with light shining over a cube sitting on a plane.
|
mod drawingplugin;
|
||||||
|
|
||||||
mod kneeboardplugin;
|
mod kneeboardplugin;
|
||||||
mod otdipcplugin;
|
mod otdipcplugin;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
mod vrcontrollerplugin;
|
mod vrcontrollerplugin;
|
||||||
|
#[allow(unused)]
|
||||||
mod vrplugin;
|
mod vrplugin;
|
||||||
|
|
||||||
use bevy_cef::prelude::WebviewExtendStandardMaterial;
|
#[allow(unused)]
|
||||||
|
use vrcontrollerplugin::VrControllersPlugin;
|
||||||
|
#[allow(unused)]
|
||||||
use vrplugin::VrPlugin;
|
use vrplugin::VrPlugin;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::RenderAssetUsages,
|
|
||||||
color::palettes::css,
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
|
window::{PresentMode, WindowResolution},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::otdipcplugin::OtdIpcPlugin;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
kneeboardplugin::KneeboardPlugin,
|
drawingplugin::DrawingPlugin, kneeboardplugin::KneeboardPlugin, otdipcplugin::TabletRotation,
|
||||||
otdipcplugin::{PenButtons, PenPosition, PenPressure},
|
|
||||||
vrcontrollerplugin::VrControllersPlugin,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[require(Camera3d)]
|
||||||
|
pub struct MainCamera;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((VrPlugin, MeshPickingPlugin))
|
// .add_plugins(VrPlugin)
|
||||||
.add_plugins(VrControllersPlugin)
|
// .add_plugins(VrControllersPlugin)
|
||||||
.add_plugins(OtdIpcPlugin)
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
.add_plugins(KneeboardPlugin)
|
primary_window: Some(Window {
|
||||||
.insert_resource(ClearColor(Color::NONE))
|
title: "Kneeboard".into(),
|
||||||
.insert_resource(LastPenPos(None))
|
resolution: WindowResolution::new(550, 720),
|
||||||
.insert_resource(PenColor(css::BLACK.into()))
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
.insert_resource(PenSize(5.0))
|
fit_canvas_to_parent: true,
|
||||||
|
prevent_default_event_handling: false,
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, (color_changer, cursor, draw, plot).chain())
|
.add_plugins(DrawingPlugin)
|
||||||
|
.add_plugins(KneeboardPlugin)
|
||||||
|
.insert_resource(TabletRotation::CW)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
fn setup(mut commands: Commands) {
|
||||||
struct MyProcGenImage(Handle<Image>);
|
commands.spawn((
|
||||||
|
Camera {
|
||||||
#[derive(Resource)]
|
clear_color: ClearColorConfig::Custom(Color::linear_rgb(0.3, 0.3, 0.3)),
|
||||||
struct PenSize(f32);
|
..default()
|
||||||
|
|
||||||
#[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<Assets<Image>>) {
|
|
||||||
let image = Image::new_fill(
|
|
||||||
Extent3d {
|
|
||||||
width: IMAGE_WIDTH as u32,
|
|
||||||
height: IMAGE_HEIGHT as u32,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
},
|
||||||
TextureDimension::D2,
|
MainCamera,
|
||||||
// Initialize it with a beige color
|
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
&(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<PenColor>, mut pen_buttons: MessageReader<PenButtons>) {
|
|
||||||
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<PenColor>,
|
|
||||||
mut buffer: ResMut<CursorBuffer>,
|
|
||||||
mut pen_size: ResMut<PenSize>,
|
|
||||||
mut pen_buttons: MessageReader<PenButtons>,
|
|
||||||
mut pen_position: MessageReader<PenPosition>,
|
|
||||||
mut pen_pressure: MessageReader<PenPressure>,
|
|
||||||
) {
|
|
||||||
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<PenSize>,
|
|
||||||
pen_color: Res<PenColor>,
|
|
||||||
mut lastloc: ResMut<LastPenPos>,
|
|
||||||
mut buffer: ResMut<DrawingBuffer>,
|
|
||||||
mut pen_buttons: MessageReader<PenButtons>,
|
|
||||||
mut pen_position: MessageReader<PenPosition>,
|
|
||||||
) {
|
|
||||||
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<T: Drawing + Resource>(
|
|
||||||
buffer: &mut ResMut<T>,
|
|
||||||
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<T: Drawing + Resource>(
|
|
||||||
buffer: &mut ResMut<T>,
|
|
||||||
cx: i32,
|
|
||||||
cy: i32,
|
|
||||||
radius: i32,
|
|
||||||
color: Color,
|
|
||||||
) {
|
|
||||||
draw_filled_circle(buffer, cx, cy, radius, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_filled_circle<T: Drawing + Resource>(
|
|
||||||
buffer: &mut ResMut<T>,
|
|
||||||
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<T: Drawing + Resource>(
|
|
||||||
buffer: &mut ResMut<T>,
|
|
||||||
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<CursorBuffer>,
|
|
||||||
buffer: Res<DrawingBuffer>,
|
|
||||||
mut images: ResMut<Assets<Image>>,
|
|
||||||
mut materials: ResMut<Assets<WebviewExtendStandardMaterial>>,
|
|
||||||
webviews: Query<&MeshMaterial3d<WebviewExtendStandardMaterial>>,
|
|
||||||
my_handle: Res<MyProcGenImage>,
|
|
||||||
) {
|
|
||||||
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<Browsers>,
|
|
||||||
// webviews: Query<(Entity, &WebviewSize), With<WebviewSource>>,
|
|
||||||
// headset: Query<&GlobalTransform, With<XrCamera>>,
|
|
||||||
// kneeboard: Query<(&GlobalTransform, Option<&LookedAt>), With<Kneeboard>>,
|
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
app::{App, Plugin, PreUpdate},
|
app::{App, Plugin, PreUpdate},
|
||||||
ecs::{message::MessageWriter, resource::Resource, system::ResMut},
|
ecs::{
|
||||||
|
message::MessageWriter,
|
||||||
|
resource::Resource,
|
||||||
|
system::{Res, ResMut},
|
||||||
|
},
|
||||||
math::Vec2,
|
math::Vec2,
|
||||||
|
prelude::Deref,
|
||||||
};
|
};
|
||||||
use otd_ipc::{Message, OtdIpc};
|
use otd_ipc::{Message, OtdIpc};
|
||||||
use triple_buffer::{Output, triple_buffer};
|
use triple_buffer::{Output, triple_buffer};
|
||||||
@@ -15,14 +20,24 @@ struct TabletSize(Option<Vec2>);
|
|||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct MaxPenPressure(Option<u32>);
|
struct MaxPenPressure(Option<u32>);
|
||||||
|
|
||||||
#[derive(bevy::ecs::message::Message)]
|
#[derive(bevy::ecs::message::Message, Deref)]
|
||||||
pub struct PenPosition {
|
pub struct PenPosition(Vec2);
|
||||||
pub position: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(bevy::ecs::message::Message)]
|
#[derive(bevy::ecs::message::Message, Deref)]
|
||||||
pub struct PenButtons {
|
pub struct PenButtons(u32);
|
||||||
pub state: u32,
|
|
||||||
|
impl PenButtons {
|
||||||
|
pub fn tip(&self) -> bool {
|
||||||
|
self.0 & 1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn a(&self) -> bool {
|
||||||
|
self.0 & 2 == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn b(&self) -> bool {
|
||||||
|
self.0 & 4 == 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(bevy::ecs::message::Message)]
|
#[derive(bevy::ecs::message::Message)]
|
||||||
@@ -30,6 +45,16 @@ pub struct PenPressure {
|
|||||||
pub pressure: f32,
|
pub pressure: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub enum TabletRotation {
|
||||||
|
Default,
|
||||||
|
CW,
|
||||||
|
UpsideDown,
|
||||||
|
CCW,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OtdIpcPlugin;
|
pub struct OtdIpcPlugin;
|
||||||
|
|
||||||
impl Plugin for OtdIpcPlugin {
|
impl Plugin for OtdIpcPlugin {
|
||||||
@@ -45,6 +70,7 @@ impl Plugin for OtdIpcPlugin {
|
|||||||
app.add_message::<PenPosition>();
|
app.add_message::<PenPosition>();
|
||||||
app.add_message::<PenPressure>();
|
app.add_message::<PenPressure>();
|
||||||
app.add_message::<PenButtons>();
|
app.add_message::<PenButtons>();
|
||||||
|
app.insert_resource(TabletRotation::Default);
|
||||||
app.insert_resource(OtdChannel(rx));
|
app.insert_resource(OtdChannel(rx));
|
||||||
app.insert_resource(MaxPenPressure(None));
|
app.insert_resource(MaxPenPressure(None));
|
||||||
app.insert_resource(TabletSize(None));
|
app.insert_resource(TabletSize(None));
|
||||||
@@ -54,6 +80,7 @@ impl Plugin for OtdIpcPlugin {
|
|||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn reader(
|
fn reader(
|
||||||
|
rotation: Res<TabletRotation>,
|
||||||
mut channel: ResMut<OtdChannel>,
|
mut channel: ResMut<OtdChannel>,
|
||||||
mut size: ResMut<TabletSize>,
|
mut size: ResMut<TabletSize>,
|
||||||
mut pressure: ResMut<MaxPenPressure>,
|
mut pressure: ResMut<MaxPenPressure>,
|
||||||
@@ -79,21 +106,25 @@ fn reader(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
button_writer.write(PenButtons {
|
button_writer.write(PenButtons(state.pen_buttons()));
|
||||||
state: state.pen_buttons(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let p = state.pressure() as f32 / pressure as f32;
|
let p = state.pressure() as f32 / pressure as f32;
|
||||||
|
|
||||||
pressure_writer.write(PenPressure { pressure: p });
|
pressure_writer.write(PenPressure { pressure: p });
|
||||||
|
|
||||||
// Make rotation configurable with some resource enum or something
|
let x = state.x();
|
||||||
let y = (size.x - state.x()) / size.x;
|
let y = state.y();
|
||||||
let x = state.y() / size.y;
|
|
||||||
|
|
||||||
let loc = Vec2::new(x, y);
|
let loc = match *rotation {
|
||||||
|
TabletRotation::Default => Vec2::new(x / size.x, y / size.y),
|
||||||
|
TabletRotation::CCW => Vec2::new(y / size.y, (size.x - x) / size.x),
|
||||||
|
TabletRotation::CW => Vec2::new((size.y - y) / size.y, x / size.x),
|
||||||
|
TabletRotation::UpsideDown => {
|
||||||
|
Vec2::new((size.x - x) / size.x, (size.y - y) / size.y)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
position_writer.write(PenPosition { position: loc });
|
position_writer.write(PenPosition(loc));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ use bevy::{
|
|||||||
use bevy_mod_openxr::prelude::*;
|
use bevy_mod_openxr::prelude::*;
|
||||||
use bevy_mod_xr::session::XrSessionCreated;
|
use bevy_mod_xr::session::XrSessionCreated;
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
#[require(Camera3d)]
|
|
||||||
pub struct MainCamera;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Headset;
|
pub struct Headset;
|
||||||
|
|
||||||
@@ -57,12 +53,4 @@ pub fn create_view_space(session: Res<OxrSession>, mut commands: Commands) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
commands.spawn((Headset, space.0));
|
commands.spawn((Headset, space.0));
|
||||||
commands.spawn((
|
|
||||||
Camera {
|
|
||||||
clear_color: ClearColorConfig::Custom(Color::linear_rgb(0.3, 0.3, 0.3)),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
MainCamera,
|
|
||||||
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user