diff --git a/Cargo.lock b/Cargo.lock index 90ef64d..6ae019a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -866,6 +875,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1305,6 +1323,10 @@ dependencies = [ "futures-executor", "glam", "image", + "log", + "profiling", + "puffin", + "puffin_http", "rand", "rand_xoshiro", "spirv-builder", @@ -2158,6 +2180,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2851,6 +2879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", + "puffin", ] [[package]] @@ -2863,6 +2892,36 @@ dependencies = [ "syn", ] +[[package]] +name = "puffin" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9dae7b05c02ec1a6bc9bcf20d8bc64a7dcbf57934107902a872014899b741f" +dependencies = [ + "anyhow", + "bincode", + "byteorder", + "cfg-if", + "itertools 0.10.5", + "lz4_flex", + "once_cell", + "parking_lot", + "serde", +] + +[[package]] +name = "puffin_http" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739a3c7f56604713b553d7addd7718c226e88d598979ae3450320800bd0e9810" +dependencies = [ + "anyhow", + "crossbeam-channel", + "log", + "parking_lot", + "puffin", +] + [[package]] name = "qoi" version = "0.4.1" diff --git a/crates/flare/Cargo.toml b/crates/flare/Cargo.toml index 20f0a20..ece1372 100644 --- a/crates/flare/Cargo.toml +++ b/crates/flare/Cargo.toml @@ -18,9 +18,13 @@ futures = "0.3" futures-executor.workspace = true glam.workspace = true image = "0.25" +puffin = "0.19" +puffin_http = "0.16" rand = { workspace = true, default-features = true } rand_xoshiro.workspace = true wgpu.workspace = true +log = "0.4.25" +profiling = { version = "1.0", features = ["profile-with-puffin"]} [build-dependencies] spirv-builder.workspace = true diff --git a/crates/flare/examples/transform_editor.rs b/crates/flare/examples/transform_editor.rs index 65cd0a2..9785fb8 100644 --- a/crates/flare/examples/transform_editor.rs +++ b/crates/flare/examples/transform_editor.rs @@ -1,5 +1,6 @@ use flare::transform_editor::TransformEditor; use flare_shader::Coefs; +use log::info; struct TransformEditorApp { transform_editor: TransformEditor, @@ -23,31 +24,44 @@ impl eframe::App for TransformEditorApp { } }); if ui.button("Add Transform").clicked() { - self.transform_editor.add_transform(Coefs::IDENTITY) + self.transform_editor.add_transform() } }) }); - egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { - ui.label(format!("Transforms: {}", self.transform_editor.len())) - }); - egui::CentralPanel::default().show(ctx, |ui| { - self.transform_editor.ui(ui) + self.transform_editor.ui(ctx, ui) }); } } fn main() -> eframe::Result { + std::env::set_var("RUST_LOG", "info"); env_logger::init(); + info!("WTF"); let initial_dimensions = egui::vec2(800., 600.); + let mut wgpu_options = egui_wgpu::WgpuConfiguration::default(); + wgpu_options.present_mode = wgpu::PresentMode::Immediate; + wgpu_options.desired_maximum_frame_latency = Some(1); let native_options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size(initial_dimensions), + wgpu_options, renderer: eframe::Renderer::Wgpu, + vsync: false, ..Default::default() }; + puffin::set_scopes_on(true); + match puffin_http::Server::new("localhost:8585") { + Ok(server) => { + info!("Server open"); + std::mem::forget(server); + } + _ => {} + } + // std::mem::forget(puffin_http::Server::new("127.0.0.1:8585").expect("Unable to start server")); + eframe::run_native( "transform_editor", native_options, diff --git a/crates/flare/src/main.rs b/crates/flare/src/main.rs index dd04c62..ffd513d 100644 --- a/crates/flare/src/main.rs +++ b/crates/flare/src/main.rs @@ -1,3 +1,5 @@ +extern crate core; + use eframe::Frame; use eframe::epaint::PaintCallbackInfo; use egui::{Context, Rect}; diff --git a/crates/flare/src/transform_editor.rs b/crates/flare/src/transform_editor.rs index 2a9c59d..cecd73c 100644 --- a/crates/flare/src/transform_editor.rs +++ b/crates/flare/src/transform_editor.rs @@ -1,51 +1,266 @@ -use egui::{emath, Rect, Sense}; +use bytemuck::Contiguous; +use egui::{DragValue, Rect, Sense, Ui, emath}; use epaint::{Color32, Shape, Stroke}; +use log::info; use flare_shader::Coefs; -pub struct TransformEditor { - transforms: Vec +pub const TRANSFORM_POINT_RADIUS: f32 = 0.03; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum TransformElement { + Origin, + X, + Y, } -fn coef_to_shapes(coef: &Coefs, to_screen: emath::RectTransform) -> Vec { - let origin = to_screen.transform_pos(egui::pos2(coef.c, -coef.f)); - let x = to_screen.transform_pos(egui::pos2(coef.c + coef.a, -(coef.f + coef.b))); - let y = to_screen.transform_pos(egui::pos2(coef.c + coef.d, -(coef.f + coef.e))); +#[derive(Copy, Clone)] +pub struct Transform { + origin: egui::Pos2, + x: egui::Pos2, + y: egui::Pos2, + active_element: Option, +} - let stroke = Stroke::new(2.0, Color32::BLUE); - let translucent = Color32::from_rgba_unmultiplied(0, 0, 255, 8); +impl Transform { + pub fn check_active(&mut self, hover_pos: Option, drag_delta: egui::Vec2) -> bool { + if hover_pos.is_none() { + self.active_element.take(); + return false; + } - let shapes = vec![ - Shape::circle_stroke(origin, 5.0, stroke), - Shape::circle_stroke(x, 5.0, stroke), - Shape::circle_stroke(y, 5.0, stroke), - Shape::convex_polygon(vec![origin, x, y], translucent, stroke), - ]; + let hover_pos = hover_pos.unwrap(); + self.active_element = if test_point_in_circle(hover_pos, self.x + drag_delta, TRANSFORM_POINT_RADIUS * 2.0) { + Some(TransformElement::X) + } else if test_point_in_circle(hover_pos, self.y + drag_delta, TRANSFORM_POINT_RADIUS * 2.0) { + Some(TransformElement::Y) + } else if test_point_in_circle(hover_pos, self.origin + drag_delta, TRANSFORM_POINT_RADIUS * 2.0) + || test_point_in_triangle(hover_pos, self.origin + drag_delta, self.x + drag_delta, self.y + drag_delta) + { + Some(TransformElement::Origin) + } else { + None + }; - shapes + self.active_element.is_some() + } + + pub fn drag_update(&mut self, drag_delta: egui::Vec2) { + if let Some(active_element) = self.active_element { + match active_element { + TransformElement::X => self.x += drag_delta, + TransformElement::Y => self.y += drag_delta, + TransformElement::Origin => { + self.origin += drag_delta; + self.x += drag_delta; + self.y += drag_delta; + } + } + } + } + + pub fn ui_draw(&self, painter: &egui::Painter, to_screen: emath::RectTransform, drag_delta: egui::Vec2) { + let color_active = Color32::from_rgba_unmultiplied(0, 0, u8::MAX_VALUE, 8); + + let stroke = Stroke::new(2.0, Color32::BLUE); + + let origin_ifs = match self.active_element { + Some(TransformElement::Origin) => self.origin + drag_delta, + _ => self.origin + }; + let origin_screen = to_screen.transform_pos(origin_ifs); + + let x_ifs = match self.active_element { + Some(TransformElement::X | TransformElement::Origin) => self.x + drag_delta, + _ => self.x + }; + let x_screen = to_screen.transform_pos(x_ifs); + + let y_ifs = match self.active_element { + Some(TransformElement::Y | TransformElement::Origin) => self.y + drag_delta, + _ => self.y + }; + let y_screen = to_screen.transform_pos(y_ifs); + + let body_color = if self.active_element.is_some() { + color_active + } else { + Color32::TRANSPARENT + }; + painter.add(Shape::convex_polygon( + vec![origin_screen, x_screen, y_screen], + body_color, + stroke, + )); + + let point_radius = to_screen.scale().min_elem() * TRANSFORM_POINT_RADIUS; + + let origin_color = match self.active_element { + Some(TransformElement::Origin) => color_active, + _ => Color32::TRANSPARENT, + }; + painter.add(Shape::circle_stroke(origin_screen, point_radius, stroke)); + painter.add(Shape::circle_filled( + origin_screen, + point_radius, + origin_color, + )); + + let x_color = match self.active_element { + Some(TransformElement::X) => color_active, + _ => Color32::TRANSPARENT, + }; + painter.add(Shape::circle_stroke(x_screen, point_radius, stroke)); + painter.add(Shape::circle_filled(x_screen, point_radius, x_color)); + + let y_color = match self.active_element { + Some(TransformElement::X) => color_active, + _ => Color32::TRANSPARENT, + }; + painter.add(Shape::circle_stroke(y_screen, point_radius, stroke)); + painter.add(Shape::circle_filled(y_screen, point_radius, y_color)); + } +} + +impl Default for Transform { + fn default() -> Self { + Transform { + origin: egui::pos2(0.0, 0.0), + x: egui::pos2(1.0, 0.0), + y: egui::pos2(0.0, -1.0), + active_element: None, + } + } +} + +#[derive(Clone, Default)] +pub struct TransformEditor { + transforms: Vec, + hover_index: Option, + hover_pos: Option, + drag_index: Option, + drag_start_pos: Option, +} + +fn test_point_in_triangle(pt: egui::Pos2, v1: egui::Pos2, v2: egui::Pos2, v3: egui::Pos2) -> bool { + puffin::profile_function!(); + + // https://stackoverflow.com/a/2049593 + let sign = |p1: egui::Pos2, p2: egui::Pos2, p3: egui::Pos2| -> f32 { + (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y) + }; + + let d1 = sign(pt, v1, v2); + let d2 = sign(pt, v2, v3); + let d3 = sign(pt, v3, v1); + + let has_neg = [d1, d2, d3].iter().any(|v| *v < 0.0); + let has_pos = [d1, d2, d3].iter().any(|v| *v > 0.0); + + !(has_neg && has_pos) +} + +fn test_point_in_circle(pt: egui::Pos2, center: egui::Pos2, radius: f32) -> bool { + ((pt.x - center.x).powf(2.0) + (pt.y - center.y).powf(2.0)) < radius.powf(2.0) } impl TransformEditor { pub fn new() -> Self { - let mut editor = Self { transforms: vec![] }; - editor.add_transform(Coefs::IDENTITY); + let mut editor = TransformEditor::default(); + editor.add_transform(); editor } - pub fn add_transform(&mut self, coefs: Coefs) { - self.transforms.push(coefs); + pub fn add_transform(&mut self) { + self.transforms.push(Default::default()); } - pub fn len(&self) -> usize { - self.transforms.len() + fn check_active(&mut self, hover_pos: Option) { + // Find the active transform; the previously active transform has priority + let drag_delta = self.drag_delta(); + if let Some(hover_index) = self.hover_index { + if self.transforms[hover_index].check_active(hover_pos, drag_delta) { + return; + } + } + + for (i, transform) in self.transforms.iter_mut().enumerate() { + if transform.check_active(hover_pos, drag_delta) { + self.hover_index = Some(i); + return; + } + } + + self.hover_index.take(); } - pub fn ui(&mut self, ui: &mut egui::Ui) -> egui::Response { - let (response, painter) = ui.allocate_painter(ui.available_size(), Sense::hover()); + fn drag_delta(&self) -> egui::Vec2 { + if let (Some(hover_pos), Some(drag_start_pos)) = (self.hover_pos, self.drag_start_pos) { + hover_pos - drag_start_pos + } else { + egui::Vec2::ZERO + } + } + pub fn ui_update( + &mut self, + hover_pos: Option, + drag_delta: egui::Vec2, + clicked: bool, + drag_started: bool, + drag_ended: bool, + ) { + if self.transforms.is_empty() { + self.hover_index.take(); + self.drag_index.take(); + self.drag_start_pos.take(); + return; + } + + self.hover_pos = hover_pos; + self.check_active(hover_pos); + + if clicked || drag_started { + self.drag_index = self.hover_index; + self.drag_start_pos = Some(hover_pos.unwrap() - drag_delta); + } + + if drag_ended { + if let Some(drag_index) = self.drag_index { + let drag_delta = self.drag_delta(); + info!("Applying drag delta {:?}", drag_delta); + self.transforms[drag_index].drag_update(drag_delta); + } + + self.drag_index.take(); + self.drag_start_pos.take(); + } + } + + pub fn ui_draw(&mut self, painter: &egui::Painter, to_screen: emath::RectTransform) { + let drag_delta = if let (Some(hover_pos), Some(drag_start_pos)) = (self.hover_pos, self.drag_start_pos) { + hover_pos - drag_start_pos + } else { + egui::Vec2::ZERO + }; + + for (index, transform) in self.transforms.iter().enumerate() { + // Active transform is painted at the end to maintain priority + if self.hover_index.map_or(false, |i| i == index) { + continue; + } + + transform.ui_draw(painter, to_screen, drag_delta); + } + + self.hover_index.map(|i| self.transforms[i].ui_draw(painter, to_screen, drag_delta)); + } + + pub fn ui(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) -> egui::Response { + let (response, painter) = ui.allocate_painter(ui.available_size(), Sense::click_and_drag()); + + // Step one: set up conversions between screen space and IFS space coordinates let interact_rect = response.interact_rect; - - // Aspect-ratio scaling; minimum dimension will be [-2.0, 2.0] let interact_max_dim = interact_rect.width().max(interact_rect.height()); let interact_min_dim = interact_rect.width().min(interact_rect.height()); let interact_max_is_width = interact_max_dim == interact_rect.width(); @@ -59,13 +274,30 @@ impl TransformEditor { let transform_area = Rect::from_min_max(ifs_min, ifs_min * -1.0); let to_screen = emath::RectTransform::from_to(transform_area, response.interact_rect); + let to_ifs = emath::RectTransform::from_to(response.interact_rect, transform_area); - self.transforms.iter().map(|coef| { - coef_to_shapes(coef, to_screen) - }).flat_map(|x| x).for_each(|shape| { - let _ = painter.add(shape); + egui::TopBottomPanel::bottom("response_stats").show(ctx, |ui| { + let hover_pos_string = response.hover_pos().map_or_else(|| "None".to_owned(), |p| { + let ifs_pos = to_ifs.transform_pos(p); + format!("({} {})", ifs_pos.x, ifs_pos.y) + }); + ui.label(format!("Hover Pos: {}", hover_pos_string)); + ui.label(format!("Hover Index: {:?}", self.hover_index)); + ui.label(format!("Drag Index: {:?}", self.drag_index)); }); + // Step two: update internal state based on recent interactions + self.ui_update( + response.hover_pos().map(|v| to_ifs.transform_pos(v)), + response.drag_delta() / interact_max_dim, + response.clicked(), + response.drag_started(), + response.drag_stopped(), + ); + + // Step three: draw the transforms + self.ui_draw(&painter, to_screen); + response } -} \ No newline at end of file +}