Wip layers

This commit is contained in:
Ruben van de Ven 2025-10-17 15:04:00 +02:00
parent 905a3faa0c
commit fac5bda2f5
6 changed files with 214 additions and 90 deletions

View file

@ -133,7 +133,7 @@ fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
// false => trap::shapes::ARE_YOU_SURE,
// };
let points = model.current_lines.clone();
let points = model.current_layers.clone();
let mut new_points = Vec::new();
for point in points.into_iter() {

View file

@ -2,7 +2,6 @@
//! A clone of the `laser_frame_stream.rs` example that allows for configuring laser settings via a
//! UI.
use bevy::scene::ron::Map;
// use bevy_nannou::prelude::DARK_GRAY;
// use nannou::lyon::geom::euclid::Transform2D;
use nannou::{geom::Rect, math::map_range as nannou_map_range};
@ -15,7 +14,7 @@ use nannou_laser::{self as laser};
use serde_json::Result;
use laserspace::trap::filters::{MappedPoint, PointFilters};
use laserspace::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX, Corner};
use laserspace::trap::tracks::CoordinateSpace;
use laserspace::trap::tracks::{CoordinateSpace, RenderableLayers};
use laserspace::trap::utils::{closest_edge, split_on_blank};
use laserspace::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}};
use zmq::Socket;
@ -72,7 +71,7 @@ struct GuiModel {
egui: Egui,
// socket for receiving points
zmq: Socket,
current_lines: RenderableLines, // a copy for the drawing renderer
current_layers: RenderableLayers, // a copy for the drawing renderer
last_update: Instant,
// dimming_factor: f32,
lost_alpha: f32,
@ -155,23 +154,23 @@ fn zmq_receive(model: &mut GuiModel) {
];
let _nr = zmq::poll(&mut items, 0).unwrap();
let lines: RenderableLines;
let layers: RenderableLayers;
if items[0].is_readable() {
let json = subscriber.recv_string(0).unwrap().unwrap();
// dbg!(&json[4..]);
// let msg: Frame = serde_json::from_str(&json[4..]).expect("No valid json?");
let res: Result<RenderableLines> = serde_json::from_str(&json);
let res: Result<RenderableLayers> = serde_json::from_str(&json);
model.lost_alpha = 1.;
model.connected = true;
lines = match res {
Ok(lines) => lines, // if Ok(255), set x to 255
layers = match res {
Ok(layers) => layers, // if Ok(255), set x to 255
Err(_e) => {
println!("No valid json?");
println!("{}", _e);
// empty if invalid
RenderableLines::new()
RenderableLayers::new()
}, // if Err("some message"), panic with error message "some message"
};
} else if model.last_update < Instant::now() - Duration::from_millis(100){
@ -180,14 +179,14 @@ fn zmq_receive(model: &mut GuiModel) {
if model.lost_alpha > 0.{
println!("No input, clear lines!!");
println!("No input, clear layers!!");
model.lost_alpha *= 0.80;
if model.lost_alpha < 0.1{
model.lost_alpha = 0.;
}
lines = model.current_lines.with_alpha(model.lost_alpha);
layers = model.current_layers.with_alpha(model.lost_alpha);
} else {
lines = RenderableLines::new()
layers = RenderableLayers::new()
}
} else {
// No new lines, break
@ -198,17 +197,17 @@ fn zmq_receive(model: &mut GuiModel) {
for (_dac, stream) in (&model.laser_streams).into_iter() {
// let lines = get_laser_lines(version);
let lines_for_laser: RenderableLines = lines.clone();
let layers_for_laser: RenderableLayers = layers.clone();
let sending = stream.send(move |laser_model: &mut LaserModel| {
let laser_lines: RenderableLines = lines_for_laser;
laser_model.current_lines = laser_lines;
let laser_layers: RenderableLayers = layers_for_laser;
laser_model.current_layers = laser_layers;
});
if let Err(e) = sending {
println!("Error sending to laser! {e:?}");
}
}
model.current_lines = lines;
model.current_layers = layers;
model.last_update = Instant::now();
}
@ -393,7 +392,7 @@ fn model(app: &App) -> GuiModel {
let egui_ctx = egui.ctx();
egui_ctx.set_style(style());
let current_lines = RenderableLines::new(); //Vec::new();
let current_layers = RenderableLayers::new(); //Vec::new();
GuiModel {
laser_api,
@ -403,7 +402,7 @@ fn model(app: &App) -> GuiModel {
dac_rx,
egui,
zmq,
current_lines: current_lines,
current_layers: current_layers,
last_update: Instant::now(),
lost_alpha: 1.,
connected: true,
@ -420,34 +419,35 @@ fn model(app: &App) -> GuiModel {
}
}
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
let current_points: LaserPoints = (&model.current_lines).into();
// let space = &model.current_lines.space;
fn layers_to_points_for_config(current_layers: &RenderableLayers, config: &DacConfig ) -> Vec<Vec<laser::Point>> {
let mut new_laser_points: Vec<laser::Point> = Vec::new();
for (layer_nr, layer) in current_layers.0.iter() {
let mask = 1 << layer_nr;
if (mask & config.layers_enabled) != 0 {
let current_points: LaserPoints = layer.into();
// 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, &model.config);
let points = config.source.get_shape(current_points, config);
// let pointno = points.points.len();
let new_points = match points.space {
CoordinateSpace::RawLaser => points,
_ => model.config.filters.apply(&points)
_ => config.filters.apply(&points)
};
// let new_laser_points = new_points.points;
new_laser_points.extend(new_points.points);
}
}
split_on_blank(new_laser_points)
}
let new_laser_points = new_points.points;
// if new_laser_points.len() < pointno {
// println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno);
// }
// split by blanked points
let lines = split_on_blank(new_laser_points);
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
let lines = layers_to_points_for_config(&model.current_layers, &model.config);
for line in lines {
frame.add_lines(line);
}
return;
}
@ -542,7 +542,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
ref mut laser_settings,
ref mut per_laser_config,
ref mut selected_stream,
ref mut current_lines,
ref mut current_layers,
..
} = *model;
@ -567,8 +567,16 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
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.columns(3, |cols| {
cols[0].label(format!("Layers {}", current_layers.0.len()));
cols[1].label(format!("Lines {}", current_layers.line_count()));
cols[2].label(format!("Points {}", current_layers.point_count()));
});
// ui.add(egui::Label::new(format!("Layers {}", current_layers.0.len())));
// ui.add(egui::Label::new(format!("Lines {}", current_layers.line_count())));
// ui.add(egui::Label::new(format!("Points {}", current_layers.point_count())));
ui.heading("General settings");
@ -732,6 +740,40 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
});
ui.horizontal_wrapped(|ui| {
for layer_nr in 1..=8 {
let mask = 1 << layer_nr;
let mut enabled_bool = mask & selected_config.layers_enabled != 0;
if ui
.add(egui::Checkbox::new(&mut enabled_bool,format!("{:?}", layer_nr)))
.changed()
{
selected_config.layers_enabled &= mask;
let layers_enabled = selected_config.layers_enabled;
if let Some(stream) = selected_laser_stream {
stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.layers_enabled = layers_enabled;
}).unwrap();
}
}
}
});
// if ui
// .add_enabled(laser_settings.enable_optimisations,
// egui::Checkbox::new(&mut laser_settings.enable_draw_reorder,"Reorder paths")
// )
// // .checkbox(&mut laser_settings.enable_draw_reorder, "Reorder paths")
// .changed()
// {
// for (_dac_id, stream) in laser_streams.iter() {
// stream
// .enable_draw_reorder(laser_settings.enable_draw_reorder)
// .ok();
// }
// }
if ui
.add(egui::Slider::new(&mut selected_config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
.changed()
@ -867,12 +909,9 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
// LaserPoints supports a mode that sends points directly into the laser
// if this mode is enabled by the sender, set background to blue
// Red if nothing is sending / not for too long.
let bgcolor = match model.current_lines.space {
CoordinateSpace::Laser => MEDIUMSLATEBLUE,
_ => match model.connected{
let bgcolor = match model.connected{
true => DARKGRAY,
false => LIGHTCORAL,
},
};
draw.background().color(bgcolor);
@ -901,7 +940,9 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
// draw current laser lines
for line in &model.current_lines.lines{
for (_, layer) in model.current_layers.0.iter() {
for line in &layer.lines{
let vertices = line.points.iter().map(|p| {
let color = srgba(p.color.red, p.color.green, p.color.blue, p.color.alpha);
@ -914,6 +955,7 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
.join_round()
.points_colored(vertices);
}
}
// show each configured laser in the canvas. Highlight the selected.
for (dac_id, config) in model.per_laser_config.iter() {
@ -1014,7 +1056,7 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
// let stream = model.laser_streams.get(&dac_id); //.expect("Selected stream not found in configs");
// 1. get config for laser
let config = model.per_laser_config.get(&dac_id).expect("Selected stream not found in configs");
let config: &DacConfig = model.per_laser_config.get(&dac_id).expect("Selected stream not found in configs");
// 2. clipping mask + its anchor points
@ -1039,28 +1081,13 @@ fn view_laser_preview(app: &App, model: &GuiModel, frame: Frame) {
// 3. current shape of the laser
let lines = layers_to_points_for_config(&model.current_layers, &config);
let current_points: LaserPoints = (&model.current_lines).into();
// check which source should be used, and get points accordingly.
// potentially ignoring the points coming from the stream
let points = config.source.get_shape(current_points, config);
// let pointno = points.points.len();
let new_points = match points.space {
CoordinateSpace::RawLaser => points,
_ => config.filters.apply(&points)
};
let new_laser_points = new_points.points;
// draw as distinct lines (this is how it is send to post-processing)
// TODO: alternatively, if the optimisation becomes an actual filter
// this should be drawn as a single line, and we can have an option to
// visualise the intermediate lines to make the draw order apparent
let lines = split_on_blank(new_laser_points);
for line in lines {
// similar to map code:

View file

@ -47,6 +47,11 @@ pub struct ScaleFilter {
pub factor: f32
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct InterpolateFilter {
pub max_distance: f32
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PincushionFilter {
pub k_x: f32,
@ -69,6 +74,7 @@ pub struct PincushionFilter {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PointFilters{
pub interpolate: InterpolateFilter,
pub dim: DimFilter,
pub homography: HomographyFilter,
pub scale: ScaleFilter,
@ -91,9 +97,14 @@ pub struct PointFilters{
// }
// }
pub fn lerp(a: f32, b: f32, t: f32) -> f32{
return (1. - t) * a + t * b
}
impl PointFilters {
pub fn apply(&self, points: &LaserPoints) -> LaserPoints{
let mut p = self.dim.apply(points);
// TODO: not implemented yet p = self.interpolate.apply(&p);
p = self.homography.apply(&p);
p = self.scale.apply(&p);
p = self.crop.apply(&p);
@ -141,6 +152,7 @@ impl Default for PointFilters {
// let crop_filter = CropFilter{};
Self {
homography: HomographyFilter::default(),
interpolate: InterpolateFilter{max_distance: 0.2},
dim: DimFilter{intensity: 0.5},
scale: ScaleFilter { factor: 1. },
pincushion: PincushionFilter{k_x: 0.,k_x2: 0., k_y: 0., k_y2: 0.},
@ -571,6 +583,57 @@ impl Filter for DimFilter {
}
}
impl Filter for InterpolateFilter {
fn apply(&self, points: &LaserPoints) -> LaserPoints {
// TODO: fix the code below
// let mut new_positions: Vec<[f32;2]> = Vec::new();
// // min distance between points before geometric filters
// let interpolated_edges = p.points.iter().zip(p.points.iter().skip(1)).map(|(p1, p2)|{
// let distance = f32::sqrt((p2.position[0] - p1.position[0]).powi(2) + (p2.position[1] - p1.position[1]).powi(2));
// let steps: i32 = (distance / required_distance).ceil() as i32;
// let mut interpolated_edge = Vec::new();
// for i in 0..steps {
// let t = (steps as f32) /(i as f32);
// interpolated_edge.push(
// laser::Point{
// position: [
// lerp(p1.position[0], p2.position[0], t),
// lerp(p1.position[1], p2.position[1], t)
// ],
// color: p1.color,
// weight: p1.weight,
// }
// );
// }
// interpolated_edge
// });
// let mut interpolated_points: Vec<laser::Point> = interpolated_edges.flatten().collect();
// if let Some(last) = p.points.last() {
// interpolated_points.push(last.clone()); // add last one so we don't lose it
// }
// LaserPoints{
// points: interpolated_points,
// space: points.space,
// }
LaserPoints::default()
}
fn reverse(&self, points: &LaserPoints) -> LaserPoints{
// cannot easily be undone
LaserPoints{
points: points.points.clone(),
space: points.space,
}
}
}
fn scale(points: &LaserPoints, factor: f32) -> LaserPoints{
let new_points = points.points.iter().map(|point| {
let mut position = point.position.clone();

View file

@ -2,7 +2,7 @@ use bevy::prelude::*;
use nannou_laser as laser;
use std::time::Instant;
use serde::{Deserialize, Serialize};
use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace};
use crate::trap::{filters::PointFilters, tracks::{CoordinateSpace, RenderableLayers}};
use super::tracks::{RenderableLines};
@ -74,7 +74,7 @@ pub fn apply_homography_matrix(h: Mat3, p: &[f32; 2]) -> [f32; 2]{
#[derive(Resource, Clone)]
pub struct LaserModel{
pub t: Instant, // register start time, so that animations can be moving
pub current_lines: RenderableLines,
pub current_layers: RenderableLayers,
// pub dimming: f32,
pub config: DacConfig, // per dac configuration
}
@ -83,7 +83,7 @@ impl LaserModel{
pub fn new() -> Self{
Self{
t: Instant::now(),
current_lines: RenderableLines::new(),
current_layers: RenderableLayers::new(),
// dimming: 0.3,
config: DacConfig::default(),
}
@ -118,16 +118,18 @@ pub struct DacConfig{
// pub homography: Mat3,
pub source: StreamSource,
pub filters: PointFilters,
pub layers_enabled: u8,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum StreamSource {
Disabled,
CurrentLines,
CurrentLayers,
Rectangle,
GridY, // lines
WorldGrid, // grid in world space
Homography, // points for homography alignment
// Layers ( layers: )
// Circle, // segments
// Spiral,
}
@ -135,7 +137,7 @@ pub enum StreamSource {
// usefull to create pull downs with an iterator
pub const STREAM_SOURCES: [StreamSource; 6] = [
StreamSource::Disabled,
StreamSource::CurrentLines,
StreamSource::CurrentLayers,
StreamSource::Rectangle,
StreamSource::GridY,
StreamSource::WorldGrid,
@ -152,7 +154,7 @@ impl Default for DacConfig{
fn default() -> DacConfig{
//DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY }
// DacConfig { name: "Unknown".into(), homography: LASER_H_CM }
DacConfig { name: "Unknown".into(), source: StreamSource::CurrentLines, filters: PointFilters::default().with_homography(LASER_H) }
DacConfig { name: "Unknown".into(), source: StreamSource::CurrentLayers, filters: PointFilters::default().with_homography(LASER_H), layers_enabled: 0b1111_1111 }
}
}
@ -214,7 +216,7 @@ impl StreamSource{
pub fn get_shape(&self, current_points: LaserPoints, config: &DacConfig) -> LaserPoints {
match self {
Self::CurrentLines => current_points,
Self::CurrentLayers => current_points,
Self::Rectangle => {
shape_rect(LaserSpace::READY, 11)
},

View file

@ -86,6 +86,36 @@ pub struct RenderableLines{
pub space: CoordinateSpace, // enum in python
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RenderableLayers(pub std::collections::HashMap<u8, RenderableLines>);
impl RenderableLayers{
pub fn new() -> Self{
Self (std::collections::HashMap::new())
}
pub fn with_alpha(&self, alpha: f32) -> Self {
if alpha == 1. {
return self.clone();
}
Self ( self.0.iter().map(|(layer, lines)| { (*layer, lines.with_alpha(alpha))}).collect() )
}
pub fn line_count(&self) -> usize {
let s = self.0.values().map(|l| l.lines.len()).sum();
s
}
pub fn point_count(&self) -> usize {
let s = self.0.values().map(|l| l.point_count()).sum();
s
}
}
impl RenderableLines{
pub fn new() -> Self{
RenderableLines { lines: Vec::new(), space: CoordinateSpace::World }

View file

@ -2,6 +2,8 @@ use zmq::Socket;
use serde_json::Result;
use bevy::{ecs::system::SystemState, prelude::*, render::Render};
use std::num::NonZero;
use crate::trap::tracks::RenderableLayers;
use super::{laser::{LaserApi, LaserTimer}, tracks::{Frame, RenderableLines, Track, TrackBundle}};
@ -60,10 +62,10 @@ fn receive_zmq_lines(
dbg!(&json);
// let msg: Frame = serde_json::from_str(&json[4..]).expect("No valid json?");
let res: Result<RenderableLines> = serde_json::from_str(&json);
let res: Result<RenderableLayers> = serde_json::from_str(&json);
let lines: RenderableLines = match res {
Ok(lines) => lines, // if Ok(255), set x to 255
let layers: RenderableLayers = match res {
Ok(layers) => layers, // if Ok(255), set x to 255
Err(_e) => {
println!("No valid json? {json}");
println!("{}", _e);
@ -73,15 +75,15 @@ fn receive_zmq_lines(
}, // if Err("some message"), panic with error message "some message"
};
println!("receive {}", lines.lines.len());
println!("receive {} lines", layers.line_count());
for (laser_api, mut laser_timer) in lasers.iter_mut() {
laser_timer.timer.tick(time.delta());
// let lines = get_laser_lines(version);
let lines: RenderableLines = lines.clone();
let lines: RenderableLayers = layers.clone();
laser_api.laser_stream.send(|laser| {
let laser_lines: RenderableLines = lines;
laser.current_lines = laser_lines;
let current_layers: RenderableLayers = lines;
laser.current_layers = current_layers;
}).unwrap();
}
}