From 3a87be73f9493e2a412b2bbabbd877b8eeadc6df Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Fri, 4 Jul 2025 13:23:58 +0200 Subject: [PATCH] corner-pin homography --- Cargo.lock | 328 +++++++++++++++++++++++++++++++++--- Cargo.toml | 5 + src/bin/render_lines_gui.rs | 230 ++++++++++++++++++++++++- src/trap/filters.rs | 163 ++++++++++++++---- src/trap/laser.rs | 107 ++++++++---- 5 files changed, 737 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea395f3..a79c8fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ dependencies = [ "accesskit", "accesskit_consumer", "hashbrown 0.15.3", - "paste", + "paste 1.0.15", "static_assertions", "windows 0.58.0", "windows-core 0.58.0", @@ -448,7 +448,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "blake3", - "derive_more", + "derive_more 1.0.0", "downcast-rs", "either", "petgraph 0.6.5", @@ -472,7 +472,7 @@ dependencies = [ "bevy_utils", "console_error_panic_hook", "ctrlc", - "derive_more", + "derive_more 1.0.0", "downcast-rs", "wasm-bindgen", "web-sys", @@ -498,7 +498,7 @@ dependencies = [ "bitflags 2.9.1", "blake3", "crossbeam-channel", - "derive_more", + "derive_more 1.0.0", "disqualified", "downcast-rs", "either", @@ -555,7 +555,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "bytemuck", - "derive_more", + "derive_more 1.0.0", "encase", "serde", "wgpu-types 23.0.0", @@ -595,7 +595,7 @@ dependencies = [ "bevy_utils", "bevy_window", "bitflags 2.9.1", - "derive_more", + "derive_more 1.0.0", "nonmax", "radsort", "serde", @@ -643,7 +643,7 @@ dependencies = [ "bevy_utils", "bitflags 2.9.1", "concurrent-queue", - "derive_more", + "derive_more 1.0.0", "disqualified", "fixedbitset 0.5.7", "nonmax", @@ -685,7 +685,7 @@ dependencies = [ "bevy_input", "bevy_time", "bevy_utils", - "derive_more", + "derive_more 1.0.0", "gilrs", ] @@ -749,7 +749,7 @@ dependencies = [ "bevy_tasks", "bevy_transform", "bevy_utils", - "derive_more", + "derive_more 1.0.0", "gltf", "percent-encoding", "serde", @@ -785,7 +785,7 @@ dependencies = [ "bevy_utils", "bitflags 2.9.1", "bytemuck", - "derive_more", + "derive_more 1.0.0", "futures-lite", "image 0.25.6", "ktx2", @@ -806,7 +806,7 @@ dependencies = [ "bevy_math", "bevy_reflect", "bevy_utils", - "derive_more", + "derive_more 1.0.0", "smol_str", ] @@ -888,9 +888,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c2650169161b64f9a93e41f13253701fdf971dc95265ed667d17bea6d2a334f" dependencies = [ "bevy_reflect", - "derive_more", + "derive_more 1.0.0", "glam 0.29.3", - "itertools", + "itertools 0.13.0", "rand 0.8.5", "rand_distr", "serde", @@ -914,7 +914,7 @@ dependencies = [ "bevy_utils", "bitflags 2.9.1", "bytemuck", - "derive_more", + "derive_more 1.0.0", "hexasphere", "serde", "wgpu 23.0.1", @@ -983,7 +983,7 @@ dependencies = [ "bevy_window", "bitflags 2.9.1", "bytemuck", - "derive_more", + "derive_more 1.0.0", "fixedbitset 0.5.7", "nonmax", "radsort", @@ -1031,7 +1031,7 @@ dependencies = [ "bevy_ptr", "bevy_reflect_derive", "bevy_utils", - "derive_more", + "derive_more 1.0.0", "disqualified", "downcast-rs", "erased-serde", @@ -1084,7 +1084,7 @@ dependencies = [ "bevy_window", "bytemuck", "codespan-reporting", - "derive_more", + "derive_more 1.0.0", "downcast-rs", "encase", "futures-lite", @@ -1130,7 +1130,7 @@ dependencies = [ "bevy_render", "bevy_transform", "bevy_utils", - "derive_more", + "derive_more 1.0.0", "serde", "uuid", ] @@ -1157,7 +1157,7 @@ dependencies = [ "bevy_window", "bitflags 2.9.1", "bytemuck", - "derive_more", + "derive_more 1.0.0", "fixedbitset 0.5.7", "guillotiere", "nonmax", @@ -1227,7 +1227,7 @@ dependencies = [ "bevy_utils", "bevy_window", "cosmic-text", - "derive_more", + "derive_more 1.0.0", "serde", "smallvec", "sys-locale", @@ -1258,7 +1258,7 @@ dependencies = [ "bevy_hierarchy", "bevy_math", "bevy_reflect", - "derive_more", + "derive_more 1.0.0", ] [[package]] @@ -1288,7 +1288,7 @@ dependencies = [ "bevy_utils", "bevy_window", "bytemuck", - "derive_more", + "derive_more 1.0.0", "nonmax", "smallvec", "taffy", @@ -1380,7 +1380,7 @@ dependencies = [ "bitflags 2.9.1", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1767,6 +1767,12 @@ dependencies = [ "const_soft_float", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.4" @@ -2020,6 +2026,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "cv-core" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6615281229e05151670257a1dc2bcc46720fe53a533093d9c4f7e534b82a9c88" +dependencies = [ + "derive_more 0.99.20", + "nalgebra 0.21.1", + "num-traits", + "sample-consensus", +] + [[package]] name = "d3d12" version = "0.7.0" @@ -2053,6 +2071,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2295,6 +2326,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2547,6 +2588,15 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -2937,6 +2987,19 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "homography" +version = "0.1.0" +source = "git+https://github.com/azazdeaz/homography#e4ae85c569b584b5d978e8855bddaffe2e7dbd82" +dependencies = [ + "cv-core", + "derive_more 0.99.20", + "eyre", + "itertools 0.10.5", + "nalgebra 0.30.1", + "sample-consensus", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -2973,7 +3036,7 @@ dependencies = [ "gif", "jpeg-decoder", "num-iter", - "num-rational", + "num-rational 0.3.2", "num-traits", "png 0.16.8", "scoped_threadpool", @@ -3003,6 +3066,12 @@ dependencies = [ "arrayvec 0.7.6", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -3071,6 +3140,15 @@ dependencies = [ "mach2", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3449,6 +3527,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3509,7 +3597,7 @@ dependencies = [ "foreign-types 0.5.0", "log", "objc", - "paste", + "paste 1.0.15", ] [[package]] @@ -3524,7 +3612,7 @@ dependencies = [ "foreign-types 0.5.0", "log", "objc", - "paste", + "paste 1.0.15", ] [[package]] @@ -3647,6 +3735,49 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "nalgebra" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6147c3d50b4f3cdabfe2ecc94a0191fd3d6ad58aefd9664cf396285883486" +dependencies = [ + "approx 0.3.2", + "generic-array", + "num-complex 0.2.4", + "num-rational 0.2.4", + "num-traits", + "rand 0.7.3", + "simba 0.1.5", + "typenum", +] + +[[package]] +name = "nalgebra" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb2d0de08694bed883320212c18ee3008576bfe8c306f4c3c4a58b4876998be" +dependencies = [ + "approx 0.5.1", + "matrixmultiply", + "nalgebra-macros", + "num-complex 0.4.6", + "num-rational 0.4.2", + "num-traits", + "simba 0.7.3", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "nannou" version = "0.19.0" @@ -3929,6 +4060,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -3960,6 +4110,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.3.2" @@ -3971,6 +4132,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -4444,12 +4615,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "pennereq" version = "0.3.1" @@ -4644,6 +4834,12 @@ dependencies = [ "toml_edit 0.22.26", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -4803,6 +4999,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -4967,6 +5169,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -5034,6 +5245,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -5043,6 +5263,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sample-consensus" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3404fd9b14a035bdff14fc4097e5d7a16435fc4661e80f19ae5204f8bee3c718" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -5093,6 +5319,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -5184,6 +5416,31 @@ dependencies = [ "libc", ] +[[package]] +name = "simba" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb931b1367faadea6b1ab1c306a860ec17aaa5fa39f367d0c744e69d971a1fb2" +dependencies = [ + "approx 0.3.2", + "num-complex 0.2.4", + "num-traits", + "paste 0.1.18", +] + +[[package]] +name = "simba" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" +dependencies = [ + "approx 0.5.1", + "num-complex 0.4.6", + "num-traits", + "paste 1.0.15", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -5726,7 +5983,10 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_nannou", + "cv-core", + "homography", "iyes_perf_ui", + "nalgebra 0.30.1", "nannou", "nannou_egui", "nannou_laser", @@ -5779,6 +6039,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -6392,6 +6658,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index e8756a5..7a8b8ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,11 @@ nannou_egui = { version = "0.19.0", features = ["wayland"] } serde_repr = "0.1.20" +# homography and its dependents: +homography = { git = "https://github.com/azazdeaz/homography" } +nalgebra = "0.30.0" +cv-core = "0.15.0" + [dev-dependencies] diff --git a/src/bin/render_lines_gui.rs b/src/bin/render_lines_gui.rs index 2dc4136..b2aea70 100644 --- a/src/bin/render_lines_gui.rs +++ b/src/bin/render_lines_gui.rs @@ -6,13 +6,14 @@ // use nannou::lyon::geom::euclid::Transform2D; use nannou::{geom::Rect, math::map_range as nannou_map_range}; use nannou::prelude::*; +use bevy::prelude::Mat3; // for glam::f32::Mat3, which is distinct from nannou::prelude::Mat3 // use nannou_egui::egui::emath::inverse_lerp; use nannou_egui::{self, egui, Egui}; use nannou_laser::DacId; use nannou_laser::{self as laser}; use serde_json::Result; use trap_rust::trap::filters::PointFilters; -use trap_rust::trap::laser::{LaserPoints, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX}; +use trap_rust::trap::laser::{shape_rect, LaserPoints, LaserSpace, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX}; use trap_rust::trap::tracks::CoordinateSpace; use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}}; use zmq::Socket; @@ -21,6 +22,12 @@ use std::time::{Instant, Duration}; use std::collections::HashMap; use serde::{Serialize,Deserialize}; +use homography::find_homography; +use cv_core::FeatureMatch; +// use opencv::prelude::Mat; +// use opencv::imgproc; + + use std::error::Error; use std::fs::File; use std::io::BufReader; @@ -41,6 +48,29 @@ pub struct StreamConfig{ type StreamConfigMap = HashMap; type StreamMap = HashMap>; +#[derive(Debug, Clone)] +enum Corner{ + TopLeft, + TopRight, + BottomLeft, + BottomRight, +} + +impl Corner { + pub fn in_laser_space() -> Vec<[f32; 2]>{ + vec!([-1.,1.], [1.,1.], [-1.,-1.], [1., -1.]) + } + + fn index(&self) -> usize { + match self { + Self::TopLeft => 0, + Self::TopRight => 1, + Self::BottomLeft => 2, + Self::BottomRight => 3, + } + } +} + struct GuiModel { // A handle to the laser API used for spawning streams and detecting DACs. laser_api: Arc, @@ -63,6 +93,9 @@ struct GuiModel { lost_alpha: f32, connected: bool, selected_stream: Option, + canvas_scale: f32, + canvas_translate: Vec2, + canvas_dragging_corner: Option, // canvas_transform: Translation2D, // dragging: bool, } @@ -281,6 +314,9 @@ fn model(app: &App) -> GuiModel { .size(1024, 768) // .key_pressed(key_pressed) // .mouse_wheel(canvas_zoom) + .mouse_pressed(map_mouse_pressed) + .mouse_released(map_mouse_released) + .mouse_moved(map_mouse_moved) .view(view_line_canvas) .build() .unwrap(); @@ -372,6 +408,9 @@ fn model(app: &App) -> GuiModel { connected: true, per_laser_config: get_dac_configs(), selected_stream: None, + canvas_scale: 25., + canvas_translate: Vec2::new(-300.,100.), + canvas_dragging_corner: None, // canvas_transform: Transform2D // dimming_factor: 1., } @@ -809,17 +848,26 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) { let win = app.window_rect(); - let scale = 25.; - let translate_x = -300.; - let translate_y = 100.; - - + let scale = model.canvas_scale; + let translate_x = model.canvas_translate.x; + let translate_y = model.canvas_translate.y; draw_grid(&draw, &win, scale, 1.); // let t = app.time; // let n_points = 10; let thickness = 2.0; + + draw.ellipse() + .x_y(0. + translate_x, 0. + translate_y) + .radius(3.) + .color(BLUE); + draw.line() + .weight(thickness) + .caps_round() + .color(RED) + .points([0. * scale + translate_x, 0. * -scale + translate_y].into(), [1. * scale + translate_x, 0. * -scale + translate_y].into()); + // let hz = ((app.mouse.x + win.right()) / win.w()).powi(4) * 1000.0; // TODO refactor to using euclid::point2D for scale @@ -836,6 +884,29 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) { .join_round() .points_colored(vertices); } + + for (dac_id, config) in model.per_laser_config.iter() { + let rect = shape_rect(LaserSpace::READY); + let points = config.filters.reverse(&rect); + + + + let vertices = points.points.iter().map(|p| { + let color = if model.selected_stream == Some(dac_id.clone()) { + srgba(1.,0.,0.,1.) + } else { + srgba(p.color[0], p.color[1], p.color[0], 0.5) + }; + + let pos = [p.position[0] * scale + translate_x, p.position[1] * -scale + translate_y]; + (pos, color) + }); + + draw.polyline() + .weight(thickness) + .join_round() + .points_colored(vertices); + } // put everything on the frame @@ -982,11 +1053,152 @@ fn style() -> egui::Style { fn mouse_moved(_app: &App, _model: &mut GuiModel, _pos: Point2) { } -fn mouse_pressed(_app: &App, _model: &mut GuiModel, _button: MouseButton) { - // _model.dragging +fn laser_corners_to_world(filters: &PointFilters) -> Vec<[f32;2]> { + let corners_raw: Vec<[f32; 2]> = Corner::in_laser_space(); + let corners_laser: LaserPoints = corners_raw.into(); + + let world_points = filters.reverse(&corners_laser); + let world_corners: Vec<[f32;2]> = world_points.points.iter().map(|p| { + p.position + }).collect(); + world_corners } -fn mouse_released(_app: &App, _model: &mut GuiModel, _button: MouseButton) {} +fn map_mouse_moved(_app: &App, model: &mut GuiModel, pos: Point2) { + + let corner = match &model.canvas_dragging_corner { + None => return, + Some(c) => c, + }; + let dac_id = match &model.selected_stream { + None => return, + Some(d) => d, + }; + + let config = model.per_laser_config.get_mut(&dac_id).expect("Dac config unavailable"); + + // 1. reverse the canvas space to world space: + let world_point = [ + (pos[0] - model.canvas_translate.x) / model.canvas_scale, + (pos[1] - model.canvas_translate.y) / -model.canvas_scale, + ]; + // 1.b reverse the filters _except homography_ on the world point, to get the source_point + // TODO + + // 2. find existing corners in world space, replacing the + // dragging corner + let mut world_corners = laser_corners_to_world(&config.filters); + world_corners[corner.index()] = world_point; + + // 3. find new homography: + // let matches = Vec::new(); + let laser_corners: LaserPoints = Corner::in_laser_space().into(); + let distorted_laser_corners: Vec<[f32;2]> = config.filters.reverse_without_homography(&laser_corners).into(); + + let matches: Vec>> = world_corners.iter().zip(distorted_laser_corners).map(|(world, laser)| { + FeatureMatch( + nalgebra::Point2::new(world[0] as f64, world[1] as f64), + nalgebra::Point2::new(laser[0] as f64, laser[1] as f64), + ) + }).collect::>(); + + dbg!(&matches); + + // 4. apply + + // let matches = vec![ + // FeatureMatch(nalgebra::Point2::new(0.0 as f64, 0.0 as f64), nalgebra::Point2::new(0.0 as f64, 2.0 as f64)), + // FeatureMatch(nalgebra::Point2::new(1.0 as f64, 1.0 as f64), nalgebra::Point2::new(1.0 as f64, 3.0 as f64)), + // FeatureMatch(nalgebra::Point2::new(2.0 as f64, 4.0 as f64), nalgebra::Point2::new(2.0 as f64, 6.0 as f64)), + // FeatureMatch(nalgebra::Point2::new(7.0 as f64, 3.0 as f64), nalgebra::Point2::new(7.0 as f64, 5.0 as f64)), + // ]; + + + // nalgebra::base::Matrix3, which should become glam::f32::Mat3 + let m = find_homography(matches).unwrap(); + + let mat: Mat3 = Mat3::from_cols_array(&[m[0] as f32, m[1] as f32, m[2] as f32, m[3] as f32, m[4] as f32, m[5] as f32, m[6] as f32, m[7] as f32, m[8] as f32]); + + + // let roi_corners_mat = Mat::from_slice(world_corners)?; + // let dst_corners_mat = Mat::from_slice(Corner::in_laser_space())?; + // let matrix = imgproc::get_perspective_transform(&roi_corners_mat, &dst_corners_mat)?; + // dbg!(&m, &mat); + + config.filters.homography.homography_matrix = mat; + + + // let matches = vec![ + // FeatureMatch(Point2::new(0.0, 0.0), Point2::new(0.0, 2.0)), + // FeatureMatch(Point2::new(1.0, 1.0), Point2::new(1.0, 3.0)), + // FeatureMatch(Point2::new(2.0, 4.0), Point2::new(2.0, 6.0)), + // FeatureMatch(Point2::new(7.0, 3.0), Point2::new(7.0, 5.0)), + // ]; + + +} + +fn map_mouse_pressed(app: &App, model: &mut GuiModel, button: MouseButton) { + if button != MouseButton::Left { + // ignore + return; + } + + if let Some(dac_id) = &model.selected_stream { + const MATCH_DISTANCE: f32 = 30.; // screen pixels + + let config = model.per_laser_config.get(&dac_id).expect("Dac config unavailable"); + + // find close corner to drag + let corners = laser_corners_to_world(&config.filters); + let canvas_corners: Vec<[f32;2]> = corners.iter().map(|p| { + [ + p[0] * model.canvas_scale + model.canvas_translate.x, + p[1] * -model.canvas_scale + model.canvas_translate.y + ] + }).collect(); + + dbg!("{:?}", &canvas_corners); + dbg!("{:?}, {:?}", &app.mouse.x, &app.mouse.y); + + let mut selected: Option = None; + for (i, c) in canvas_corners.iter().enumerate(){ + if (app.mouse.x - c[0]).abs() < MATCH_DISTANCE && (app.mouse.y - c[1]).abs() < MATCH_DISTANCE { + dbg!("close to corner! {:?}", &c); + match i { + 0 => selected = Some(Corner::TopLeft), + 1 => selected = Some(Corner::TopRight), + 2 => selected = Some(Corner::BottomLeft), + 3 => selected = Some(Corner::BottomRight), + _ => selected = None, + } + break; + } + } + dbg!("selected! {:?}", &selected); + model.canvas_dragging_corner = selected; + + + } + + + let half_w = (1024 / 2) as f32; + let half_h = (1024 / 2) as f32; + + let x = app.mouse.x / half_w; + let y = app.mouse.y / half_h; + + if x > 1. || x < -1. || y > 1. || y < -1. { + println!("Click outside of canvas: {} {}", x, y); + return + } + + +} + +fn map_mouse_released(_app: &App, model: &mut GuiModel, _button: MouseButton) { + model.canvas_dragging_corner = None; +} fn mouse_wheel(_app: &App, _model: &mut GuiModel, _dt: MouseScrollDelta, _phase: TouchPhase) { // canvas zoom diff --git a/src/trap/filters.rs b/src/trap/filters.rs index 2314b1b..f896a69 100644 --- a/src/trap/filters.rs +++ b/src/trap/filters.rs @@ -9,6 +9,7 @@ pub trait Filter { // fn set_config(&self) // fn set_config(&self) fn apply(&self, points: &LaserPoints) -> LaserPoints; + fn reverse(&self, points: &LaserPoints) -> LaserPoints; } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -84,6 +85,34 @@ impl PointFilters { p } + // laser space to world space + pub fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + let mut p = self.dim.reverse(points); + // dbg!("in {:?}", &p.points[0]); + p = self.pincushion.reverse(&p); + // dbg!("undistort {:?}", &p.points[0]); + p = self.scale.reverse(&p); + // dbg!("unscale {:?}", &p.points[0]); + p = self.homography.reverse(&p); + // dbg!("unperspective {:?}", &p.points[0]); + // p = self.crop.reverse(&p); + p + } + + // same as reverse() but ignores homography. Required when gathering points for calculating homography. + pub fn reverse_without_homography(&self, points: &LaserPoints) -> LaserPoints{ + let mut p = self.dim.reverse(points); + // dbg!("in {:?}", &p.points[0]); + p = self.pincushion.reverse(&p); + // dbg!("undistort {:?}", &p.points[0]); + p = self.scale.reverse(&p); + // dbg!("unscale {:?}", &p.points[0]); + // p = self.homography.reverse(&p); + // dbg!("unperspective {:?}", &p.points[0]); + // p = self.crop.reverse(&p); + p + } + pub fn with_homography(mut self, h: Mat3) -> Self{ self.homography.homography_matrix = h; self @@ -116,9 +145,8 @@ impl Filter for HomographyFilter { _ => panic!("Invalid coordinate space"), }; - // 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 / 2.; // when using TMP_PYTHON_LASER_H + // also converts from world space to laser space (origin in center) + let s = 0xFFF as f32 / 2.; let normalised_pos: [f32;2] = [new_position[0]/s - 1., new_position[1]/s - 1.]; laser::Point { position: normalised_pos, @@ -131,6 +159,32 @@ impl Filter for HomographyFilter { space: CoordinateSpace::Laser } } + + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + let space = points.space; + let inv_matrix = self.homography_matrix.inverse(); + + let projected_positions: Vec = points.points.iter().map(|point| { + let s = 0xFFF as f32 / 2.; + let p = point.position; + let de_normalised_position: [f32;2] = [(p[0] + 1.)*s, (p[1]+1.)*s]; + let new_position = match space { + CoordinateSpace::World => p, + CoordinateSpace::Laser => apply_homography_matrix(inv_matrix, &p), + _ => panic!("Invalid coordinate space"), + + }; + // also converts from world space to laser space (origin in center) + laser::Point { + position: new_position, + .. point.clone() + } + }).collect(); + LaserPoints{ + points: projected_positions, + space: CoordinateSpace::World + } + } } impl Default for HomographyFilter{ @@ -266,40 +320,64 @@ impl Filter for CropFilter { space } } + + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + // we cannot really add points, can we + return LaserPoints{ + points: points.points.clone(), + space: points.space, + }; + } +} + +fn change_brightness(points: &LaserPoints, intensity: f32) -> LaserPoints{ + let new_points = points.points.iter().map(|point| { + let mut color = point.color.clone(); + if intensity != 1.0 { + color[0] *= intensity; + color[1] *= intensity; + color[2] *= intensity; + } + Point::new(point.position, color) + }).collect(); + LaserPoints { + points: new_points, + space: points.space + } } impl Filter for DimFilter { fn apply(&self, points: &LaserPoints) -> LaserPoints { - let new_points = points.points.iter().map(|point| { - let mut color = point.color.clone(); - if self.intensity != 1.0 { - color[0] *= self.intensity; - color[1] *= self.intensity; - color[2] *= self.intensity; - } - Point::new(point.position, color) - }).collect(); - LaserPoints { - points: new_points, - space: points.space + change_brightness(points, self.intensity) + } + + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + change_brightness(points, 1./self.intensity) + } +} + +fn scale(points: &LaserPoints, factor: f32) -> LaserPoints{ + let new_points = points.points.iter().map(|point| { + let mut position = point.position.clone(); + if factor != 1.0 { + position[0] *= factor; + position[1] *= factor; } + Point::new(position, point.color) + }).collect(); + LaserPoints { + points: new_points, + space: points.space } } impl Filter for ScaleFilter { fn apply(&self, points: &LaserPoints) -> LaserPoints { - let new_points = points.points.iter().map(|point| { - let mut position = point.position.clone(); - if self.factor != 1.0 { - position[0] *= self.factor; - position[1] *= self.factor; - } - Point::new(position, point.color) - }).collect(); - LaserPoints { - points: new_points, - space: points.space - } + scale(points, self.factor) + } + + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + scale(points, 1./self.factor) } } @@ -310,8 +388,6 @@ impl Filter for PincushionFilter { // becomes trivial fn apply(&self, points: &LaserPoints) -> LaserPoints{ let space = points.space; - // dbg!(&space); - // assert!(!matches!(space, CoordinateSpace::Laser)); let projected_positions: Vec = points.points.iter().map(|point| { let p = point.position; @@ -340,4 +416,33 @@ impl Filter for PincushionFilter { space } } + + // should be a reversal of what apply() does. TODO: check if the below actually + // checks out. It might need to be slightly different, in particular, radius calculation + // as an approximate, it might do + fn reverse(&self, points: &LaserPoints) -> LaserPoints{ + let space = points.space; + + let projected_positions: Vec = points.points.iter().map(|point| { + let p = point.position; + + let radius = (p[0].powi(2) + p[1].powi(2)).sqrt(); + + let new_position = [ + p[0] / (1. + self.k_x * radius.powi(2)+ self.k_x2 * radius.powi(4)), + p[1] / (1. + self.k_y * radius.powi(2)+ self.k_y2 * radius.powi(4)) + ]; + + laser::Point { + position: new_position, + .. point.clone() + } + + }).collect(); + + LaserPoints{ + points: projected_positions, + space + } + } } diff --git a/src/trap/laser.rs b/src/trap/laser.rs index c58fce4..a830062 100644 --- a/src/trap/laser.rs +++ b/src/trap/laser.rs @@ -7,11 +7,39 @@ use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace}; 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], @@ -132,44 +160,59 @@ impl Default for LaserPoints { } } +// 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) -> 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.,1.,1.], + 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.,1.,1.], + 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.,1.,1.], + 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.,1.,1.], + 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 => { - let mut points = Vec::new(); - let steps: usize = 10; - for i in (0..=steps).rev() { - points.push(laser::Point{ - position:[0xFFF as f32, (0xFFF * i / steps) as f32], - color: [1.,1.,1.], - weight: 0, - }); - } - for i in (0..=steps).rev() { - points.push(laser::Point{ - position:[(0xFFF * i / steps) as f32, 0.0], - color: [1.,1.,1.], - weight: 0, - }); - } - for i in 0..=steps { - points.push(laser::Point{ - position:[0.0 , (0xFFF * i / steps) as f32], - color: [1.,1.,1.], - weight: 0, - }); - } - for i in 0..=steps { - points.push(laser::Point{ - position:[(0xFFF * i / steps) as f32 , 0xFFF as f32], - color: [1.,1.,1.], - weight: 0, - }); - } - // dbg!("{:?}", &points); - LaserPoints { points, space: CoordinateSpace::Laser } + shape_rect(LaserSpace::OLD) }, Self::Grid => { let lines: usize = 5;