Compare commits

...

3 commits

Author SHA1 Message Date
Ruben van de Ven
d2fa6e007c Filters work with cleaner homography, basic map zoom 2025-07-04 14:11:22 +02:00
Ruben van de Ven
8cc79b24a7 corner-pin homography 2025-07-04 13:27:09 +02:00
Ruben van de Ven
3a87be73f9 corner-pin homography 2025-07-04 13:23:58 +02:00
5 changed files with 739 additions and 107 deletions

328
Cargo.lock generated
View file

@ -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"

View file

@ -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]

View file

@ -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,14 @@ use std::time::{Instant, Duration};
use std::collections::HashMap;
use serde::{Serialize,Deserialize};
use nannou::winit::dpi::PhysicalPosition;
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 +50,29 @@ pub struct StreamConfig{
type StreamConfigMap = HashMap<DacId, StreamConfig>;
type StreamMap = HashMap<DacId, laser::FrameStream<LaserModel>>;
#[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<laser::Api>,
@ -63,6 +95,9 @@ struct GuiModel {
lost_alpha: f32,
connected: bool,
selected_stream: Option<DacId>,
canvas_scale: f32,
canvas_translate: Vec2,
canvas_dragging_corner: Option<Corner>,
// canvas_transform: Translation2D<f32, ScreenSpace, ScreenSpace>,
// dragging: bool,
}
@ -280,7 +315,10 @@ fn model(app: &App) -> GuiModel {
.new_window()
.size(1024, 768)
// .key_pressed(key_pressed)
// .mouse_wheel(canvas_zoom)
.mouse_wheel(map_wheel_zoom)
.mouse_pressed(map_mouse_pressed)
.mouse_released(map_mouse_released)
.mouse_moved(map_mouse_moved)
.view(view_line_canvas)
.build()
.unwrap();
@ -372,6 +410,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.,
}
@ -704,7 +745,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
}
if ui
.add(egui::Slider::new(&mut selected_config.filters.scale.factor, 0.0..=2.).text("Scale"))
.add(egui::Slider::new(&mut selected_config.filters.scale.factor, 0.001..=2.).text("Scale"))
.changed()
{
let factor = selected_config.filters.scale.factor;
@ -796,8 +837,9 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
// get canvas to draw on
let draw = app.draw();
// set background to blue
// LaserPoints supports a mode that sends points directly into the laser
// if this mode is enabled by the sender, set background to blue
// Red if nothing is sending / not for too long.
let bgcolor = match model.current_lines.space {
CoordinateSpace::Laser => MEDIUMSLATEBLUE,
_ => match model.connected{
@ -809,20 +851,29 @@ 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;
// background grid
draw_grid(&draw, &win, scale, 1.);
// let t = app.time;
// let n_points = 10;
let thickness = 2.0;
// let hz = ((app.mouse.x + win.right()) / win.w()).powi(4) * 1000.0;
// TODO refactor to using euclid::point2D for scale
// draw origin indicator
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());
// draw current laser lines
for line in &model.current_lines.lines{
let vertices = line.points.iter().map(|p| {
let color = srgba(p.color.red, p.color.green, p.color.blue, p.color.alpha);
@ -836,6 +887,33 @@ fn view_line_canvas(app: &App, model: &GuiModel, frame: Frame) {
.join_round()
.points_colored(vertices);
}
// show each configured laser in the canvas. Highlight the selected.
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::hex("e52d9f");
// ORCHID. to_srgba()
srgba(229./255.,45./255.,159./255.,0.8)
} else {
// ORCHID;
srgba(1.,1.,1., 0.2)
};
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,12 +1060,137 @@ 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. For the dragging point, 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,
];
// 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 the corners of the laser, correct them for all
// geometric filters, except the homography itself.
let laser_corners: LaserPoints = Corner::in_laser_space().into();
let distorted_laser_corners: Vec<[f32;2]> = config.filters.reverse_without_homography(&laser_corners).into();
// 4. find new homography on pairs of points, and convert to compatible matrix type
let matches: Vec<FeatureMatch<nalgebra::Point2<f64>>> = 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::<Vec<_>>();
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]);
// 5. update config in Gui and laser stream threat
config.filters.homography.homography_matrix = mat.clone();
let selected_laser_stream = model.laser_streams.get(&dac_id);
if let Some(stream) = selected_laser_stream {
stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.homography.homography_matrix = mat;
}).unwrap();
}
fn mouse_wheel(_app: &App, _model: &mut GuiModel, _dt: MouseScrollDelta, _phase: TouchPhase) {
// canvas zoom
}
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<Corner> = 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 map_wheel_zoom(_app: &App, model: &mut GuiModel, dt: MouseScrollDelta, _phase: TouchPhase) {
let (x,y) = match dt {
MouseScrollDelta::PixelDelta(PhysicalPosition::<f64>{ x, y }) => (x as f32, y as f32),
MouseScrollDelta::LineDelta(x, y) => (x,y),
};
model.canvas_scale += y;
}

View file

@ -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,12 +145,11 @@ 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,
position: new_position,
.. point.clone()
}
@ -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<laser::Point> = points.points.iter().map(|point| {
let p = point.position;
// let s = 0xFFF as f32 / 2.;
// 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<laser::Point> = 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<laser::Point> = 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
}
}
}

View file

@ -7,11 +7,39 @@ 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],
@ -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;