WIP: clip mask drawing

This commit is contained in:
Ruben van de Ven 2025-07-04 16:12:33 +02:00
parent 65acde713f
commit 9ef4c6745d
3 changed files with 136 additions and 48 deletions

View file

@ -13,7 +13,7 @@ use nannou_laser::DacId;
use nannou_laser::{self as laser}; use nannou_laser::{self as laser};
use serde_json::Result; use serde_json::Result;
use trap_rust::trap::filters::PointFilters; 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::tracks::CoordinateSpace;
use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}}; use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}};
use zmq::Socket; use zmq::Socket;
@ -50,28 +50,7 @@ pub struct StreamConfig{
type StreamConfigMap = HashMap<DacId, StreamConfig>; type StreamConfigMap = HashMap<DacId, StreamConfig>;
type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>; type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>;
#[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 { struct GuiModel {
// A handle to the laser API used for spawning streams and detecting DACs. // A handle to the laser API used for spawning streams and detecting DACs.
@ -98,6 +77,7 @@ struct GuiModel {
canvas_scale: f32, canvas_scale: f32,
canvas_translate: Vec2, canvas_translate: Vec2,
canvas_dragging_corner: Option<Corner>, canvas_dragging_corner: Option<Corner>,
preview_dragging_point: Option<usize>,
// canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>, // canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>,
// dragging: bool, // dragging: bool,
} }
@ -329,6 +309,8 @@ fn model(app: &App) -> GuiModel {
// .key_pressed(key_pressed) // .key_pressed(key_pressed)
// .mouse_wheel(canvas_zoom) // .mouse_wheel(canvas_zoom)
.mouse_pressed(laser_preview_mouse_pressed) .mouse_pressed(laser_preview_mouse_pressed)
.mouse_released(laser_mouse_released)
.mouse_moved(laser_mouse_moved)
.view(view_laser_preview) .view(view_laser_preview)
.build() .build()
.unwrap(); .unwrap();
@ -413,6 +395,7 @@ fn model(app: &App) -> GuiModel {
canvas_scale: 25., canvas_scale: 25.,
canvas_translate: Vec2::new(-300.,100.), canvas_translate: Vec2::new(-300.,100.),
canvas_dragging_corner: None, canvas_dragging_corner: None,
preview_dragging_point: None,
// canvas_transform: Transform2D // canvas_transform: Transform2D
// dimming_factor: 1., // dimming_factor: 1.,
} }
@ -935,6 +918,9 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
draw.to_frame(app, &frame).unwrap(); 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 // preview the selected laser, to draw clip mask
fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) { fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
// get canvas to draw on // 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 win = app.window_rect();
let w = 1024.; let w = LASER_PREVIEW_WIDTH;
let h = 1024.; let h = LASER_PREVIEW_HEIGHT;
let hh = h / 2.; let hh = h / 2.;
let hw = w / 2.; let hw = w / 2.;
@ -963,9 +949,12 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
}, },
Some(dac_id) => { Some(dac_id) => {
// let stream = model.laser_streams.get(&dac_id); //.expect("Selected stream not found in configs"); // 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"); 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()) .h(win_rect.h())
.font_size(10) .font_size(10)
.align_text_bottom() .align_text_bottom()
@ -973,6 +962,21 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
.color(WHITE) .color(WHITE)
.w(win_rect.w()); .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 current_points: LaserPoints = (&model.current_lines).into();
let space = &model.current_lines.space; let space = &model.current_lines.space;
@ -1001,32 +1005,57 @@ 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 { if button != MouseButton::Left {
// ignore // ignore
// TODO: right click remove point within Margin
return; return;
} }
let half_w = (1024 / 2) as f32; let half_w = LASER_PREVIEW_WIDTH / 2.;
let half_h = (1024 / 2) as f32; let half_h = LASER_PREVIEW_HEIGHT / 2.;
let x = app.mouse.x / half_w; let laser_x = app.mouse.x / half_w;
let y = app.mouse.y / half_h; let laser_y = app.mouse.y / half_h;
if x > 1. || x < -1. || y > 1. || y < -1. { // if x > 1. || x < -1. || y > 1. || y < -1. {
println!("Click outside of canvas: {} {}", x, y); // println!("Click outside of canvas: {} {}", x, y);
return // 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.
}
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) { fn draw_grid(draw: &Draw, win: &Rect, step: f32, weight: f32) {
@ -1072,8 +1101,6 @@ fn style() -> egui::Style {
style style
} }
fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) {
}
fn laser_corners_to_world(filters: &PointFilters) -> Vec<[f32;2]> { fn laser_corners_to_world(filters: &PointFilters) -> Vec<[f32;2]> {
let corners_raw: Vec<[f32; 2]> = Corner::in_laser_space(); 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 { match i {
0 => selected = Some(Corner::TopLeft), 0 => selected = Some(Corner::TopLeft),
1 => selected = Some(Corner::TopRight), 1 => selected = Some(Corner::TopRight),
2 => selected = Some(Corner::BottomLeft), 2 => selected = Some(Corner::BottomRight),
3 => selected = Some(Corner::BottomRight), 3 => selected = Some(Corner::BottomLeft),
_ => selected = None, _ => selected = None,
} }
break; break;

View file

@ -1,6 +1,6 @@
use bevy::prelude::*; // for glam::f32::Mat3 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 nannou_laser::{self as laser, Point};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -22,6 +22,12 @@ pub struct CropFilter {
pub enabled: bool pub enabled: bool
} }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ClipFilter {
pub enabled: bool,
pub mask: Vec<[f32; 2]>
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DimFilter { pub struct DimFilter {
pub intensity: f32 pub intensity: f32
@ -59,6 +65,7 @@ pub struct PointFilters{
pub scale: ScaleFilter, pub scale: ScaleFilter,
pub pincushion: PincushionFilter, pub pincushion: PincushionFilter,
pub crop: CropFilter, pub crop: CropFilter,
pub clip: ClipFilter,
} }
// list of enums deprecated in favour of struct // list of enums deprecated in favour of struct
@ -128,6 +135,7 @@ impl Default for PointFilters {
scale: ScaleFilter { factor: 1. }, scale: ScaleFilter { factor: 1. },
pincushion: PincushionFilter{k_x: 0.,k_x2: 0., k_y: 0., k_y2: 0.}, pincushion: PincushionFilter{k_x: 0.,k_x2: 0., k_y: 0., k_y2: 0.},
crop: CropFilter{ enabled: true }, 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) // also converts from world space to laser space (origin in center)
let s = 0xFFF as f32 / 2.; // let s = 0xFFF as f32 / 2.;
let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.]; // let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.];
laser::Point { laser::Point {
position: new_position, position: new_position,
.. point.clone() .. point.clone()
@ -322,7 +330,7 @@ impl Filter for CropFilter {
} }
fn reverse(&self, points: &LaserPoints) -> LaserPoints{ fn reverse(&self, points: &LaserPoints) -> LaserPoints{
// we cannot really add points, can we // we cannot really conjure up points, can we
return LaserPoints{ return LaserPoints{
points: points.points.clone(), points: points.points.clone(),
space: points.space, 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{ fn change_brightness(points: &LaserPoints, intensity: f32) -> LaserPoints{
let new_points = points.points.iter().map(|point| { let new_points = points.points.iter().map(|point| {
let mut color = point.color.clone(); let mut color = point.color.clone();

View file

@ -308,3 +308,26 @@ impl StreamSource{
} }
} }
} }
#[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,
}
}
}