Split track-receiving bin from the simpler line-receiving

This commit is contained in:
Ruben van de Ven 2025-04-10 21:38:37 +02:00
parent 32e1a98608
commit 483f021010
5 changed files with 335 additions and 46 deletions

216
src/bin/render_tracks.rs Normal file
View file

@ -0,0 +1,216 @@
use bevy::prelude::*;
use bevy_nannou::prelude::*;
use bevy_nannou::NannouPlugin;
use nannou_laser::point::Rgb;
use trap_rust::trap::laser::apply_homography_matrix;
use trap_rust::trap::laser::python_cv_h_into_mat3;
use trap_rust::trap::laser::LaserApi;
use trap_rust::trap::laser::LaserModel;
use trap_rust::trap::laser::LaserTimer;
use trap_rust::trap::laser::TMP_PYTHON_LASER_H;
use trap_rust::trap::laser::TMP_PYTHON_LASER_H_FOR_NANNOU;
use trap_rust::trap::shapes::PositionAndIntensity;
use trap_rust::trap::tracks::LaserPoints;
use trap_rust::trap::tracks::RenderableLines;
use trap_rust::trap::tracks::SpawnedTime;
use trap_rust::trap::tracks::Track;
use trap_rust::trap::zmqplugin::ZmqPlugin;
// use iyes_perf_ui::PerfUiPlugin;
use nannou_laser as laser;
use trap_rust::trap::zmqplugin::ZmqReceiveTarget;
use std::time::Duration;
// pub mod trap_rust::trap;
fn main() {
let mut app = App::new();
// app.add_plugins((DefaultPlugins, NannouPlugin))
app.add_plugins((DefaultPlugins, NannouPlugin))
// .add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin)
// .add_plugins(bevy::diagnostic::EntityCountDiagnosticsPlugin)
// .add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin)
// .add_plugins(PerfUiPlugin)
.add_systems(Startup, setup)
.add_systems(Startup, setup_laser)
.add_systems(Update, update)
.add_systems(Update, exit_system)
.add_plugins(ZmqPlugin {
// url: "tcp://localhost:5558".to_string(),
// url: "tcp://100.109.175.82:99173".to_string(),
url: "tcp://127.0.0.1:99173".to_string(),
filter: "".to_string(),
target: ZmqReceiveTarget::TRACKS
})
.run();
}
// Because of world.insert_non_send_resource, this is an exclusive system
// see: https://bevy-cheatbook.github.io/programming/exclusive.html
fn setup_laser(mut commands: Commands) {
// laser works on non-exclusive system (like the normal setup()), _but_ world
// is needed in the laser callback
// Initialise the state that we want to live on the laser thread and spawn the stream.
let laser_model = LaserModel::new();
let _laser_api = laser::Api::new();
// let detected_dacs = _laser_api.detect_dacs(DacVariant::DacVariantHelios);
// while let Ok(res) = detected_dacs {
// if let laser::DetectDacs::Helios { previous_dac } = res {
// info!("DACS: {:?}", previous_dac);
// }
// }
let laser_stream = _laser_api
.new_frame_stream(laser_model, laser_frame_producer)
// .detected_dac(dac)
.build()
.unwrap();
let api = LaserApi {
_laser_api,
laser_stream,
// current_points: Vec::new(),
};
commands.spawn((api, LaserTimer {
// create the non-repeating fuse timer
timer: Timer::new(Duration::from_millis(1000), TimerMode::Repeating),
}));
}
fn setup(mut commands: Commands) {
// Spawn a camera for our main window
// TODO: look into https://docs.rs/visioncortex/latest/visioncortex/struct.PerspectiveTransform.html
commands.spawn(render::NannouCamera);
}
fn text2points_with_color(position_and_intensity: PositionAndIntensity, color: Rgb) -> laser::Point{
let used_color = color.map(|v| v * position_and_intensity[2]);
let p = [position_and_intensity[0], -position_and_intensity[1]];
laser::Point::new(p, color)
}
fn text2points(position_and_intensity: PositionAndIntensity) -> laser::Point{
let color = match position_and_intensity[2] {
1.0 => [1.0; 3],
0.0 => [0.0; 3],
_ => [1.0; 3] // TODO add provided color
};
let p = [position_and_intensity[0], -position_and_intensity[1]];
laser::Point::new(p, color)
}
// impl Into<Mat3> for [[f32;3]; 3] {
// fn from(m) -> Self{
// }
// }
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_FOR_NANNOU);
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
// let dt = model.t.elapsed().as_millis();
// let use_second = (dt % 1000) > 500;
// let positions = match use_second {
// true => trap::shapes::YOUR_FUTURE,
// false => trap::shapes::ARE_YOU_SURE,
// };
let points = model.current_points.clone();
let mut new_points = Vec::new();
for point in points.into_iter() {
let p = point.position;
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; // when using TMP_PYTHON_LASER_H
let new_point = laser::Point {
position: [new_position[0]/s, new_position[1]/s],
.. point
};
new_points.push(new_point);
}
info!("Points {}", new_points.len());
// println!("{:?}", new_points);
frame.add_lines(new_points);
}
fn get_laser_lines(use_second: bool) -> Vec<nannou_laser::Point>{
let positions = match use_second {
true => trap_rust::trap::shapes::YOUR_FUTURE,
false => trap_rust::trap::shapes::ARE_YOU_SURE,
};
let points = positions.iter().cloned().map(text2points).collect();
return points
}
fn exit_system(keys: Res<ButtonInput<KeyCode>>, mut exit: EventWriter<AppExit>) {
if keys.just_pressed(KeyCode::KeyQ) {
info!("Sending exit command");
exit.send(AppExit::Success);
}
}
fn update(
// mut commands: Commands,
// keys: Res<ButtonInput<KeyCode>>,
draws: Query<&Draw>,
mut lasers: Query<(&mut LaserApi, &mut LaserTimer)>,
tracks: Query<(&Track, &SpawnedTime)>,
time: Res<Time>,
) {
let mut lines = RenderableLines::new();
for (track, created_at) in tracks.iter() {
// println!("{} {}, history: {}", track.track_id.to_string(), created_at.instant.elapsed().as_millis(), track.history.len());
let rl = RenderableLines::from(track);
lines.lines.extend(rl.lines);
}
for (laser_api, mut laser_timer) in lasers.iter_mut() {
laser_timer.timer.tick(time.delta());
let version = laser_timer.timer.elapsed().as_millis() > 500;
debug!("{} {}", version, laser_timer.timer.elapsed().as_millis());
// let lines = get_laser_lines(version);
let points: LaserPoints = (&lines).into();
laser_api.laser_stream.send(|laser| {
let laserpoints: LaserPoints = points;
// TODO: homography
laser.current_points = laserpoints;
}).unwrap();
}
for draw in draws.iter() {
draw.background().color(DIM_GRAY);
draw.ellipse().color(LIGHT_GRAY).w_h(100.0, 100.0);
}
}

View file

@ -1,18 +1,16 @@
use bevy::ecs::system::SystemState;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::view::RenderLayers;
use bevy::window::WindowResolution;
use bevy_nannou::prelude::properties::spatial::position;
use bevy_nannou::prelude::*; use bevy_nannou::prelude::*;
use bevy_nannou::NannouPlugin; use bevy_nannou::NannouPlugin;
use iyes_perf_ui::prelude::*;
use nannou_laser::point::Rgb; use nannou_laser::point::Rgb;
use nannou_laser::DacVariant;
use nannou_laser::DetectedDacError;
use trap::laser::apply_homography_matrix; use trap::laser::apply_homography_matrix;
use trap::laser::python_cv_h_into_mat3; use trap::laser::python_cv_h_into_mat3;
use trap::laser::LaserApi;
use trap::laser::LaserModel;
use trap::laser::LaserTimer;
use trap::laser::TMP_PYTHON_LASER_H; use trap::laser::TMP_PYTHON_LASER_H;
use trap::laser::TMP_PYTHON_LASER_H_FOR_NANNOU;
use trap::shapes::PositionAndIntensity; use trap::shapes::PositionAndIntensity;
use trap::tracks::LaserPoints; use trap::tracks::LaserPoints;
use trap::tracks::RenderableLines; use trap::tracks::RenderableLines;
@ -22,9 +20,8 @@ use trap::zmqplugin::ZmqPlugin;
// use iyes_perf_ui::PerfUiPlugin; // use iyes_perf_ui::PerfUiPlugin;
use nannou_laser as laser; use nannou_laser as laser;
use std::fs; use trap::zmqplugin::ZmqReceiveTarget;
use std::time::Instant;
use std::time::Duration; use std::time::Duration;
@ -40,7 +37,7 @@ fn main() {
// .add_plugins(PerfUiPlugin) // .add_plugins(PerfUiPlugin)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Startup, setup_laser) .add_systems(Startup, setup_laser)
.add_systems(Update, update) // .add_systems(Update, update)
.add_systems(Update, exit_system) .add_systems(Update, exit_system)
@ -48,34 +45,14 @@ fn main() {
// url: "tcp://localhost:5558".to_string(), // url: "tcp://localhost:5558".to_string(),
// url: "tcp://100.109.175.82:99173".to_string(), // url: "tcp://100.109.175.82:99173".to_string(),
url: "tcp://127.0.0.1:99173".to_string(), url: "tcp://127.0.0.1:99173".to_string(),
filter: "".to_string() filter: "".to_string(),
target: ZmqReceiveTarget::LINES
}) })
.run(); .run();
} }
#[derive(Resource)]
struct LaserModel{
t: Instant, // register start time, so that animations can be moving
current_points: LaserPoints
}
impl LaserModel{
pub fn new() -> Self{
Self{
t: Instant::now(),
current_points: Vec::new()
}
}
}
#[derive(Component)]
struct LaserApi{
_laser_api: laser::Api,
laser_stream: laser::FrameStream<LaserModel>,
}
// Because of world.insert_non_send_resource, this is an exclusive system // Because of world.insert_non_send_resource, this is an exclusive system
// see: https://bevy-cheatbook.github.io/programming/exclusive.html // see: https://bevy-cheatbook.github.io/programming/exclusive.html
@ -190,10 +167,6 @@ fn get_laser_lines(use_second: bool) -> Vec<nannou_laser::Point>{
} }
#[derive(Component)]
struct LaserTimer {
timer: Timer,
}

View file

@ -1,4 +1,8 @@
use bevy::prelude::*; use bevy::prelude::*;
use nannou_laser as laser;
use std::time::Instant;
use super::tracks::LaserPoints;
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],
[ 1.02328119e+01, 1.47185254e+02, 1.96295638e+02], [ 1.02328119e+01, 1.47185254e+02, 1.96295638e+02],
@ -22,4 +26,33 @@ 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_in = Vec3::new(p[0],p[1],1.0);
let v_out = h.mul_vec3(v_in); let v_out = h.mul_vec3(v_in);
[v_out[0] / v_out[2], v_out[1] / v_out[2]] [v_out[0] / v_out[2], v_out[1] / v_out[2]]
}
#[derive(Resource)]
pub struct LaserModel{
pub t: Instant, // register start time, so that animations can be moving
pub current_points: LaserPoints
}
impl LaserModel{
pub fn new() -> Self{
Self{
t: Instant::now(),
current_points: Vec::new()
}
}
}
#[derive(Component)]
pub struct LaserApi{
pub _laser_api: laser::Api,
pub laser_stream: laser::FrameStream<LaserModel>,
}
#[derive(Component)]
pub struct LaserTimer {
pub timer: Timer,
} }

View file

@ -40,18 +40,18 @@ pub struct Track {
} }
// coordinates in world space. To be converted to laser::Point or a drawable point // coordinates in world space. To be converted to laser::Point or a drawable point
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct RenderablePoint{ pub struct RenderablePoint{
pub position: Vec2, pub position: Vec2,
pub color: Color pub color: Color
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct RenderableLine{ pub struct RenderableLine{
points: Vec<RenderablePoint> points: Vec<RenderablePoint>
} }
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct RenderableLines{ pub struct RenderableLines{
pub lines: Vec<RenderableLine> pub lines: Vec<RenderableLine>
} }

View file

@ -1,9 +1,8 @@
use zmq::Socket; use zmq::Socket;
use serde_json::Result; use serde_json::Result;
use bevy::{ecs::system::SystemState, prelude::*}; use bevy::{ecs::system::SystemState, prelude::*, render::Render};
use super::tracks::{Frame, Track, TrackBundle}; use super::{laser::{LaserApi, LaserTimer}, tracks::{Frame, LaserPoints, RenderableLines, Track, TrackBundle}};
// use trap::{Frame, Track, TrackBundle}; // use trap::{Frame, Track, TrackBundle};
@ -25,7 +24,63 @@ fn setup_zmq(world: &mut World, params: &mut SystemState<Res<ZmqSettings>>) {
// world.query::<>() // world.query::<>()
} }
fn receive_zmq_messsages(mut commands: Commands, subscriber: NonSend<Socket>, mut tracks_q: Query<&mut Track>) { // fn receive_msg<T>(subscriber: Socket) -> Result<T> {
// let mut items = [
// subscriber.as_poll_item(zmq::POLLIN)
// ];
// let _nr = zmq::poll(&mut items, 0).unwrap();
// 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<T> = serde_json::from_str(&json);
// res
// } else {
// Err()
// }
// }
fn receive_zmq_lines(
subscriber: NonSend<Socket>,
mut lasers: Query<(&mut LaserApi, &mut LaserTimer)>,
time: Res<Time>
) {
let mut items = [
subscriber.as_poll_item(zmq::POLLIN)
];
let _nr = zmq::poll(&mut items, 0).unwrap();
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 lines: RenderableLines = match res {
Ok(lines) => lines, // if Ok(255), set x to 255
Err(_e) => {
println!("No valid json? {json}");
return
}, // if Err("some message"), panic with error message "some message"
};
for (laser_api, mut laser_timer) in lasers.iter_mut() {
laser_timer.timer.tick(time.delta());
// let lines = get_laser_lines(version);
let points: LaserPoints = (&lines).into();
laser_api.laser_stream.send(|laser| {
let laser_points: LaserPoints = points;
laser.current_points = laser_points;
}).unwrap();
}
}
}
fn receive_zmq_tracks(mut commands: Commands, subscriber: NonSend<Socket>, mut tracks_q: Query<&mut Track>) {
let mut items = [ let mut items = [
subscriber.as_poll_item(zmq::POLLIN) subscriber.as_poll_item(zmq::POLLIN)
]; ];
@ -82,9 +137,15 @@ struct ZmqSettings {
filter: String filter: String
} }
pub enum ZmqReceiveTarget {
LINES,
TRACKS,
}
pub struct ZmqPlugin { pub struct ZmqPlugin {
pub url: String, pub url: String,
pub filter: String pub filter: String,
pub target: ZmqReceiveTarget
} }
impl Plugin for ZmqPlugin { impl Plugin for ZmqPlugin {
@ -93,9 +154,15 @@ impl Plugin for ZmqPlugin {
url: self.url.clone(), url: self.url.clone(),
filter: self.filter.clone() filter: self.filter.clone()
}; };
app app
.insert_resource(settings) .insert_resource(settings)
.add_systems(Startup, setup_zmq) .add_systems(Startup, setup_zmq)
.add_systems(Update, receive_zmq_messsages); ;
match self.target {
ZmqReceiveTarget::TRACKS => app.add_systems(Update, receive_zmq_tracks),
ZmqReceiveTarget::LINES => app.add_systems(Update, receive_zmq_lines),
};
} }
} }