Compare commits
10 commits
0ca03aa1e7
...
319062b0b4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
319062b0b4 | ||
![]() |
a364a9daad | ||
![]() |
e291ab480a | ||
![]() |
e8fec78250 | ||
![]() |
7c3968d2dc | ||
![]() |
150b3b7fd8 | ||
![]() |
b25fc93997 | ||
![]() |
188420c4ce | ||
![]() |
d989cdaa86 | ||
![]() |
9aefe4f7f6 |
6 changed files with 919 additions and 300 deletions
|
@ -2,34 +2,42 @@
|
||||||
//! 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::math::Mat3;
|
// use bevy_nannou::prelude::DARK_GRAY;
|
||||||
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, util::map_range};
|
use nannou_laser::{self as laser};
|
||||||
use serde_json::Result;
|
use serde_json::Result;
|
||||||
use serde::{Serialize,Deserialize};
|
use trap_rust::trap::filters::PointFilters;
|
||||||
use trap_rust::trap::laser::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::tracks::CoordinateSpace;
|
||||||
use trap_rust::trap::{laser::{apply_homography_matrix, python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{LaserPoints, 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;
|
||||||
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;
|
||||||
|
|
||||||
|
// use egui_dropdown::DropDownBox;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
nannou::app(model).update(update).run();
|
nannou::app(model).update(update).run();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Model {
|
pub struct StreamConfig{
|
||||||
|
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: Vec<laser::FrameStream<LaserModel>>,
|
laser_streams: StreamConfigMap,
|
||||||
// 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.
|
||||||
|
@ -46,6 +54,7 @@ struct Model {
|
||||||
// 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,
|
||||||
}
|
}
|
||||||
|
@ -55,6 +64,7 @@ struct LaserSettings {
|
||||||
latency_points: u32,
|
latency_points: u32,
|
||||||
frame_hz: u32,
|
frame_hz: u32,
|
||||||
enable_optimisations: bool,
|
enable_optimisations: bool,
|
||||||
|
enable_draw_reorder: bool,
|
||||||
distance_per_point: f32,
|
distance_per_point: f32,
|
||||||
blank_delay_points: u32,
|
blank_delay_points: u32,
|
||||||
radians_per_point: f32,
|
radians_per_point: f32,
|
||||||
|
@ -74,6 +84,7 @@ impl Default for LaserSettings {
|
||||||
) * 4,
|
) * 4,
|
||||||
frame_hz: 35, //stream::DEFAULT_FRAME_HZ,
|
frame_hz: 35, //stream::DEFAULT_FRAME_HZ,
|
||||||
enable_optimisations: true,
|
enable_optimisations: true,
|
||||||
|
enable_draw_reorder: true,
|
||||||
distance_per_point: InterpolationConfig::DEFAULT_DISTANCE_PER_POINT,
|
distance_per_point: InterpolationConfig::DEFAULT_DISTANCE_PER_POINT,
|
||||||
blank_delay_points: InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS,
|
blank_delay_points: InterpolationConfig::DEFAULT_BLANK_DELAY_POINTS,
|
||||||
radians_per_point: InterpolationConfig::DEFAULT_RADIANS_PER_POINT,
|
radians_per_point: InterpolationConfig::DEFAULT_RADIANS_PER_POINT,
|
||||||
|
@ -83,7 +94,8 @@ 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
|
||||||
|
@ -99,7 +111,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 Model) {
|
fn zmq_receive(model: &mut GuiModel) {
|
||||||
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)
|
||||||
|
@ -147,12 +159,12 @@ fn zmq_receive(model: &mut Model) {
|
||||||
|
|
||||||
// println!("receive {}", lines.lines.len());
|
// println!("receive {}", lines.lines.len());
|
||||||
|
|
||||||
for laser_stream in (&model.laser_streams).into_iter() {
|
for (_dac, stream_config) 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 = laser_stream.send(move |laser| {
|
let sending = stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
||||||
let laser_lines: RenderableLines = lines_for_laser;
|
let laser_lines: RenderableLines = lines_for_laser;
|
||||||
laser.current_lines = laser_lines;
|
laser_model.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:?}");
|
||||||
|
@ -165,18 +177,9 @@ fn zmq_receive(model: &mut Model) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// #[derive(Serialize, Deserialize)]
|
|
||||||
// #[serde(remote = "DacId")]
|
|
||||||
// pub enum DacIdSerializable {
|
|
||||||
// EtherDream { mac_address: [u8; 6] },
|
|
||||||
// Helios { id: u32 },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// DEPRECATED
|
|
||||||
|
|
||||||
|
|
||||||
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{
|
||||||
|
|
||||||
|
@ -185,7 +188,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)
|
.. DacConfig::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
dac_configs.insert(
|
dac_configs.insert(
|
||||||
|
@ -201,7 +204,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)
|
.. DacConfig::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
dac_configs.insert(
|
dac_configs.insert(
|
||||||
|
@ -217,13 +220,30 @@ 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)
|
.. DacConfig::default()
|
||||||
|
// filters: PointFilters::default(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
dac_configs.insert(
|
||||||
|
DacId::EtherDream {
|
||||||
|
mac_address: [
|
||||||
|
18,
|
||||||
|
52,
|
||||||
|
86,
|
||||||
|
120,
|
||||||
|
154,
|
||||||
|
188,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
DacConfig{
|
||||||
|
name: "Emulator".into(),
|
||||||
|
.. DacConfig::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
dac_configs
|
dac_configs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn model(app: &App) -> Model {
|
fn model(app: &App) -> GuiModel {
|
||||||
// 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()
|
||||||
|
@ -243,6 +263,16 @@ fn model(app: &App) -> Model {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.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)
|
||||||
|
.view(view_laser_preview)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Initialise the state that we want to live on the laser thread and spawn the stream.
|
// Initialise the state that we want to live on the laser thread and spawn the stream.
|
||||||
let laser_settings = LaserSettings::default();
|
let laser_settings = LaserSettings::default();
|
||||||
let laser_model = LaserModel::new();
|
let laser_model = LaserModel::new();
|
||||||
|
@ -295,7 +325,7 @@ fn model(app: &App) -> Model {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 = vec![];
|
let laser_streams = HashMap::new(); //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();
|
||||||
|
@ -305,7 +335,7 @@ fn model(app: &App) -> Model {
|
||||||
|
|
||||||
let current_lines = RenderableLines::new(); //Vec::new();
|
let current_lines = RenderableLines::new(); //Vec::new();
|
||||||
|
|
||||||
Model {
|
GuiModel {
|
||||||
laser_api,
|
laser_api,
|
||||||
laser_settings,
|
laser_settings,
|
||||||
laser_model,
|
laser_model,
|
||||||
|
@ -318,181 +348,43 @@ fn model(app: &App) -> Model {
|
||||||
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 current_points: LaserPoints = (&model.current_lines).into();
|
||||||
let space = &model.current_lines.space;
|
let space = &model.current_lines.space;
|
||||||
let pointno = points.len();
|
|
||||||
|
|
||||||
// dbg!(&model.config.name);
|
// 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 mut new_points = Vec::new();
|
let pointno = points.points.len();
|
||||||
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 new_points = model.config.filters.apply(&points);
|
||||||
let point = points[id] ;
|
let new_laser_points = new_points.points;
|
||||||
|
if new_laser_points.len() < pointno {
|
||||||
let mut new_positions: Vec<[f32;2]> = Vec::new();
|
println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno);
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_points.len() < pointno {
|
// on reconnect gives Unknown
|
||||||
println!("Cropped Points {} (was: {})", new_points.len(), pointno);
|
// dbg!(&model.config);
|
||||||
}
|
// dbg!(&points.points[0]);
|
||||||
|
// dbg!(&new_laser_points[0]);
|
||||||
|
|
||||||
// println!("{:?}", new_points);
|
frame.add_lines(new_laser_points);
|
||||||
frame.add_lines(new_points);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_window_event(_app: &App, model: &mut Model, event: &nannou::winit::event::WindowEvent) {
|
fn raw_window_event(_app: &App, model: &mut GuiModel, event: &nannou::winit::event::WindowEvent) {
|
||||||
model.egui.handle_raw_event(event);
|
model.egui.handle_raw_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(_app: &App, model: &mut Model, update: Update) {
|
fn update(_app: &App, model: &mut GuiModel, 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());
|
||||||
|
@ -507,53 +399,81 @@ fn update(_app: &App, model: &mut Model, 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)
|
.detected_dac(dac.clone())
|
||||||
.build()
|
.build()
|
||||||
.expect("failed to establish stream with newly detected DAC");
|
.expect("failed to establish stream with newly detected DAC");
|
||||||
model.laser_streams.push(stream);
|
// dbg!(stream.enable_draw_reorder());
|
||||||
|
model.laser_streams.insert(dac.id(), StreamConfig{ stream, config: config.clone() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 (i, stream) in model.laser_streams.iter().enumerate() {
|
for (dac_id, stream_config) in model.laser_streams.iter() {
|
||||||
if stream.is_closed() {
|
if stream_config.stream.is_closed() {
|
||||||
dropped.push(i);
|
dropped.push(dac_id.clone());
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
||||||
|
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.with_config(config), laser_frame_producer)
|
||||||
|
.detected_dac(dac)
|
||||||
|
.build()
|
||||||
|
{
|
||||||
|
Err(err) => eprintln!("failed to restart stream: {}", err),
|
||||||
|
Ok(stream) => {
|
||||||
|
println!("Reinsert stream. {:?}", dac_id);
|
||||||
|
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 Model {
|
let GuiModel {
|
||||||
ref mut egui,
|
ref mut egui,
|
||||||
ref laser_streams,
|
ref mut 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;
|
||||||
|
|
||||||
|
@ -570,34 +490,21 @@ fn update(_app: &App, model: &mut Model, update: Update) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.heading("Laser Points");
|
ui.heading("Laser Points");
|
||||||
|
|
||||||
ui.separator();
|
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.add(egui::Label::new(format!("Lines {}", model.current_lines.lines.len())));
|
ui.heading("General settings");
|
||||||
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 stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_point_hz(hz).ok();
|
stream.stream.set_point_hz(hz).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
|
@ -605,20 +512,20 @@ fn update(_app: &App, model: &mut Model, update: Update) {
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
let latency = laser_settings.latency_points;
|
let latency = laser_settings.latency_points;
|
||||||
for stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_latency_points(latency).ok();
|
stream.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 stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_frame_hz(hz).ok();
|
stream.stream.set_frame_hz(hz).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.heading("Laser Path Interpolation");
|
ui.heading("Laser Path Interpolation");
|
||||||
|
@ -627,86 +534,205 @@ fn update(_app: &App, model: &mut Model, update: Update) {
|
||||||
.checkbox(&mut laser_settings.enable_optimisations, "Optimize Path")
|
.checkbox(&mut laser_settings.enable_optimisations, "Optimize Path")
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
for stream in laser_streams {
|
for (_dac_id, stream_config) in laser_streams.iter() {
|
||||||
stream
|
stream_config.stream
|
||||||
.enable_optimisations(laser_settings.enable_optimisations)
|
.enable_optimisations(laser_settings.enable_optimisations)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.add(
|
.add_enabled(laser_settings.enable_optimisations,
|
||||||
egui::Slider::new(&mut laser_settings.distance_per_point, 0.01..=1.0)
|
egui::Checkbox::new(&mut laser_settings.enable_draw_reorder,"Reorder paths")
|
||||||
.text("Distance Per Point"),
|
|
||||||
)
|
)
|
||||||
|
// .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
for (_dac_id, stream_config) in laser_streams.iter() {
|
||||||
|
stream_config.stream
|
||||||
|
.enable_draw_reorder(laser_settings.enable_draw_reorder)
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.add_enabled(laser_settings.enable_optimisations,
|
||||||
|
egui::Slider::new(&mut laser_settings.distance_per_point, 0.01..=1.0)
|
||||||
|
.text("Distance Per Point"),
|
||||||
|
)
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
let distance = laser_settings.distance_per_point;
|
let distance = laser_settings.distance_per_point;
|
||||||
for stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_distance_per_point(distance).ok();
|
stream.stream.set_distance_per_point(distance).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.add(
|
.add_enabled(laser_settings.enable_optimisations,
|
||||||
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 stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_blank_delay_points(delay).ok();
|
stream.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);
|
||||||
if ui
|
if ui
|
||||||
.add(egui::Slider::new(&mut degrees, 1.0..=180.0).text("Degrees Per Point"))
|
.add_enabled(laser_settings.enable_optimisations,
|
||||||
|
egui::Slider::new(&mut degrees, 1.0..=180.0).text("Degrees Per Point")
|
||||||
|
)
|
||||||
.changed()
|
.changed()
|
||||||
{
|
{
|
||||||
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 stream in laser_streams {
|
for (_dac_id, stream) in laser_streams.iter() {
|
||||||
stream.set_radians_per_point(radians).ok();
|
stream.stream.set_radians_per_point(radians).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.heading("Connected DACs");
|
ui.separator();
|
||||||
|
ui.heading("Laser specific settings");
|
||||||
if laser_streams.len() < 1 {
|
|
||||||
ui.label("no streams");
|
if laser_streams.is_empty() {
|
||||||
|
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");
|
||||||
|
|
||||||
|
|
||||||
for stream in laser_streams {
|
let source = &mut stream_config.config.source;
|
||||||
let dac = stream
|
|
||||||
.dac()
|
egui::ComboBox::from_label("Source")
|
||||||
.expect("`dac` returned `None` even though one was specified during stream creation");
|
.selected_text(format!("{source:?}"))
|
||||||
ui.add(egui::Label::new(format!("{:?}", dac.id())));
|
.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();
|
||||||
|
};
|
||||||
|
|
||||||
//if egui::ComboBox::from_label("Homography")
|
}
|
||||||
// .selected_text(format!("{radio:?}"))
|
});
|
||||||
// .show_ui(ui, |ui| {
|
|
||||||
// ui.selectable_value(radio, Enum::First, "First");
|
|
||||||
// ui.selectable_value(radio, Enum::Second, "Second");
|
if ui
|
||||||
// ui.selectable_value(radio, Enum::Third, "Third");
|
.add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
|
||||||
// })
|
.changed()
|
||||||
// .changed() {
|
{
|
||||||
// let sending = laser_stream.send(move |laser| {
|
let factor = stream_config.config.filters.dim.intensity;
|
||||||
// let laser_lines: RenderableLines = lines_for_laser;
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
||||||
// laser.current_lines = laser_lines;
|
laser_model.config.filters.dim.intensity = factor;
|
||||||
// });
|
}).unwrap();
|
||||||
// if let Err(e) = sending {
|
}
|
||||||
// println!("Error sending to laser! {e:?}");
|
|
||||||
// }
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -0.5..=0.5).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_x2, -0.2..=0.2).text("Higher order pincushion x"))
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
let factor = stream_config.config.filters.pincushion.k_x2;
|
||||||
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
||||||
|
laser_model.config.filters.pincushion.k_x2 = factor;
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -0.5..=0.5).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
|
||||||
|
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y2, -0.2..=0.2).text("Higher order pincushion y"))
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
let factor = stream_config.config.filters.pincushion.k_y2;
|
||||||
|
stream_config.stream.send(move |laser_model: &mut LaserModel| {
|
||||||
|
laser_model.config.filters.pincushion.k_y2 = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ui.label("Select a DAC");
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_laser_settings(_app: &App, model: &Model, frame: Frame) {
|
fn view_laser_settings(_app: &App, model: &GuiModel, frame: Frame) {
|
||||||
model.egui.draw_to_frame(&frame).unwrap();
|
model.egui.draw_to_frame(&frame).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_line_canvas(app: &App, model: &Model, frame: Frame) {
|
fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
|
||||||
// get canvas to draw on
|
// get canvas to draw on
|
||||||
let draw = app.draw();
|
let draw = app.draw();
|
||||||
|
|
||||||
|
@ -756,6 +782,98 @@ fn view_line_canvas(app: &App, model: &Model, frame: Frame) {
|
||||||
draw.to_frame(app, &frame).unwrap();
|
draw.to_frame(app, &frame).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preview the selected laser, to draw clip mask
|
||||||
|
fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
|
||||||
|
// get canvas to draw on
|
||||||
|
let draw = app.draw();
|
||||||
|
|
||||||
|
|
||||||
|
draw.background().color(DARKGRAY);
|
||||||
|
|
||||||
|
let win = app.window_rect();
|
||||||
|
|
||||||
|
let w = 1024.;
|
||||||
|
let h = 1024.;
|
||||||
|
let hh = h / 2.;
|
||||||
|
let hw = w / 2.;
|
||||||
|
|
||||||
|
let thickness = 2.0;
|
||||||
|
|
||||||
|
let win_rect = app.main_window().rect().pad(20.0);
|
||||||
|
|
||||||
|
match &model.selected_stream {
|
||||||
|
None => {
|
||||||
|
draw.text("Select a stream to preview")
|
||||||
|
.color(WHITE)
|
||||||
|
.font_size(24)
|
||||||
|
.wh(win_rect.wh());
|
||||||
|
},
|
||||||
|
Some(dac_id) => {
|
||||||
|
let stream_config: & StreamConfig = model.laser_streams.get(&dac_id).expect("Selected stream not found in configs");
|
||||||
|
|
||||||
|
draw.text(&format!("{:?}", dac_id))
|
||||||
|
.h(win_rect.h())
|
||||||
|
.font_size(10)
|
||||||
|
.align_text_bottom()
|
||||||
|
.left_justify()
|
||||||
|
.color(WHITE)
|
||||||
|
.w(win_rect.w());
|
||||||
|
|
||||||
|
|
||||||
|
let current_points: LaserPoints = (&model.current_lines).into();
|
||||||
|
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 = stream_config.config.source.get_shape(current_points);
|
||||||
|
|
||||||
|
let pointno = points.points.len();
|
||||||
|
|
||||||
|
let new_points = stream_config.config.filters.apply(&points);
|
||||||
|
|
||||||
|
// similar to map code:
|
||||||
|
|
||||||
|
let vertices = new_points.points.iter().map(|p| {
|
||||||
|
let color = srgba(p.color[0], p.color[1], p.color[0], 1.);
|
||||||
|
|
||||||
|
let pos = [p.position[0] * hw, p.position[1] * hh];
|
||||||
|
(pos, color)
|
||||||
|
});
|
||||||
|
|
||||||
|
draw.polyline()
|
||||||
|
.weight(thickness)
|
||||||
|
.join_round()
|
||||||
|
.points_colored(vertices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
draw.to_frame(app, &frame).unwrap();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn laser_preview_mouse_pressed(app: &App, _model: &mut GuiModel, button: MouseButton) {
|
||||||
|
if button != MouseButton::Left {
|
||||||
|
// ignore
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let half_w = (1024 / 2) as f32;
|
||||||
|
let half_h = (1024 / 2) as f32;
|
||||||
|
|
||||||
|
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 draw_grid(draw: &Draw, win: &Rect, step: f32, weight: f32) {
|
fn draw_grid(draw: &Draw, win: &Rect, step: f32, weight: f32) {
|
||||||
let step_by = || (0..).map(|i| i as f32 * step);
|
let step_by = || (0..).map(|i| i as f32 * step);
|
||||||
|
@ -800,15 +918,15 @@ fn style() -> egui::Style {
|
||||||
style
|
style
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_moved(_app: &App, _model: &mut Model, _pos: Point2) {
|
fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_pressed(_app: &App, _model: &mut Model, _button: MouseButton) {
|
fn mouse_pressed(_app: &App, _model: &mut GuiModel, _button: MouseButton) {
|
||||||
// _model.dragging
|
// _model.dragging
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_released(_app: &App, _model: &mut Model, _button: MouseButton) {}
|
fn mouse_released(_app: &App, _model: &mut GuiModel, _button: MouseButton) {}
|
||||||
|
|
||||||
fn mouse_wheel(_app: &App, _model: &mut Model, _dt: MouseScrollDelta, _phase: TouchPhase) {
|
fn mouse_wheel(_app: &App, _model: &mut GuiModel, _dt: MouseScrollDelta, _phase: TouchPhase) {
|
||||||
// canvas zoom
|
// canvas zoom
|
||||||
}
|
}
|
||||||
|
|
343
src/trap/filters.rs
Normal file
343
src/trap/filters.rs
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
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, Debug)]
|
||||||
|
pub struct HomographyFilter {
|
||||||
|
pub homography_matrix: Mat3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct CropFilter {
|
||||||
|
pub enabled: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct DimFilter {
|
||||||
|
pub intensity: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ScaleFilter {
|
||||||
|
pub factor: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct PincushionFilter {
|
||||||
|
pub k_x: f32,
|
||||||
|
pub k_x2: f32,
|
||||||
|
pub k_y: f32,
|
||||||
|
pub k_y2: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[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<PointFilter>); // deprecated
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
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_x2: 0., k_y: 0., k_y2: 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;
|
||||||
|
|
||||||
|
// Apply Brown-Conrady model of distortion
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Distortion_(optics)#Software_correction
|
||||||
|
// calculate radius (though we might be able to get away, just using p[1] and p[0]
|
||||||
|
// as we calculate axes independently)
|
||||||
|
let radius = (p[0].powi(2) + p[1].powi(2)).sqrt();
|
||||||
|
|
||||||
|
let new_position = [
|
||||||
|
p[0] * (1. + self.k_x * radius.powi(2)+ self.k_x2 * radius.powi(4)),
|
||||||
|
p[1] * (1. + self.k_y * radius.powi(2)+ self.k_y2 * radius.powi(4))
|
||||||
|
];
|
||||||
|
|
||||||
|
laser::Point {
|
||||||
|
position: new_position,
|
||||||
|
.. point.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
LaserPoints{
|
||||||
|
points: projected_positions,
|
||||||
|
space
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,16 @@ 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 super::tracks::{LaserPoints, RenderableLines};
|
use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace};
|
||||||
|
|
||||||
|
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],
|
||||||
|
@ -38,7 +47,7 @@ pub fn apply_homography_matrix(h: Mat3, p: &[f32; 2]) -> [f32; 2]{
|
||||||
pub struct LaserModel{
|
pub struct LaserModel{
|
||||||
pub t: Instant, // register start time, so that animations can be moving
|
pub t: Instant, // register start time, so that animations can be moving
|
||||||
pub current_lines: RenderableLines,
|
pub current_lines: RenderableLines,
|
||||||
pub dimming: f32,
|
// pub dimming: f32,
|
||||||
pub config: DacConfig, // per dac configuration
|
pub config: DacConfig, // per dac configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +56,7 @@ impl LaserModel{
|
||||||
Self{
|
Self{
|
||||||
t: Instant::now(),
|
t: Instant::now(),
|
||||||
current_lines: RenderableLines::new(),
|
current_lines: RenderableLines::new(),
|
||||||
dimming: 0.3,
|
// dimming: 0.3,
|
||||||
config: DacConfig::default(),
|
config: DacConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,14 +82,37 @@ pub struct LaserTimer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct DacConfig{
|
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 source: StreamSource,
|
||||||
|
pub filters: PointFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub enum StreamSource {
|
||||||
|
CurrentLines,
|
||||||
|
Rectangle,
|
||||||
|
Grid, // lines
|
||||||
|
WorldGrid, // grid in world space
|
||||||
|
// Circle, // segments
|
||||||
|
// Spiral,
|
||||||
|
}
|
||||||
|
|
||||||
|
// usefull to create pull downs with an iterator
|
||||||
|
pub const STREAM_SOURCES: [StreamSource; 4] = [
|
||||||
|
StreamSource::CurrentLines,
|
||||||
|
StreamSource::Rectangle,
|
||||||
|
StreamSource::Grid,
|
||||||
|
StreamSource::WorldGrid,
|
||||||
|
// StreamSource::Circle,
|
||||||
|
// StreamSource::Spiral
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX);
|
const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX);
|
||||||
|
|
||||||
|
@ -89,6 +121,127 @@ 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 }
|
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 / 10) {
|
||||||
|
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 / 10) {
|
||||||
|
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 }
|
||||||
|
},
|
||||||
|
Self::WorldGrid => {
|
||||||
|
// a grid in world space. Usefull for calibrating
|
||||||
|
let mut points = Vec::new();
|
||||||
|
for i in (-20..=50).step_by(5) {
|
||||||
|
let color = if i % 10 == 0 { [1.,0.,0.]} else {[1.,1.,1.]};
|
||||||
|
points.push(laser::Point{
|
||||||
|
position:[i as f32, 0.],
|
||||||
|
color: [0., 0., 0.],
|
||||||
|
weight: 0,
|
||||||
|
});
|
||||||
|
for j in (-20..=50).step_by(2) {
|
||||||
|
points.push(laser::Point{
|
||||||
|
position:[i as f32, j as f32],
|
||||||
|
color,
|
||||||
|
weight: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
points.push(points[points.len()-1].blanked());
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in (-20..=50).step_by(5) {
|
||||||
|
let color = if i % 10 == 0 { [0.,0.,1.]} else {[1.,1.,1.]};
|
||||||
|
points.push(laser::Point{
|
||||||
|
position:[0., i as f32],
|
||||||
|
color: [0., 0., 0.],
|
||||||
|
weight: 0,
|
||||||
|
});
|
||||||
|
for j in (-20..=50).step_by(2) {
|
||||||
|
points.push(laser::Point{
|
||||||
|
position:[j as f32, i as f32],
|
||||||
|
color,
|
||||||
|
weight: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
points.push(points[points.len()-1].blanked());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LaserPoints { points, space: CoordinateSpace::World }
|
||||||
|
},
|
||||||
|
_ => LaserPoints::default(), // empty set
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,4 +9,5 @@ pub mod tracks;
|
||||||
|
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
|
|
||||||
pub mod laser;
|
pub mod laser;
|
||||||
|
pub mod filters;
|
|
@ -4,6 +4,8 @@ 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>
|
||||||
|
@ -68,7 +70,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)]
|
#[derive(Clone, Debug, Serialize_repr, Deserialize_repr, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum CoordinateSpace {
|
pub enum CoordinateSpace {
|
||||||
Image = 1,
|
Image = 1,
|
||||||
|
@ -140,7 +142,6 @@ 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()
|
||||||
|
@ -160,7 +161,10 @@ 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)));
|
||||||
}
|
}
|
||||||
points
|
Self{
|
||||||
|
points,
|
||||||
|
space: CoordinateSpace::World
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, LaserPoints, RenderableLines, Track, TrackBundle}};
|
use super::{laser::{LaserApi, LaserTimer}, tracks::{Frame, RenderableLines, Track, TrackBundle}};
|
||||||
|
|
||||||
|
|
||||||
// use trap::{Frame, Track, TrackBundle};
|
// use trap::{Frame, Track, TrackBundle};
|
||||||
|
|
Loading…
Reference in a new issue