laserspace/src/trap/laser.rs
2025-07-04 16:12:33 +02:00

333 lines
No EOL
11 KiB
Rust

use bevy::prelude::*;
use nannou_laser as laser;
use std::time::Instant;
use serde::{Deserialize, Serialize};
use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace};
use super::tracks::{RenderableLines};
#[derive(Debug)]
pub struct LaserPoints{
pub points: Vec<laser::Point>,
pub space: CoordinateSpace
}
impl From<Vec<[f32;2]>> 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<Vec<[f32;2]>> 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_lines: RenderableLines,
// pub dimming: f32,
pub config: DacConfig, // per dac configuration
}
impl LaserModel{
pub fn new() -> Self{
Self{
t: Instant::now(),
current_lines: RenderableLines::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<LaserModel>,
}
#[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,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum StreamSource {
Disabled,
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; 5] = [
StreamSource::Disabled,
StreamSource::CurrentLines,
StreamSource::Rectangle,
StreamSource::Grid,
StreamSource::WorldGrid,
// 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::CurrentLines, filters: PointFilters::default().with_homography(LASER_H) }
}
}
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: [0.2,0.2,0.2],
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: [0.2,0.2,0.2],
weight: 0,
});
}
for i in 0..steps {
points.push(laser::Point{
position:[0.0 * factor - offset, (0xFFF * i / steps) as f32 * factor - offset],
color: [0.2,0.2,0.2],
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: [0.2,0.2,0.2],
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_lines: LaserPoints) -> LaserPoints {
match self {
Self::CurrentLines => current_lines,
Self::Rectangle => {
shape_rect(LaserSpace::READY, 11)
},
Self::Grid => {
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 / 10) {
// 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 }
},
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 index(&self) -> usize {
match self {
Self::TopLeft => 0,
Self::TopRight => 1,
Self::BottomRight => 2,
Self::BottomLeft => 3,
}
}
}