From 9ef4c6745df271404b3f636aff6e834724025459 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 4 Jul 2025 16:12:33 +0200 Subject: [PATCH] WIP: clip mask drawing --- src/bin/render_lines_gui.rs | 115 ++++++++++++++++++++++-------------- src/trap/filters.rs | 46 +++++++++++++-- src/trap/laser.rs | 23 ++++++++ 3 files changed, 136 insertions(+), 48 deletions(-) diff --git a/src/bin/render_lines_gui.rs b/src/bin/render_lines_gui.rs index 3b1e117..86a0eaf 100644 --- a/src/bin/render_lines_gui.rs +++ b/src/bin/render_lines_gui.rs @@ -13,7 +13,7 @@ use nannou_laser::DacId; use nannou_laser::{self as laser}; use serde_json::Result; use trap_rust::trap::filters::PointFilters; -use trap_rust::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX}; +use trap_rust::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX, Corner}; use trap_rust::trap::tracks::CoordinateSpace; use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}}; use zmq::Socket; @@ -50,28 +50,7 @@ pub struct StreamConfig{ type StreamConfigMap = HashMap; type StreamMap = HashMap>; -#[derive(Debug, Clone)] -enum Corner{ - TopLeft, - TopRight, - BottomLeft, - BottomRight, -} -impl Corner { - pub fn in_laser_space() -> Vec<[f32; 2]>{ - vec!([-1.,1.], [1.,1.], [-1.,-1.], [1., -1.]) - } - - fn index(&self) -> usize { - match self { - Self::TopLeft => 0, - Self::TopRight => 1, - Self::BottomLeft => 2, - Self::BottomRight => 3, - } - } -} struct GuiModel { // A handle to the laser API used for spawning streams and detecting DACs. @@ -98,6 +77,7 @@ struct GuiModel { canvas_scale: f32, canvas_translate: Vec2, canvas_dragging_corner: Option, + preview_dragging_point: Option, // canvas_transform: Translation2D, // dragging: bool, } @@ -322,13 +302,15 @@ fn model(app: &App) -> GuiModel { .view(view_line_canvas) .build() .unwrap(); - + let w_id_laserpreview = app .new_window() .size(1024, 1024) // .key_pressed(key_pressed) // .mouse_wheel(canvas_zoom) .mouse_pressed(laser_preview_mouse_pressed) + .mouse_released(laser_mouse_released) + .mouse_moved(laser_mouse_moved) .view(view_laser_preview) .build() .unwrap(); @@ -413,6 +395,7 @@ fn model(app: &App) -> GuiModel { canvas_scale: 25., canvas_translate: Vec2::new(-300.,100.), canvas_dragging_corner: None, + preview_dragging_point: None, // canvas_transform: Transform2D // dimming_factor: 1., } @@ -935,6 +918,9 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) { draw.to_frame(app, &frame).unwrap(); } +const LASER_PREVIEW_WIDTH: f32 = 1024.; +const LASER_PREVIEW_HEIGHT: f32 = 1024.; + // preview the selected laser, to draw clip mask fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { // get canvas to draw on @@ -945,8 +931,8 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { let win = app.window_rect(); - let w = 1024.; - let h = 1024.; + let w = LASER_PREVIEW_WIDTH; + let h = LASER_PREVIEW_HEIGHT; let hh = h / 2.; let hw = w / 2.; @@ -963,9 +949,12 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { }, Some(dac_id) => { // let stream = model.laser_streams.get(&dac_id); //.expect("Selected stream not found in configs"); + + // 1. get config for laser let config = model.per_laser_config.get(&dac_id).expect("Selected stream not found in configs"); - draw.text(&format!("{:?}", dac_id)) + // 2 draw identifier of laser + draw.text(&format!("{} {:?}", config.name, dac_id)) .h(win_rect.h()) .font_size(10) .align_text_bottom() @@ -973,6 +962,21 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { .color(WHITE) .w(win_rect.w()); + // 3. clipping mask + its anchor points + let clip_points: Vec<[f32;2]> = Corner::in_laser_space().iter().map(|p| { + [p[0] * hw, p[1] * hh] + }).collect(); + + draw.polygon() + .color(srgba(1.,1.,1.,0.)) + .stroke(PINK) + .stroke_weight(thickness) + .join_round() + .points(clip_points); + + + // 4. current shape of the laser + let current_points: LaserPoints = (&model.current_lines).into(); let space = &model.current_lines.space; @@ -1001,34 +1005,59 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { } } - - - - - draw.to_frame(app, &frame).unwrap(); + draw.to_frame(app, &frame).unwrap(); } -fn laser_preview_mouse_pressed(app: &App, _model: &mut GuiModel, button: MouseButton) { +fn laser_preview_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseButton) { + let dac_id = match &model.selected_stream { + None => return, + Some(d) => d, + }; + if button != MouseButton::Left { // ignore + // TODO: right click remove point within Margin return; } - let half_w = (1024 / 2) as f32; - let half_h = (1024 / 2) as f32; + let half_w = LASER_PREVIEW_WIDTH / 2.; + let half_h = LASER_PREVIEW_HEIGHT / 2.; + + let laser_x = app.mouse.x / half_w; + let laser_y = app.mouse.y / half_h; + + // if x > 1. || x < -1. || y > 1. || y < -1. { + // println!("Click outside of canvas: {} {}", x, y); + // return + // } + + // 1. check if empty. it not check if one point is close + // 2a. if closest point is within selection margin, select that item. + // 2b. if not, if close point is found, if point has neighbours (i.e. len of vec > 1), and find which of neighbours is closest. Insert point between these. Select new point + // 2c. if no closest point is found (thus Vec is empty), create a point under cursor. Select that new point. - let x = app.mouse.x / half_w; - let y = app.mouse.y / half_h; - if x > 1. || x < -1. || y > 1. || y < -1. { - println!("Click outside of canvas: {} {}", x, y); - return - } } + +fn laser_mouse_moved(app: &App, model: &mut GuiModel, pos: Point2) { + let half_w = LASER_PREVIEW_WIDTH / 2.; + let half_h = LASER_PREVIEW_HEIGHT / 2.; + + let laser_x = app.mouse.x / half_w; + let laser_y = app.mouse.y / half_h; + + // 1. Move selected point to laser_x, laser_y +} + +fn laser_mouse_released(_app: &App, model: &mut GuiModel, _button: MouseButton) { + // deselect point + model.preview_dragging_point = None; +} + fn draw_grid(draw: &Draw, win: &Rect, step: f32, weight: f32) { let step_by = || (0..).map(|i| i as f32 * step); let r_iter = step_by().take_while(|&f| f < win.right()); @@ -1072,8 +1101,6 @@ fn style() -> egui::Style { style } -fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) { -} fn laser_corners_to_world(filters: &PointFilters) -> Vec<[f32;2]> { let corners_raw: Vec<[f32; 2]> = Corner::in_laser_space(); @@ -1169,8 +1196,8 @@ fn map_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseButton) { match i { 0 => selected = Some(Corner::TopLeft), 1 => selected = Some(Corner::TopRight), - 2 => selected = Some(Corner::BottomLeft), - 3 => selected = Some(Corner::BottomRight), + 2 => selected = Some(Corner::BottomRight), + 3 => selected = Some(Corner::BottomLeft), _ => selected = None, } break; diff --git a/src/trap/filters.rs b/src/trap/filters.rs index bd222ab..3c4ad1b 100644 --- a/src/trap/filters.rs +++ b/src/trap/filters.rs @@ -1,6 +1,6 @@ use bevy::prelude::*; // for glam::f32::Mat3 -use crate::trap::{laser::{apply_homography_matrix, LaserPoints}, tracks::CoordinateSpace}; +use crate::trap::{laser::{apply_homography_matrix, Corner, LaserPoints}, tracks::CoordinateSpace}; use nannou_laser::{self as laser, Point}; use serde::{Deserialize, Serialize}; @@ -22,6 +22,12 @@ pub struct CropFilter { pub enabled: bool } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ClipFilter { + pub enabled: bool, + pub mask: Vec<[f32; 2]> +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DimFilter { pub intensity: f32 @@ -59,6 +65,7 @@ pub struct PointFilters{ pub scale: ScaleFilter, pub pincushion: PincushionFilter, pub crop: CropFilter, + pub clip: ClipFilter, } // list of enums deprecated in favour of struct @@ -128,6 +135,7 @@ impl Default for PointFilters { scale: ScaleFilter { factor: 1. }, pincushion: PincushionFilter{k_x: 0.,k_x2: 0., k_y: 0., k_y2: 0.}, crop: CropFilter{ enabled: true }, + clip: ClipFilter{ enabled: false, mask: Corner::in_laser_space() }, } } } @@ -146,8 +154,8 @@ impl Filter for HomographyFilter { }; // also converts from world space to laser space (origin in center) - let s = 0xFFF as f32 / 2.; - let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.]; + // let s = 0xFFF as f32 / 2.; + // let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.]; laser::Point { position: new_position, .. point.clone() @@ -322,7 +330,7 @@ impl Filter for CropFilter { } fn reverse(&self, points: &LaserPoints) -> LaserPoints{ - // we cannot really add points, can we + // we cannot really conjure up points, can we return LaserPoints{ points: points.points.clone(), space: points.space, @@ -330,6 +338,36 @@ impl Filter for CropFilter { } } + + +impl Filter for ClipFilter { + + fn apply(&self, points: &LaserPoints) -> LaserPoints { + if !self.enabled { + // don't modify if disabled + return LaserPoints{ + points: points.points.clone(), + space: points.space, + }; + } + + // TODO + return LaserPoints{ + points: points.points.clone(), + space: points.space, + }; + } + + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + // we cannot really conjure up points, can we + return LaserPoints{ + points: points.points.clone(), + space: points.space, + }; + } + +} + fn change_brightness(points: &LaserPoints, intensity: f32) -> LaserPoints{ let new_points = points.points.iter().map(|point| { let mut color = point.color.clone(); diff --git a/src/trap/laser.rs b/src/trap/laser.rs index 4753a76..e1a70fa 100644 --- a/src/trap/laser.rs +++ b/src/trap/laser.rs @@ -307,4 +307,27 @@ impl StreamSource{ _ => LaserPoints::default(), // empty set } } +} + +#[derive(Debug, Clone)] +pub enum Corner{ + TopLeft, + TopRight, + BottomRight, + BottomLeft, +} + +impl Corner { + pub fn in_laser_space() -> Vec<[f32; 2]>{ + vec!([-1.,1.], [1.,1.], [1., -1.], [-1.,-1.]) + } + + pub fn index(&self) -> usize { + match self { + Self::TopLeft => 0, + Self::TopRight => 1, + Self::BottomRight => 2, + Self::BottomLeft => 3, + } + } } \ No newline at end of file