Compare commits

..

No commits in common. "d989cdaa869ab85fd6e92cd34bf1ebfd11f174be" and "0ca03aa1e77f1efe9de7fe6038365bcf34446dee" have entirely different histories.

6 changed files with 278 additions and 608 deletions

View file

@ -2,40 +2,34 @@
//! A clone of the `laser_frame_stream.rs` example that allows for configuring laser settings via a //! A clone of the `laser_frame_stream.rs` example that allows for configuring laser settings via a
//! UI. //! UI.
// use bevy_nannou::prelude::DARK_GRAY; use bevy::math::Mat3;
use bevy_nannou::prelude::DARK_GRAY;
// use nannou::lyon::geom::euclid::Transform2D; // use nannou::lyon::geom::euclid::Transform2D;
use nannou::{geom::Rect, math::map_range as nannou_map_range}; use nannou::{geom::Rect, math::map_range as nannou_map_range};
use nannou::prelude::*; use nannou::prelude::*;
// use nannou_egui::egui::emath::inverse_lerp; use nannou_egui::egui::emath::inverse_lerp;
use nannou_egui::{self, egui, Egui}; use nannou_egui::{self, egui, Egui};
use nannou_laser::DacId; use nannou_laser::DacId;
use nannou_laser::{self as laser}; use nannou_laser::{self as laser, util::map_range};
use serde_json::Result; use serde_json::Result;
use trap_rust::trap::filters::PointFilters; use serde::{Serialize,Deserialize};
use trap_rust::trap::laser::{LaserPoints, TMP_DESK_CLUBMAX}; use trap_rust::trap::laser::TMP_DESK_CLUBMAX;
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::{apply_homography_matrix, python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{LaserPoints, RenderableLines}};
use zmq::Socket; use zmq::Socket;
use std::sync::{mpsc, Arc}; use std::sync::{mpsc, Arc};
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use std::collections::HashMap; use std::collections::HashMap;
fn main() { fn main() {
nannou::app(model).update(update).run(); nannou::app(model).update(update).run();
} }
pub struct StreamConfig{ struct Model {
pub stream: laser::FrameStream<LaserModel>,
pub config: DacConfig,
}
type StreamConfigMap = HashMap<DacId, StreamConfig>;
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.
laser_api: Arc<laser::Api>, laser_api: Arc<laser::Api>,
// All of the live stream handles. // All of the live stream handles.
laser_streams: StreamConfigMap, laser_streams: Vec<laser::FrameStream<LaserModel>>,
// A copy of the state that will live on the laser thread so we can present a GUI. // A copy of the state that will live on the laser thread so we can present a GUI.
laser_model: LaserModel, laser_model: LaserModel,
// A copy of the laser settings so that we can control them with the GUI. // A copy of the laser settings so that we can control them with the GUI.
@ -52,7 +46,6 @@ struct GuiModel {
// dimming_factor: f32, // dimming_factor: f32,
lost_alpha: f32, lost_alpha: f32,
connected: bool, connected: bool,
selected_stream: Option<DacId>,
// canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>, // canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>,
// dragging: bool, // dragging: bool,
} }
@ -90,8 +83,7 @@ impl Default for LaserSettings {
fn setup_zmq() -> Socket{ fn setup_zmq() -> Socket{
// let url = "tcp://100.109.175.82:99174"; let url = "tcp://100.109.175.82:99174";
let url = "tcp://127.0.0.1:99174";
let context = zmq::Context::new(); let context = zmq::Context::new();
let subscriber = context.socket(zmq::SUB).unwrap(); let subscriber = context.socket(zmq::SUB).unwrap();
subscriber.set_conflate(true).unwrap(); // only keep latest entry subscriber.set_conflate(true).unwrap(); // only keep latest entry
@ -107,7 +99,7 @@ fn setup_zmq() -> Socket{
// fn zmq_receive(subscriber: &Socket, laser_streams: &Vec<laser::FrameStream<LaserModel>>) { // fn zmq_receive(subscriber: &Socket, laser_streams: &Vec<laser::FrameStream<LaserModel>>) {
/// Receive items if available on the queue and update Model with the new data /// Receive items if available on the queue and update Model with the new data
fn zmq_receive(model: &mut GuiModel) { fn zmq_receive(model: &mut Model) {
let subscriber = &model.zmq; let subscriber = &model.zmq;
let mut items = [ let mut items = [
subscriber.as_poll_item(zmq::POLLIN) subscriber.as_poll_item(zmq::POLLIN)
@ -155,10 +147,10 @@ fn zmq_receive(model: &mut GuiModel) {
// println!("receive {}", lines.lines.len()); // println!("receive {}", lines.lines.len());
for (_dac, stream_config) in (&model.laser_streams).into_iter() { for laser_stream in (&model.laser_streams).into_iter() {
// let lines = get_laser_lines(version); // let lines = get_laser_lines(version);
let lines_for_laser: RenderableLines = lines.clone(); let lines_for_laser: RenderableLines = lines.clone();
let sending = stream_config.stream.send(move |laser| { let sending = laser_stream.send(move |laser| {
let laser_lines: RenderableLines = lines_for_laser; let laser_lines: RenderableLines = lines_for_laser;
laser.current_lines = laser_lines; laser.current_lines = laser_lines;
}); });
@ -185,7 +177,6 @@ fn zmq_receive(model: &mut GuiModel) {
type DacConfigMap = HashMap<DacId, DacConfig>; type DacConfigMap = HashMap<DacId, DacConfig>;
// Some hardcoded config. Not spending time on reading/writing config atm. // Some hardcoded config. Not spending time on reading/writing config atm.
fn get_dac_configs() -> DacConfigMap{ fn get_dac_configs() -> DacConfigMap{
@ -194,8 +185,7 @@ fn get_dac_configs() -> DacConfigMap{
DacId::Helios { id: 926298163 }, DacId::Helios { id: 926298163 },
DacConfig{ DacConfig{
name: "Helios#1".into(), name: "Helios#1".into(),
homography: python_cv_h_into_mat3(TMP_PYTHON_LASER_H), homography: python_cv_h_into_mat3(TMP_PYTHON_LASER_H)
filters: PointFilters::default(),
} }
); );
dac_configs.insert( dac_configs.insert(
@ -211,8 +201,7 @@ fn get_dac_configs() -> DacConfigMap{
}, },
DacConfig{ DacConfig{
name: "ED - 192.168.8.101".into(), name: "ED - 192.168.8.101".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX)
filters: PointFilters::default(),
} }
); );
dac_configs.insert( dac_configs.insert(
@ -228,31 +217,13 @@ fn get_dac_configs() -> DacConfigMap{
}, },
DacConfig{ DacConfig{
name: "ED - 192.168.9.101".into(), name: "ED - 192.168.9.101".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX)
filters: PointFilters::default(),
}
);
dac_configs.insert(
DacId::EtherDream {
mac_address: [
18,
52,
86,
120,
154,
188,
],
},
DacConfig{
name: "Emulator".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX),
filters: PointFilters::default(),
} }
); );
dac_configs dac_configs
} }
fn model(app: &App) -> GuiModel { fn model(app: &App) -> Model {
// Create a window to receive keyboard events. // Create a window to receive keyboard events.
let w_id_lasersettings = app let w_id_lasersettings = app
.new_window() .new_window()
@ -324,7 +295,7 @@ fn model(app: &App) -> GuiModel {
}); });
// We'll use a `Vec` to collect laser streams as they appear. // We'll use a `Vec` to collect laser streams as they appear.
let laser_streams = HashMap::new(); //vec![]; let laser_streams = vec![];
// A user-interface to tweak the settings. // A user-interface to tweak the settings.
let window = app.window(w_id_lasersettings).unwrap(); let window = app.window(w_id_lasersettings).unwrap();
@ -334,7 +305,7 @@ fn model(app: &App) -> GuiModel {
let current_lines = RenderableLines::new(); //Vec::new(); let current_lines = RenderableLines::new(); //Vec::new();
GuiModel { Model {
laser_api, laser_api,
laser_settings, laser_settings,
laser_model, laser_model,
@ -347,33 +318,181 @@ fn model(app: &App) -> GuiModel {
lost_alpha: 1., lost_alpha: 1.,
connected: true, connected: true,
per_laser_config: get_dac_configs(), per_laser_config: get_dac_configs(),
selected_stream: None,
// canvas_transform: Transform2D // canvas_transform: Transform2D
// dimming_factor: 1., // dimming_factor: 1.,
} }
} }
// Draw lines or points based on the `DrawMode`.
// fn add_points<I>(points: I, scale: f32, frame: &mut laser::Frame)
// where
// I: IntoIterator,
// I::Item: AsRef<laser::Point>,
// {
// let points = points.into_iter().map(|p| {
// let mut p = p.as_ref().clone();
// p.position[0] *= scale;
// p.position[1] *= scale;
// p
// });
// frame.add_lines(points);
// }
const LASER_MIN: f32 = -1.0;
const LASER_MAX: f32 = 1.0;
fn within_laser_bounds(position: &[f32; 2]) -> bool {
!(position[0] < LASER_MIN || position[0] > LASER_MAX || position[1] < LASER_MIN || position[1] > LASER_MAX)
}
fn interpolate_to_bounds(a: &[f32;2], b: &[f32;2]) {
//let t_a = inverse_lerp(a)
}
// From ChatGTP: Lian-Barsky Algorithm for line segment cropping
fn clip_line_to_bounds(
p1: &[f32; 2],
p2: &[f32; 2],
) -> Option<([f32; 2], [f32; 2])> {
let min = [LASER_MIN, LASER_MIN];
let max = [LASER_MAX, LASER_MAX];
let dx = p2[0] - p1[0];
let dy = p2[1] - p1[1];
let mut t0 = 0.0;
let mut t1 = 1.0;
let checks = [
(-dx, p1[0] - min[0]), // Left
(dx, max[0] - p1[0]), // Right
(-dy, p1[1] - min[1]), // Bottom
(dy, max[1] - p1[1]), // Top
];
for (p, q) in checks {
if p == 0.0 {
if q < 0.0 {
return None; // Line is parallel and outside
}
} else {
let r = q / p;
if p < 0.0 {
if r > t1 {
return None;
} else if r > t0 {
t0 = r;
}
} else {
if r < t0 {
return None;
} else if r < t1 {
t1 = r;
}
}
}
}
let clipped_p1 = [p1[0] + t0 * dx, p1[1] + t0 * dy];
let clipped_p2 = [p1[0] + t1 * dx, p1[1] + t1 * dy];
Some((clipped_p1, clipped_p2))
}
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){ fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
let points: LaserPoints = (&model.current_lines).into(); let points: LaserPoints = (&model.current_lines).into();
let space = &model.current_lines.space; let space = &model.current_lines.space;
let pointno = points.points.len(); let pointno = points.len();
let new_points = model.config.filters.apply(&points); // dbg!(&model.config.name);
let new_laser_points = new_points.points;
if new_laser_points.len() < pointno { let mut new_points = Vec::new();
println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno); let projected_positions: Vec<[f32;2]> = points.iter().map(|point| {
let p = point.position;
let new_position = match space {
CoordinateSpace::World => apply_homography_matrix(model.config.homography, &p),
CoordinateSpace::Laser => p,
_ => panic!("Invalid coordinate space"),
};
// let new_position = apply_homography_matrix(LASER_H, &p);
// let s = 1.; // when using TMP_PYTHON_LASER_H_FOR_NANNOU -- doesn't work?
let s = 0xFFF as f32 / 2.; // when using TMP_PYTHON_LASER_H
[new_position[0]/s - 1., new_position[1]/s - 1.]
}).collect();
for (id, position ) in projected_positions.iter().enumerate() {
let point = points[id] ;
let mut new_positions: Vec<[f32;2]> = Vec::new();
// const LASER_MIN: f32 = -1.0;
// const LASER_MAX: f32 = 1.0;
if !within_laser_bounds(position) {
let mut either = false;
if id > 0 {
let prev_position = projected_positions[id-1];
if within_laser_bounds(&prev_position) {
either = true;
// interpolate with prev
let clip = clip_line_to_bounds(&prev_position, position);
if let Some((p1, p2)) = clip {
new_positions.push(p1);
new_positions.push(p2);
}
}
}
if id < (projected_positions.len()-1) {
let next_position = projected_positions[id+1];
if within_laser_bounds(&next_position) {
either = true;
// interpolate with next
let clip = clip_line_to_bounds(position, &next_position);
if let Some((p1, p2)) = clip {
new_positions.push(p1);
new_positions.push(p2);
}
}
}
if !either {
// if neither prev nor next is withint bounds, point can be ditched
continue;
}
} else {
new_positions.push(position.clone());
}
let mut color = point.color.clone();
if model.dimming < 1.0 {
color[0] *= model.dimming;
color[1] *= model.dimming;
color[2] *= model.dimming;
}
for position in new_positions {
// let pos: [f32; 2] = position.clone();
let new_point = laser::Point {
position,
color,
.. point.clone()
};
new_points.push(new_point);
}
} }
frame.add_lines(new_laser_points); if new_points.len() < pointno {
return; println!("Cropped Points {} (was: {})", new_points.len(), pointno);
}
// println!("{:?}", new_points);
frame.add_lines(new_points);
} }
fn raw_window_event(_app: &App, model: &mut GuiModel, event: &nannou::winit::event::WindowEvent) { fn raw_window_event(_app: &App, model: &mut Model, event: &nannou::winit::event::WindowEvent) {
model.egui.handle_raw_event(event); model.egui.handle_raw_event(event);
} }
fn update(_app: &App, model: &mut GuiModel, update: Update) { fn update(_app: &App, model: &mut Model, update: Update) {
// First, check for new laser DACs. // First, check for new laser DACs.
for dac in model.dac_rx.try_recv() { for dac in model.dac_rx.try_recv() {
println!("Detected DAC {:?}!", dac.id()); println!("Detected DAC {:?}!", dac.id());
@ -388,69 +507,53 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
let stream = model let stream = model
.laser_api .laser_api
.new_frame_stream(model.laser_model.with_config(config), laser_frame_producer) .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
.detected_dac(dac.clone()) .detected_dac(dac)
.build() .build()
.expect("failed to establish stream with newly detected DAC"); .expect("failed to establish stream with newly detected DAC");
model.laser_streams.insert(dac.id(), StreamConfig{ stream, config: config.clone() }); model.laser_streams.push(stream);
} }
// Check if any streams have dropped out (e.g network issues, DAC turned off) and attempt to // Check if any streams have dropped out (e.g network issues, DAC turned off) and attempt to
// start them again. // start them again.
let mut dropped = vec![]; let mut dropped = vec![];
for (dac_id, stream_config) in model.laser_streams.iter() { for (i, stream) in model.laser_streams.iter().enumerate() {
if stream_config.stream.is_closed() { if stream.is_closed() {
dropped.push(dac_id.clone()); dropped.push(i);
}
}
for i in dropped.into_iter().rev() {
let stream = model.laser_streams.remove(i);
let dac = stream
.dac()
.expect("`dac` returned `None` even though one was specified during stream creation");
let res = stream
.close()
.expect("stream was unexpectedly already closed from another stream handle")
.expect("failed to join stream thread");
if let Err(err) = res {
eprintln!("Stream closed due to an error: {}", err);
}
println!("attempting to restart stream with DAC {:?}", dac.id());
match model
.laser_api
.new_frame_stream(model.laser_model.clone(), laser_frame_producer)
.detected_dac(dac)
.build()
{
Err(err) => eprintln!("failed to restart stream: {}", err),
Ok(stream) => model.laser_streams.push(stream),
} }
} }
for dac_id in dropped.into_iter().rev() {
// let stream = ;
let s = model.laser_streams.remove(&dac_id);
if model.selected_stream == Some(dac_id){
model.selected_stream = None;
}
if let Some(stream_config) = s{
let dac = stream_config.stream
.dac()
.expect("`dac` returned `None` even though one was specified during stream creation");
let res = stream_config.stream
.close()
.expect("stream was unexpectedly already closed from another stream handle")
.expect("failed to join stream thread");
if let Err(err) = res {
eprintln!("Stream closed due to an error: {}", err);
}
// TODO: keeps looping on disconnect.
println!("attempting to restart stream with DAC {:?}", dac.id());
let dac_id = dac.id();
match model
.laser_api
.new_frame_stream(model.laser_model.clone(), laser_frame_producer)
.detected_dac(dac)
.build()
{
Err(err) => eprintln!("failed to restart stream: {}", err),
Ok(stream) => {model.laser_streams.insert(dac_id, StreamConfig{stream, config: stream_config.config});},
}
}
}
// check if new messages have arrived. Update the model with new data.
zmq_receive(model); zmq_receive(model);
// Update the GUI. // Update the GUI.
let GuiModel { let Model {
ref mut egui, ref mut egui,
ref mut laser_streams, ref laser_streams,
ref mut laser_model, ref mut laser_model,
ref mut laser_settings, ref mut laser_settings,
ref mut per_laser_config,
ref mut selected_stream,
ref mut current_lines,
.. ..
} = *model; } = *model;
@ -467,21 +570,34 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
} }
ui.heading("Laser Points"); ui.heading("Laser Points");
ui.separator();
ui.add(egui::Label::new(format!("Lines {}", current_lines.lines.len())));
ui.add(egui::Label::new(format!("Points {}", current_lines.point_count())));
ui.heading("General settings"); ui.separator();
ui.add(egui::Label::new(format!("Lines {}", model.current_lines.lines.len())));
ui.add(egui::Label::new(format!("Points {}", model.current_lines.point_count())));
ui.heading("Laser Settings");
if ui
.add(egui::Slider::new(&mut model.laser_model.dimming, 0.0..=1.).text("Dimming"))
.changed()
{
for laser_stream in laser_streams {
let factor = model.laser_model.dimming;
// let lines = get_laser_lines(version);
laser_stream.send(move |laser| {
laser.dimming = factor;
}).unwrap();
}
}
if ui if ui
.add(egui::Slider::new(&mut laser_settings.point_hz, 1_000..=50_000).text("DAC PPS")) .add(egui::Slider::new(&mut laser_settings.point_hz, 1_000..=50_000).text("DAC PPS"))
.changed() .changed()
{ {
let hz = laser_settings.point_hz; let hz = laser_settings.point_hz;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_point_hz(hz).ok(); stream.set_point_hz(hz).ok();
} }
} }
if ui if ui
@ -489,20 +605,20 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
.changed() .changed()
{ {
let latency = laser_settings.latency_points; let latency = laser_settings.latency_points;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_latency_points(latency).ok(); stream.set_latency_points(latency).ok();
} }
} }
if ui if ui
.add(egui::Slider::new(&mut laser_settings.frame_hz, 1..=120).text("Target FPS")) .add(egui::Slider::new(&mut laser_settings.frame_hz, 1..=120).text("Target FPS"))
.changed() .changed()
{ {
let hz = laser_settings.frame_hz; let hz = laser_settings.frame_hz;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_frame_hz(hz).ok(); stream.set_frame_hz(hz).ok();
} }
} }
ui.separator(); ui.separator();
ui.heading("Laser Path Interpolation"); ui.heading("Laser Path Interpolation");
@ -511,34 +627,34 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
.checkbox(&mut laser_settings.enable_optimisations, "Optimize Path") .checkbox(&mut laser_settings.enable_optimisations, "Optimize Path")
.changed() .changed()
{ {
for (_dac_id, stream_config) in laser_streams.iter() { for stream in laser_streams {
stream_config.stream stream
.enable_optimisations(laser_settings.enable_optimisations) .enable_optimisations(laser_settings.enable_optimisations)
.ok(); .ok();
} }
} }
if ui if ui
.add( .add(
egui::Slider::new(&mut laser_settings.distance_per_point, 0.01..=1.0) egui::Slider::new(&mut laser_settings.distance_per_point, 0.01..=1.0)
.text("Distance Per Point"), .text("Distance Per Point"),
) )
.changed() .changed()
{ {
let distance = laser_settings.distance_per_point; let distance = laser_settings.distance_per_point;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_distance_per_point(distance).ok(); stream.set_distance_per_point(distance).ok();
} }
} }
if ui if ui
.add( .add(
egui::Slider::new(&mut laser_settings.blank_delay_points, 0..=32) egui::Slider::new(&mut laser_settings.blank_delay_points, 0..=32)
.text("Blank Delay (Points)"), .text("Blank Delay (Points)"),
) )
.changed() .changed()
{ {
let delay = laser_settings.blank_delay_points; let delay = laser_settings.blank_delay_points;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_blank_delay_points(delay).ok(); stream.set_blank_delay_points(delay).ok();
} }
} }
let mut degrees = rad_to_deg(laser_settings.radians_per_point); let mut degrees = rad_to_deg(laser_settings.radians_per_point);
@ -548,119 +664,22 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
{ {
let radians = deg_to_rad(degrees); let radians = deg_to_rad(degrees);
laser_settings.radians_per_point = radians; laser_settings.radians_per_point = radians;
for (_dac_id, stream) in laser_streams.iter() { for stream in laser_streams {
stream.stream.set_radians_per_point(radians).ok(); stream.set_radians_per_point(radians).ok();
} }
} }
ui.separator(); ui.heading("Connected DACs");
ui.heading("Laser specific settings");
if laser_streams.len() < 1 {
if laser_streams.is_empty() { ui.label("no streams");
ui.label("No dacs available");
} else {
ui.horizontal(|ui| {
for (dac_id, _stream) in laser_streams.iter() {
ui.selectable_value(
selected_stream,
Some(dac_id.clone()),
if let Some(config) = per_laser_config.get(&dac_id) { config.name.clone() } else { "DAC".into() }
);
}
});
} }
if let Some(selected_stream_value) = selected_stream {
ui.separator();
ui.add(egui::Label::new(format!("{:?}", selected_stream_value)));
let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs");
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.scale.factor, 0.0..=2.).text("Scale"))
.changed()
{
let factor = stream_config.config.filters.scale.factor;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.scale.factor = factor;
}).unwrap();
}
if ui
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -1.0..=1.).text("Pincushion x"))
.changed()
{
let factor = stream_config.config.filters.pincushion.k_x;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.pincushion.k_x = factor;
}).unwrap();
}
if ui
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -1.0..=1.).text("Pincushion y"))
.changed()
{
let factor = stream_config.config.filters.pincushion.k_y;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.pincushion.k_y = factor;
}).unwrap();
}
if ui
.checkbox(&mut stream_config.config.filters.crop.enabled ,"Crop")
.changed()
{
let enabled = stream_config.config.filters.crop.enabled;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.crop.enabled = enabled;
}).unwrap();
}
for stream in laser_streams {
let dac = stream
// ui.heading("Connected DACs"); .dac()
.expect("`dac` returned `None` even though one was specified during stream creation");
// if laser_streams.is_empty() { ui.add(egui::Label::new(format!("{:?}", dac.id())));
// ui.label("no streams");
// }
// for (_dac_id, stream_config) in laser_streams.iter_mut() {
// let dac: laser::DetectedDac = stream_config.stream
// .dac()
// .expect("`dac` returned `None` even though one was specified during stream creation");
// ui.add(egui::Label::new(format!("{:?}", dac.id())));
// if ui
// // todo : from custom dac config:
// .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
// .changed()
// {
// for (_dac_id, laser_stream) in laser_streams.iter() {
// let factor = model.laser_model.dimming;
// // let lines = get_laser_lines(version);
// laser_stream.stream.send(move |laser| {
// // laser: LaserModel
// laser.config.filters.dim.intensity = factor;
// // laser.dimming = factor;
// }).unwrap();
// }
// }
// }
//if egui::ComboBox::from_label("Homography") //if egui::ComboBox::from_label("Homography")
// .selected_text(format!("{radio:?}")) // .selected_text(format!("{radio:?}"))
@ -671,25 +690,23 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// }) // })
// .changed() { // .changed() {
// let sending = laser_stream.send(move |laser| { // let sending = laser_stream.send(move |laser| {
// let laser_lines: RenderableLines = lines_for_laser; // let laser_lines: RenderableLines = lines_for_laser;
// laser.current_lines = laser_lines; // laser.current_lines = laser_lines;
// }); // });
// if let Err(e) = sending { // if let Err(e) = sending {
// println!("Error sending to laser! {e:?}"); // println!("Error sending to laser! {e:?}");
// } // }
// }; // };
} else {
ui.label("Select a DAC");
} }
}); });
} }
fn view_laser_settings(_app: &App, model: &GuiModel, frame: Frame) { fn view_laser_settings(_app: &App, model: &Model, frame: Frame) {
model.egui.draw_to_frame(&frame).unwrap(); model.egui.draw_to_frame(&frame).unwrap();
} }
fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) { fn view_line_canvas(app: &App, model: &Model, frame: Frame) {
// get canvas to draw on // get canvas to draw on
let draw = app.draw(); let draw = app.draw();
@ -783,15 +800,15 @@ fn style() -> egui::Style {
style style
} }
fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) { fn mouse_moved(_app: &App, _model: &mut Model, _pos: Point2) {
} }
fn mouse_pressed(_app: &App, _model: &mut GuiModel, _button: MouseButton) { fn mouse_pressed(_app: &App, _model: &mut Model, _button: MouseButton) {
// _model.dragging // _model.dragging
} }
fn mouse_released(_app: &App, _model: &mut GuiModel, _button: MouseButton) {} fn mouse_released(_app: &App, _model: &mut Model, _button: MouseButton) {}
fn mouse_wheel(_app: &App, _model: &mut GuiModel, _dt: MouseScrollDelta, _phase: TouchPhase) { fn mouse_wheel(_app: &App, _model: &mut Model, _dt: MouseScrollDelta, _phase: TouchPhase) {
// canvas zoom // canvas zoom
} }

View file

@ -1,333 +0,0 @@
use bevy::prelude::*; // for glam::f32::Mat3
use crate::trap::{laser::{apply_homography_matrix, LaserPoints}, tracks::CoordinateSpace};
use nannou_laser::{self as laser, Point};
use serde::{Deserialize, Serialize};
pub trait Filter {
// fn set_config(&self)
// fn set_config(&self)
fn apply(&self, points: &LaserPoints) -> LaserPoints;
}
#[derive(Serialize, Deserialize, Clone)]
pub struct HomographyFilter {
pub homography_matrix: Mat3
}
#[derive(Serialize, Deserialize, Clone)]
pub struct CropFilter {
pub enabled: bool
}
#[derive(Serialize, Deserialize, Clone)]
pub struct DimFilter {
pub intensity: f32
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ScaleFilter {
pub factor: f32
}
#[derive(Serialize, Deserialize, Clone)]
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),
}
pub struct PointFilterList(Vec<PointFilter>); // deprecated
#[derive(Serialize, Deserialize, Clone)]
pub struct PointFilters{
pub dim: DimFilter,
pub homography: HomographyFilter,
pub scale: ScaleFilter,
pub pincushion: PincushionFilter,
pub crop: CropFilter,
}
// list of enums deprecated in favour of struct
// impl Default for PointFilterList {
// fn default() -> Self {
// // let crop_filter = CropFilter{};
// Self (
// vec![
// PointFilter::Dim(DimFilter{intensity: 0.5}),
// PointFilter::Pincushion(PincushionFilter{k_x: 0., k_y: 0.}),
// PointFilter::Crop(CropFilter{}),
// ]
// )
// }
// }
impl PointFilters {
pub fn apply(&self, points: &LaserPoints) -> LaserPoints{
let mut p = self.dim.apply(points);
p = self.homography.apply(&p);
p = self.scale.apply(&p);
p = self.pincushion.apply(&p);
p = self.crop.apply(&p);
p
}
pub fn with_homography(mut self, h: Mat3) -> Self{
self.homography.homography_matrix = h;
self
}
}
impl Default for PointFilters {
fn default() -> Self {
// let crop_filter = CropFilter{};
Self {
homography: HomographyFilter::default(),
dim: DimFilter{intensity: 0.5},
scale: ScaleFilter { factor: 1. },
pincushion: PincushionFilter{k_x: 0., k_y: 0.},
crop: CropFilter{ enabled: true },
}
}
}
impl Filter for HomographyFilter {
fn apply(&self, points: &LaserPoints) -> LaserPoints{
let space = points.space;
let projected_positions: Vec<laser::Point> = points.points.iter().map(|point| {
let p = point.position;
let new_position = match space {
CoordinateSpace::World => apply_homography_matrix(self.homography_matrix, &p),
CoordinateSpace::Laser => p,
_ => panic!("Invalid coordinate space"),
};
// let new_position = apply_homography_matrix(LASER_H, &p);
// let s = 1.; // when using TMP_PYTHON_LASER_H_FOR_NANNOU -- doesn't work?
let s = 0xFFF as f32 / 2.; // when using TMP_PYTHON_LASER_H
let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.];
laser::Point {
position: normalised_pos,
.. point.clone()
}
}).collect();
LaserPoints{
points: projected_positions,
space: CoordinateSpace::Laser
}
}
}
impl Default for HomographyFilter{
fn default() -> Self {
return Self { homography_matrix: Mat3::IDENTITY }
}
}
const LASER_MIN: f32 = -1.0;
const LASER_MAX: f32 = 1.0;
fn within_laser_bounds(position: &[f32; 2]) -> bool {
!(position[0] < LASER_MIN || position[0] > LASER_MAX || position[1] < LASER_MIN || position[1] > LASER_MAX)
}
// From ChatGTP: Lian-Barsky Algorithm for line segment cropping
fn clip_line_to_bounds(
p1: &[f32; 2],
p2: &[f32; 2],
) -> Option<([f32; 2], [f32; 2])> {
let min = [LASER_MIN, LASER_MIN];
let max = [LASER_MAX, LASER_MAX];
let dx = p2[0] - p1[0];
let dy = p2[1] - p1[1];
let mut t0 = 0.0;
let mut t1 = 1.0;
let checks = [
(-dx, p1[0] - min[0]), // Left
(dx, max[0] - p1[0]), // Right
(-dy, p1[1] - min[1]), // Bottom
(dy, max[1] - p1[1]), // Top
];
for (p, q) in checks {
if p == 0.0 {
if q < 0.0 {
return None; // Line is parallel and outside
}
} else {
let r = q / p;
if p < 0.0 {
if r > t1 {
return None;
} else if r > t0 {
t0 = r;
}
} else {
if r < t0 {
return None;
} else if r < t1 {
t1 = r;
}
}
}
}
let clipped_p1 = [p1[0] + t0 * dx, p1[1] + t0 * dy];
let clipped_p2 = [p1[0] + t1 * dx, p1[1] + t1 * dy];
Some((clipped_p1, clipped_p2))
}
impl Filter for CropFilter {
fn apply(&self, points: &LaserPoints) -> LaserPoints {
if !self.enabled {
// don't modify if disabled
return LaserPoints{
points: points.points.clone(),
space: points.space,
};
}
let space = points.space;
let mut new_points = Vec::new();
for (id, point) in points.points.iter().enumerate() {
let mut new_positions: Vec<[f32;2]> = Vec::new();
// const LASER_MIN: f32 = -1.0;
// const LASER_MAX: f32 = 1.0;
if !within_laser_bounds(&point.position) {
let mut either = false;
if id > 0 {
let prev_position = points.points[id-1].position;
if within_laser_bounds(&prev_position) {
either = true;
// interpolate with prev
let clip = clip_line_to_bounds(&prev_position, &point.position);
if let Some((p1, p2)) = clip {
new_positions.push(p1);
new_positions.push(p2);
}
}
}
if id < (points.points.len()-1) {
let next_position = points.points[id+1].position;
if within_laser_bounds(&next_position) {
either = true;
// interpolate with next
let clip = clip_line_to_bounds(&point.position, &next_position);
if let Some((p1, p2)) = clip {
new_positions.push(p1);
new_positions.push(p2);
}
}
}
if !either {
// if neither prev nor next is withint bounds, point can be ditched
continue;
}
} else {
new_positions.push(point.position.clone());
}
for position in new_positions {
// let pos: [f32; 2] = position.clone();
let new_point = laser::Point {
position,
.. point.clone()
};
new_points.push(new_point);
}
}
LaserPoints{
points: new_points,
space
}
}
}
impl Filter for DimFilter {
fn apply(&self, points: &LaserPoints) -> LaserPoints {
let new_points = points.points.iter().map(|point| {
let mut color = point.color.clone();
if self.intensity != 1.0 {
color[0] *= self.intensity;
color[1] *= self.intensity;
color[2] *= self.intensity;
}
Point::new(point.position, color)
}).collect();
LaserPoints {
points: new_points,
space: points.space
}
}
}
impl Filter for ScaleFilter {
fn apply(&self, points: &LaserPoints) -> LaserPoints {
let new_points = points.points.iter().map(|point| {
let mut position = point.position.clone();
if self.factor != 1.0 {
position[0] *= self.factor;
position[1] *= self.factor;
}
Point::new(position, point.color)
}).collect();
LaserPoints {
points: new_points,
space: points.space
}
}
}
impl Filter for PincushionFilter {
// The formula for pincushion distortion is: r_u = r_d * (1 + k * r_d^2)
// see also https://stackoverflow.com/a/6227310
// As points in laser space center around 0,0, calculating from the center
// becomes trivial
fn apply(&self, points: &LaserPoints) -> LaserPoints{
let space = points.space;
// dbg!(&space);
// assert!(!matches!(space, CoordinateSpace::Laser));
let projected_positions: Vec<laser::Point> = 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))
];
laser::Point {
position: new_position,
.. point.clone()
}
}).collect();
LaserPoints{
points: projected_positions,
space
}
}
}

View file

@ -2,15 +2,7 @@ use bevy::prelude::*;
use nannou_laser as laser; use nannou_laser as laser;
use std::time::Instant; use std::time::Instant;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::trap::{filters::{PointFilter, PointFilters}, tracks::CoordinateSpace}; use super::tracks::{LaserPoints, RenderableLines};
use super::tracks::{RenderableLines};
pub struct LaserPoints{
pub points: Vec<laser::Point>,
pub space: CoordinateSpace
}
// homography for laserworld in studio // homography for laserworld in studio
pub const TMP_PYTHON_LASER_H: [[f32;3];3] = [[ 2.47442963e+02, -7.01714050e+01, -9.71749119e+01], pub const TMP_PYTHON_LASER_H: [[f32;3];3] = [[ 2.47442963e+02, -7.01714050e+01, -9.71749119e+01],
@ -86,8 +78,7 @@ pub struct DacConfig{
// #[serde(with = "DacIdSerializable")] // #[serde(with = "DacIdSerializable")]
// id: DacId, // id: DacId,
pub name: String, pub name: String,
pub homography: Mat3, pub homography: Mat3
pub filters: PointFilters
} }
const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H); const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H);
@ -98,6 +89,6 @@ impl Default for DacConfig{
fn default() -> DacConfig{ fn default() -> DacConfig{
//DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY } //DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY }
// DacConfig { name: "Unknown".into(), homography: LASER_H_CM } // 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(), homography: LASER_H }
} }
} }

View file

@ -9,5 +9,4 @@ pub mod tracks;
pub mod shapes; pub mod shapes;
pub mod laser; pub mod laser;
pub mod filters;

View file

@ -4,8 +4,6 @@ use std::time::Instant;
use nannou_laser as laser; use nannou_laser as laser;
use serde_repr::*; use serde_repr::*;
use crate::trap::laser::LaserPoints;
#[derive(Serialize,Deserialize)] #[derive(Serialize,Deserialize)]
pub struct Frame { pub struct Frame {
pub tracks: std::collections::HashMap<String, Track> pub tracks: std::collections::HashMap<String, Track>
@ -70,7 +68,7 @@ impl RenderableLine {
} }
// see also trap/lines.py for matching values // see also trap/lines.py for matching values
#[derive(Clone, Debug, Serialize_repr, Deserialize_repr, Copy)] #[derive(Clone, Debug, Serialize_repr, Deserialize_repr)]
#[repr(u8)] #[repr(u8)]
pub enum CoordinateSpace { pub enum CoordinateSpace {
Image = 1, Image = 1,
@ -142,6 +140,7 @@ impl From<&Track> for RenderableLines{
} }
// TODO migrate to euclid::Point2D<f32, LaserSpace> // TODO migrate to euclid::Point2D<f32, LaserSpace>
pub type LaserPoints = Vec<laser::Point>;
impl From<&RenderableLines> for LaserPoints { impl From<&RenderableLines> for LaserPoints {
// much like nannou_laser::stream::frame::add_lines() // much like nannou_laser::stream::frame::add_lines()
@ -161,10 +160,7 @@ impl From<&RenderableLines> for LaserPoints {
} }
points.extend(line.points.iter().map(|p| laser::Point::from(p))); points.extend(line.points.iter().map(|p| laser::Point::from(p)));
} }
Self{ points
points,
space: CoordinateSpace::World
}
} }
} }

View file

@ -2,7 +2,7 @@ use zmq::Socket;
use serde_json::Result; use serde_json::Result;
use bevy::{ecs::system::SystemState, prelude::*, render::Render}; use bevy::{ecs::system::SystemState, prelude::*, render::Render};
use std::num::NonZero; use std::num::NonZero;
use super::{laser::{LaserApi, LaserTimer}, tracks::{Frame, RenderableLines, Track, TrackBundle}}; use super::{laser::{LaserApi, LaserTimer}, tracks::{Frame, LaserPoints, RenderableLines, Track, TrackBundle}};
// use trap::{Frame, Track, TrackBundle}; // use trap::{Frame, Track, TrackBundle};