use bevy::prelude::*; use nannou_laser as laser; use std::time::Instant; use serde::{Deserialize, Serialize}; use crate::trap::{filters::PointFilters, tracks::{CoordinateSpace, RenderableLayers}}; use super::tracks::{RenderableLines}; #[derive(Debug)] pub struct LaserPoints{ pub points: Vec, pub space: CoordinateSpace } impl From> for LaserPoints{ // assumes input is in CoordinateSpace::Laser fn from(input: Vec<[f32;2]>) -> LaserPoints { // let points = Vec::new(); let points = input.iter().map(|p| { laser::Point { position: p.clone(), color: [1.,1.,1.], weight: 0, } }).collect(); LaserPoints{ points, space: CoordinateSpace::Laser } } } impl Into> for LaserPoints{ fn into(self) -> Vec<[f32;2]> { // let points = Vec::new(); self.points.iter().map(|p| { p.position }).collect() } } // homography for laserworld in studio pub const TMP_PYTHON_LASER_H: [[f32;3];3] = [[ 2.47442963e+02, -7.01714050e+01, -9.71749119e+01], [ 1.02328119e+01, 1.47185254e+02, 1.96295638e+02], [-1.20921986e-03, -3.32735973e-02, 1.00000000e+00]]; pub const TMP_PYTHON_LASER_H_FOR_NANNOU: [[f32;3]; 3] = [[ 2.47442963e+02/0xFFF as f32, -7.01714050e+01, -9.71749119e+01], [ 1.02328119e+01, 1.47185254e+02/0xFFF as f32, 1.96295638e+02], [-1.20921986e-03, -3.32735973e-02, 1.00000000e+00]]; // pub const TMP_DESK_CLUBMAX: [[f32; 3];3] = [[0.003018943396859696, -0.002188214018866917, 4.477472619274084], [0.0006859845340564413, -0.007161248327764752, 18.904283109325856], [2.691266191666664e-05, -0.0002590919318146652, 1.0]]; pub const TMP_DESK_CLUBMAX: [[f32; 3];3] = [[112.4994944830643, -51.10450906207176, 462.38070124241983], [8.808883296848508, -144.0698004299043, 2684.09476106297], [-0.0007453502703082056, -0.035951964534732414, 1.0]]; pub const fn python_cv_h_into_mat3(m: [[f32;3]; 3]) -> Mat3{ Mat3::from_cols( Vec3::new(m[0][0], m[1][0], m[2][0]), Vec3::new(m[0][1], m[1][1], m[2][1]), Vec3::new(m[0][2], m[1][2], m[2][2]), ) } /// Transform point with transform (homography) matrix /// this behaviour is conguent with openCV perspectiveTransform pub fn apply_homography_matrix(h: Mat3, p: &[f32; 2]) -> [f32; 2]{ let v_in = Vec3::new(p[0],p[1],1.0); let v_out = h.mul_vec3(v_in); [v_out[0] / v_out[2], v_out[1] / v_out[2]] } #[derive(Resource, Clone)] pub struct LaserModel{ pub t: Instant, // register start time, so that animations can be moving pub current_layers: RenderableLayers, // pub dimming: f32, pub config: DacConfig, // per dac configuration } impl LaserModel{ pub fn new() -> Self{ Self{ t: Instant::now(), current_layers: RenderableLayers::new(), // dimming: 0.3, config: DacConfig::default(), } } pub fn with_config(&self, config: &DacConfig) -> Self{ LaserModel{ config: config.clone(), .. self.clone()} } } #[derive(Component)] pub struct LaserApi{ pub _laser_api: laser::Api, pub laser_stream: laser::FrameStream, } #[derive(Component)] pub struct LaserTimer { pub timer: Timer, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DacConfig{ // #[serde(with = "DacIdSerializable")] // id: DacId, pub name: String, // pub homography: Mat3, pub source: StreamSource, pub filters: PointFilters, #[serde(default = "default_layers_enabled")] pub layers_enabled: u8, // window specific settings pub window_monitor: String, pub window_enabled: bool, pub window_fullscreen: bool, } fn default_layers_enabled() -> u8 { 0b1111_1111 } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub enum StreamSource { Disabled, CurrentLayers, Rectangle, GridY, // lines WorldGrid, // grid in world space Homography, // points for homography alignment // Layers ( layers: ) // Circle, // segments // Spiral, } // usefull to create pull downs with an iterator pub const STREAM_SOURCES: [StreamSource; 6] = [ StreamSource::Disabled, StreamSource::CurrentLayers, StreamSource::Rectangle, StreamSource::GridY, StreamSource::WorldGrid, StreamSource::Homography, // StreamSource::Spiral ]; const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H); const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX); 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::CurrentLayers, filters: PointFilters::default().with_homography(LASER_H), layers_enabled: 0b1111_1111 , window_monitor: "".into(), window_enabled: false, window_fullscreen: false } } } impl Default for LaserPoints { fn default() -> LaserPoints { LaserPoints { points: Vec::new(), space: CoordinateSpace::World } } } // because code is a mess, old homography assumed conversion to 0xFFF (10bit range) as // accepted by Python helios code. Nannuo uses the relative -1..1 coordinate system // coordinates are converted behind the scenes. // to derpecate this, the homography filter needs to be adapted pub enum LaserSpace { OLD, READY, } pub fn shape_rect(space: LaserSpace, steps: usize) -> LaserPoints { let offset: f32 = match space { LaserSpace::OLD => 0., _ => 1. }; let factor: f32 = match space { LaserSpace::OLD => 1., _ => 2./0xFFF as f32 }; let mut points = Vec::new(); // let steps: usize = 10; for i in (0..=steps).rev() { points.push(laser::Point{ position:[0xFFF as f32 * factor - offset, (0xFFF * i / steps) as f32 * factor - offset], color: [1.0,1.0,1.0], weight: 0, }); } for i in (0..steps).rev() { points.push(laser::Point{ position:[(0xFFF * i / steps) as f32 * factor - offset, 0.0 * factor - offset], color: [1.0,1.0,1.0], weight: 0, }); } for i in 0..steps { points.push(laser::Point{ position:[0.0 * factor - offset, (0xFFF * i / steps) as f32 * factor - offset], color: [1.0,1.0,1.0], weight: 0, }); } for i in 0..=steps { points.push(laser::Point{ position:[(0xFFF * i / steps) as f32 * factor - offset , 0xFFF as f32 * factor - offset], color: [1.0,1.0,1.0], weight: 0, }); } // dbg!("{:?}", &points); LaserPoints { points, space: CoordinateSpace::Laser } } // the different shapes that override the provided lines if needed impl StreamSource{ pub fn get_shape(&self, current_points: LaserPoints, config: &DacConfig) -> LaserPoints { match self { Self::CurrentLayers => current_points, Self::Rectangle => { shape_rect(LaserSpace::READY, 11) }, Self::GridY => { let lines: usize = 5; let half = (0xFFF / 2) as f32; let mut points = Vec::new(); // vertical lines for i in 0..=lines { let offset = if i % 2 == 0 { 0 } else {0xFFF } as f32; let x = i * 0xFFF / lines; points.push(laser::Point{ position:[(x as f32 - half) / half, (offset as f32 - half)/half], color: [0., 0., 0.], weight: 0, }); for j in (0..=0xFFF).step_by(0xFFF / 20) { // go back and forth, so galvo has it easier let y = if i % 2 == 0 { j } else {0xFFF - j}; points.push(laser::Point{ position:[(x as f32 - half)/half, (y as f32 - half)/half], color: [1.,1.,1.], weight: 0, }); } points.push(points[points.len()-1].blanked()); } // for i in 0..=lines { // let offset = if i % 2 == 0 { 0 } else {0xFFF } as f32; // let y = i * 0xFFF / lines; // points.push(laser::Point{ // position:[(offset as f32 - half)/half, (y as f32 - half)/half], // color: [0., 0., 0.], // weight: 0, // }); // for j in (0..=0xFFF).step_by(0xFFF / 10) { // // go back and forth, so galvo has it easier // let x = if i % 2 == 0 { j } else {0xFFF - j}; // points.push(laser::Point{ // position:[(x as f32 - half)/half, (y as f32 - half)/half], // 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 } }, Self::Homography => { config.filters.homography.to_cursor_laserpoints() } StreamSource::Disabled => LaserPoints::default(), // empty set _ => LaserPoints::default(), // empty set } } } #[derive(Debug, Clone)] pub enum Corner{ TopLeft, TopRight, BottomRight, BottomLeft, } impl Corner { pub fn in_laser_space() -> Vec<[f32; 2]>{ vec!([-1.,1.], [1.,1.], [1., -1.], [-1.,-1.]) } pub fn within_bounds(p: [f32;2] ) -> bool { !(p[0] < -1. || p[0] > 1. || p[1] < -1. || p[1] > 1.) } pub fn index(&self) -> usize { match self { Self::TopLeft => 0, Self::TopRight => 1, Self::BottomRight => 2, Self::BottomLeft => 3, } } }