From b25fc93997c09c68b241fe205ca3a14d5ac56aaa Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Wed, 2 Jul 2025 10:54:43 +0200 Subject: [PATCH] Test patterns, and glitchy pillow distortion --- src/bin/render_lines_gui.rs | 132 ++++++++++++++++++++++++++++++++---- src/trap/filters.rs | 69 +++++++++++++------ src/trap/laser.rs | 106 +++++++++++++++++++++++++++-- 3 files changed, 268 insertions(+), 39 deletions(-) diff --git a/src/bin/render_lines_gui.rs b/src/bin/render_lines_gui.rs index f88e7ce..4ae155b 100644 --- a/src/bin/render_lines_gui.rs +++ b/src/bin/render_lines_gui.rs @@ -12,7 +12,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::{LaserPoints, TMP_DESK_CLUBMAX}; +use trap_rust::trap::laser::{LaserPoints, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX}; 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; @@ -20,6 +20,8 @@ use std::sync::{mpsc, Arc}; use std::time::{Instant, Duration}; use std::collections::HashMap; +// use egui_dropdown::DropDownBox; + fn main() { nannou::app(model).update(update).run(); @@ -196,8 +198,7 @@ fn get_dac_configs() -> DacConfigMap{ DacId::Helios { id: 926298163 }, DacConfig{ name: "Helios#1".into(), - homography: python_cv_h_into_mat3(TMP_PYTHON_LASER_H), - filters: PointFilters::default(), + .. DacConfig::default() } ); dac_configs.insert( @@ -213,8 +214,7 @@ fn get_dac_configs() -> DacConfigMap{ }, DacConfig{ name: "ED - 192.168.8.101".into(), - homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), - filters: PointFilters::default(), + .. DacConfig::default() } ); dac_configs.insert( @@ -230,8 +230,8 @@ fn get_dac_configs() -> DacConfigMap{ }, DacConfig{ name: "ED - 192.168.9.101".into(), - homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), - filters: PointFilters::default(), + .. DacConfig::default() + // filters: PointFilters::default(), } ); dac_configs.insert( @@ -247,8 +247,7 @@ fn get_dac_configs() -> DacConfigMap{ }, DacConfig{ name: "Emulator".into(), - homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), - filters: PointFilters::default(), + .. DacConfig::default() } ); dac_configs @@ -357,8 +356,35 @@ fn model(app: &App) -> GuiModel { fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){ - let points: LaserPoints = (&model.current_lines).into(); + let current_points: LaserPoints = (&model.current_lines).into(); + // let points = LaserPoints { points: vec!( + // laser::Point{ + // position:[ 9.4, 7.2], + // color: [1.,1.,0.], + // weight: 0, + // }, + // laser::Point{ + // position:[ 12.4, 7.2], + // color: [1.,1.,0.], + // weight: 0, + // }, + // laser::Point{ + // position:[ 12.4, 4.2], + // color: [1.,1.,0.], + // weight: 0, + // }, + // laser::Point{ + // position:[ 9.4, 4.2], + // color: [1.,1.,0.], + // weight: 0, + // }, + // ), space: CoordinateSpace::World }; let space = &model.current_lines.space; + + // check which source should be used, and get points accordingly. + // potentially ignoring the points coming from the stream + let points = model.config.source.get_shape(current_points); + let pointno = points.points.len(); let new_points = model.config.filters.apply(&points); @@ -367,6 +393,11 @@ fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){ println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno); } + // on reconnect gives Unknown + // dbg!(&model.config); + // dbg!(&points.points[0]); + // dbg!(&new_laser_points[0]); + frame.add_lines(new_laser_points); return; } @@ -428,9 +459,17 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { // TODO: keeps looping on disconnect. println!("attempting to restart stream with DAC {:?}", dac.id()); let dac_id = dac.id(); + let config = match model.per_laser_config.contains_key(&dac.id()) { + true => &model.per_laser_config[&dac.id()], + false => { + println!("Found unknown DAC, try to register it in get_dac_configs()"); + dbg!(&dac.id()); + &DacConfig::default() + }, + }; match model .laser_api - .new_frame_stream(model.laser_model.clone(), laser_frame_producer) + .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) .detected_dac(dac) .build() { @@ -600,6 +639,73 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs"); + + let source = &mut stream_config.config.source; + // for source_option in STREAM_SOURCES { + // if ui.radio_value(source, source_option.clone(), format!("{:?}", &source_option)).changed() { + // println!("Clicked!") + // }; + + // } + egui::ComboBox::from_label("Source") + .selected_text(format!("{source:?}")) + .show_ui(ui, |ui| { + for source_option in STREAM_SOURCES { + if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() { + // let source = source_option; + stream_config.stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.source = source_option; + }).unwrap(); + }; + + } + // ui.selectable_value(source, StreamSource::CurrentLines, "Zmq Stream"); + // ui.selectable_value(source, StreamSource::Rectangle, "Rectangle"); + // ui.selectable_value(source, StreamSource::Grid, "Grid"); + }); + + + // ui.radio_value(source, StreamSource::Rectangle, "Rectangle"); + // ui.radio_value(source, StreamSource::Grid, "Grid"); + + // if ui.add(DropDownBox::from_iter( + // vec!(StreamSource::CurrentLines, StreamSource::Rectangle, StreamSource::Grid(5)), + // "test_dropbox", + // &mut stream_config.config.source, + // |ui, text| ui.selectable_label(false, text) + // )).changed() { + // println!("Changed source! {:?}", stream_config.config.source); + // let source = stream_config.config.source; + // stream_config.stream.send(move |laser_model: &mut LaserModel| { + // laser_model.config.source = source; + // }).unwrap(); + // } + // if ui.add( + // egui::ComboBox::from_label("Source") + // .selected_text(format!("{source:?}")) + // // .show_ui(ui, |ui| { + // // ui.selectable_value(source, StreamSource::CurrentLines, "Zmq Stream"); + // // ui.selectable_value(source, StreamSource::Rectangle, "Rectangle"); + // // ui.selectable_value(source, StreamSource::Grid(5), "Grid"); + // // }) + // ).changed() + // { + // let source = stream_config.config.source; + // stream_config.stream.send(move |laser_model: &mut LaserModel| { + // laser_model.config.source = source; + // }).unwrap(); + // } + + if ui + .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming")) + .changed() + { + let factor = stream_config.config.filters.dim.intensity; + stream_config.stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.dim.intensity = factor; + }).unwrap(); + } + if ui .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming")) .changed() @@ -622,7 +728,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -1.0..=1.).text("Pincushion x")) + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, 0.0..=2.).text("Pincushion x")) .changed() { let factor = stream_config.config.filters.pincushion.k_x; @@ -632,7 +738,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -1.0..=1.).text("Pincushion y")) + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, 0.0..=2.).text("Pincushion y")) .changed() { let factor = stream_config.config.filters.pincushion.k_y; diff --git a/src/trap/filters.rs b/src/trap/filters.rs index 501aba8..f2c780a 100644 --- a/src/trap/filters.rs +++ b/src/trap/filters.rs @@ -11,45 +11,45 @@ pub trait Filter { fn apply(&self, points: &LaserPoints) -> LaserPoints; } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct HomographyFilter { pub homography_matrix: Mat3 } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct CropFilter { pub enabled: bool } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct DimFilter { pub intensity: f32 } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct ScaleFilter { pub factor: f32 } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct PincushionFilter { pub k_x: f32, pub k_y: f32 } -#[derive(Serialize, Deserialize, Clone)] -// TODO consider moving to struct? -pub enum PointFilter { - Homography(HomographyFilter), - Dim(DimFilter), - Scale(ScaleFilter), - Pincushion(PincushionFilter), - Crop(CropFilter), -} +// #[derive(Serialize, Deserialize, Clone, Debug)] +// // TODO consider moving to struct? +// pub enum PointFilter { +// Homography(HomographyFilter), +// Dim(DimFilter), +// Scale(ScaleFilter), +// Pincushion(PincushionFilter), +// Crop(CropFilter), +// } -pub struct PointFilterList(Vec); // deprecated +// pub struct PointFilterList(Vec); // deprecated -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct PointFilters{ pub dim: DimFilter, pub homography: HomographyFilter, @@ -95,7 +95,7 @@ impl Default for PointFilters { homography: HomographyFilter::default(), dim: DimFilter{intensity: 0.5}, scale: ScaleFilter { factor: 1. }, - pincushion: PincushionFilter{k_x: 0., k_y: 0.}, + pincushion: PincushionFilter{k_x: 1., k_y: 1.}, crop: CropFilter{ enabled: true }, } } @@ -313,10 +313,37 @@ impl Filter for PincushionFilter { let projected_positions: Vec = points.points.iter().map(|point| { let p = point.position; - let new_position = [ - p[0] * (1. + self.k_x * p[0].powi(2)), - p[1] * (1. + self.k_y * p[1].powi(2)) - ]; + let mut radius = (p[0].powi(2) + p[1].powi(2)).sqrt(); + let new_position = if radius > 0. { + let theta = p[1].atan2(p[0]); + let radius_x = radius.powf(self.k_x); + let radius_y = radius.powf(self.k_y); + let x = radius_x * theta.cos(); + let y = radius_y * theta.sin(); + [x, y] + } else { + p + }; + + + // // Convert to polar coords: + // // float radius = length(v); + // if (radius > 0) + // { + // float theta = atan(v.y,v.x); + + // // Distort: + // radius = pow(radius, BarrelPower); + + // // Convert back to Cartesian: + // v.x = radius * cos(theta); + // v.y = radius * sin(theta); + // p.xy = v.xy * p.w; + // } + // let new_position = [ + // p[0] * (1. + self.k_x * p[0].powi(2)), + // p[1] * (1. + self.k_y * p[1].powi(2)) + // ]; laser::Point { position: new_position, diff --git a/src/trap/laser.rs b/src/trap/laser.rs index 8a8ac1a..1394a4c 100644 --- a/src/trap/laser.rs +++ b/src/trap/laser.rs @@ -2,10 +2,11 @@ use bevy::prelude::*; use nannou_laser as laser; use std::time::Instant; use serde::{Deserialize, Serialize}; -use crate::trap::{filters::{PointFilter, PointFilters}, tracks::CoordinateSpace}; +use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace}; use super::tracks::{RenderableLines}; + pub struct LaserPoints{ pub points: Vec, pub space: CoordinateSpace @@ -81,15 +82,29 @@ pub struct LaserTimer { } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct DacConfig{ // #[serde(with = "DacIdSerializable")] // id: DacId, pub name: String, - pub homography: Mat3, - pub filters: PointFilters + // pub homography: Mat3, + pub source: StreamSource, + pub filters: PointFilters, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum StreamSource { + CurrentLines, + Rectangle, + Grid, // lines + Circle, // segments + Spiral, +} + +// usefull to create pull downs with an iterator +pub const STREAM_SOURCES: [StreamSource; 5] = [StreamSource::CurrentLines, StreamSource::Rectangle, StreamSource::Grid, StreamSource::Circle, StreamSource::Spiral]; + + const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H); const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX); @@ -98,6 +113,87 @@ impl Default for DacConfig{ fn default() -> DacConfig{ //DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY } // DacConfig { name: "Unknown".into(), homography: LASER_H_CM } - DacConfig { name: "Unknown".into(), homography: LASER_H, filters: PointFilters::default().with_homography(LASER_H) } + DacConfig { name: "Unknown".into(), source: StreamSource::CurrentLines, filters: PointFilters::default().with_homography(LASER_H) } } } + + +impl Default for LaserPoints { + fn default() -> LaserPoints { + LaserPoints { points: Vec::new(), space: CoordinateSpace::World } + } +} + +// the different shapes that override the provided lines if needed +impl StreamSource{ + pub fn get_shape(&self, current_lines: LaserPoints) -> LaserPoints { + match self { + Self::CurrentLines => current_lines, + Self::Rectangle => LaserPoints { points: vec!( + laser::Point{ + position:[0xFFF as f32, 0xFFF as f32], + color: [1.,1.,1.], + weight: 0, + }, + laser::Point{ + position:[0xFFF as f32, 0.0], + color: [1.,1.,1.], + weight: 0, + }, + laser::Point{ + position:[0.0, 0.0], + color: [1.,1.,1.], + weight: 0, + }, + laser::Point{ + position:[0.0, 0xFFF as f32], + color: [1.,1.,1.], + weight: 0, + }, + laser::Point{ + position:[0xFFF as f32, 0xFFF as f32], + color: [1.,1.,1.], + weight: 0, + }, + ), space: CoordinateSpace::Laser }, + Self::Grid => { + let mut points = Vec::new(); + for i in (0..=0xFFF).step_by(0xFFF / 5) { + points.push(laser::Point{ + position:[i as f32, 0.], + color: [0., 0., 0.], + weight: 0, + }); + for j in (0..=0xFFF).step_by(0xFFF / 50) { + points.push(laser::Point{ + position:[i as f32, j as f32], + color: [1.,1.,1.], + weight: 0, + }); + } + points.push(points[points.len()-1].blanked()); + } + + for i in (0..=0xFFF).step_by(0xFFF / 5) { + points.push(laser::Point{ + position:[0., i as f32], + color: [0., 0., 0.], + weight: 0, + }); + for j in (0..=0xFFF).step_by(0xFFF / 50) { + points.push(laser::Point{ + position:[j as f32, i as f32], + color: [1.,1.,1.], + weight: 0, + }); + } + points.push(points[points.len()-1].blanked()); + } + + + LaserPoints { points, space: CoordinateSpace::Laser } + }, + _ => LaserPoints::default(), + } + } +} \ No newline at end of file