333 lines
No EOL
11 KiB
Rust
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,
|
|
}
|
|
}
|
|
} |