Add Panes

This commit is contained in:
Michael Mikovsky
2024-11-23 20:32:08 -07:00
parent 6818a6949f
commit 61cd4767ba
5 changed files with 887 additions and 647 deletions
+1
View File
@@ -12,6 +12,7 @@ eframe = { version = "0.29.1", features = [
"persistence",]}
egui = { version = "0.29.1", features = ["callstack", "default", "log"] }
erased-serde = "0.4.5"
glam = "0.29.2"
rand = "0.8.5"
serde = { version = "1.0.215", features = ["derive"] }
+23 -175
View File
@@ -1,20 +1,16 @@
// use egui::{FontFamily, FontId, RichText, Visuals};
use eframe::egui_glow;
use std::{sync::Arc, time::Instant};
use egui::{accesskit::TextAlign, mutex::Mutex, Align2, Color32, FontId, Pos2, Stroke};
use egui_glow::glow;
// use eframe::egui_glow;
// use std::{sync::Arc, time::Instant};
// use egui::{accesskit::TextAlign, mutex::Mutex, Align2, Color32, FontId, Pos2, Stroke};
// use egui_glow::glow;
use crate::{panes::Pane, point_cloud_renderer::PointRenderer, PaneManager};
use crate::panes::PaneManager;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
// #[derive(serde::Deserialize, serde::Serialize)]
// #[serde(default)] // if we add new fields, give them default values when deserializing old state
// #[serde(default)]
pub struct App {
// #[serde(skip)]
renderer: Arc<Mutex<PointRenderer>>,
points: Vec<(i32, i32, i32, Color32)>,
file_dialog_open: bool,
cur_path: String,
pane_manager: PaneManager,
}
@@ -31,79 +27,35 @@ impl App {
// }
Some(Self {
renderer: Arc::new(Mutex::new(PointRenderer::new(cc.gl.clone(), 1_000_000))),
points: Vec::new(),
file_dialog_open: false,
cur_path: "./".to_string(),
pane_manager: PaneManager::new(Some(cc)),
})
}
}
impl Default for App {
fn default() -> Self {
Self {
pane_manager: PaneManager::new(None),
}
}
}
impl eframe::App for App {
/// Called each time the UI needs repainting, which may be many times per second.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`.
// For inspiration and more examples, go to https://emilk.github.io/egui
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
// NOTE: no File->Quit on web pages!
let is_web = cfg!(target_arch = "wasm32");
if !is_web {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.add_space(16.0);
}
egui::widgets::global_theme_preference_switch(ui);
if ui.button("Load PLY").clicked() {
self.file_dialog_open = true;
}
});
});
if self.file_dialog_open {
egui::Window::new("Load PLY File")
.show(ctx, |ui| {
ui.label("Enter PLY file path:");
ui.text_edit_singleline(&mut self.cur_path); // Add proper path handling
ui.horizontal(|ui| {
if ui.button("Load").clicked() {
let renderer = &mut self.renderer.lock();
// Add proper path handling and error reporting
let ply = renderer.load_ply(self.cur_path.clone());
if let Err(e) = ply {
eprintln!("Failed to load PLY: {}", e);
}else{
// self.renderer.lock().camera.reset();
self.points = ply.unwrap();
}
self.file_dialog_open = false;
}
if ui.button("Cancel").clicked() {
self.file_dialog_open = false;
}
});
});
}
egui::CentralPanel::default().show(ctx, |ui| {
self.pane_manager.render(ui);
// egui::scroll_area::ScrollArea::vertical().show(ui, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui.max_rect(), ui);
});
// egui::Frame::canvas(ui.style()).show(ui, |ui| {
// self.custom_painting(ui.max_rect(), ui);
// });
// })
});
}
// fn on_exit(&mut self, gl: Option<&glow::Context>) {
@@ -116,108 +68,4 @@ impl eframe::App for App {
// fn save(&mut self, storage: &mut dyn eframe::Storage) {
// eframe::set_value(storage, eframe::APP_KEY, self);
// }
}
impl App {
fn custom_painting(&mut self, max_rect: egui::Rect, ui: &mut egui::Ui) {
let start_time = Instant::now();
let (rect, response) =
ui.allocate_exact_size(egui::Vec2 { x: max_rect.width(), y: max_rect.height() }, egui::Sense::drag());
let input_state = ui.input(|input_state| {input_state.clone()});
// ui.painter();.
// println!("{}",response.drag_motion().x);
// let response = Box::new(response);
// let ui = ui.to_owned();
// self.anglex += response.drag_motion().x * 0.01;
// self.angley += response.drag_motion().y * 0.01;
// Clone locals so we can move them into the paint callback:
if self.points.is_empty() {
let radius = 1000i32;
for i in 0..100000 {
// let theta = (i as f32 * 0.1).sin() * std::f32::consts::PI;
// let phi = (i as f32 * 0.1).cos() * std::f32::consts::PI;
let x = (radius as f32 * (i as f32).cos()) as i32;
let y = (radius as f32 * (i as f32).sin()) as i32;
let z = (i as f32 * 0.05) as i32;
// let x = (i as f32 * 0.1) as u32;
// let y = (i as f32 * 0.1) as u32 ;
// let z = (i as f32 * 0.1) as u32;
// Color based on position
let color = Color32::from_rgba_premultiplied(
((x as f32 / radius as f32) * 255.0) as u8,
((y as f32 / radius as f32) * 255.0) as u8,
((z as f32 / radius as f32) * 255.0) as u8,
255,
);
self.points.push((x, y, z, color));
}
}
let renderer = self.renderer.clone();
renderer.lock().clear();
// let painter = ui.painter();
for &(x, y, z, color) in &self.points {
renderer.lock().add_point(x, y, z, color);
}
let o = renderer.lock().camera.orientation.clone();
let cb = egui_glow::CallbackFn::new(move |_info, _painter| {
renderer.lock().render(rect, input_state.clone());
});
let callback = egui::PaintCallback {
rect,
callback: Arc::new(cb),
};
ui.painter().add(callback);
let pos1 = o.inverse()*glam::Vec3::X;
let pos2 = o.inverse()*glam::Vec3::Y;
let pos3 = o.inverse()*glam::Vec3::Z;
let line_length:f32 = 20.;
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos1.x, y: -line_length*pos1.y,}], Stroke {
width: 1.5,
color: Color32::RED,
});
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos2.x, y: -line_length*pos2.y,}], Stroke {
width: 1.5,
color: Color32::BLUE,
});
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos3.x, y: -line_length*pos3.y,}], Stroke {
width: 1.5,
color: Color32::GREEN,
});
let end_time = Instant::now();
println!("{}", end_time.duration_since(start_time).as_millis());
ui.painter().text(Pos2 {x:0.,y:0.}, Align2::LEFT_TOP, format!("{}",end_time.duration_since(start_time).as_millis()), FontId::monospace(12.), Color32::WHITE);
}
}
+41 -41
View File
@@ -2,7 +2,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use rushroom::App;
use rushroom::PaneManager;
// use rushroom::PaneManager;
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
@@ -30,49 +30,49 @@ fn main() -> eframe::Result {
)
}
// When compiling to web using trunk:
#[cfg(target_arch = "wasm32")]
fn main() {
use eframe::wasm_bindgen::JsCast as _;
// // When compiling to web using trunk:
// #[cfg(target_arch = "wasm32")]
// fn main() {
// use eframe::wasm_bindgen::JsCast as _;
// Redirect `log` message to `console.log` and friends:
eframe::WebLogger::init(log::LevelFilter::Debug).ok();
// // Redirect `log` message to `console.log` and friends:
// eframe::WebLogger::init(log::LevelFilter::Debug).ok();
let web_options = eframe::WebOptions::default();
// let web_options = eframe::WebOptions::default();
wasm_bindgen_futures::spawn_local(async {
let document = web_sys::window()
.expect("No window")
.document()
.expect("No document");
// wasm_bindgen_futures::spawn_local(async {
// let document = web_sys::window()
// .expect("No window")
// .document()
// .expect("No document");
let canvas = document
.get_element_by_id("the_canvas_id")
.expect("Failed to find the_canvas_id")
.dyn_into::<web_sys::HtmlCanvasElement>()
.expect("the_canvas_id was not a HtmlCanvasElement");
// let canvas = document
// .get_element_by_id("the_canvas_id")
// .expect("Failed to find the_canvas_id")
// .dyn_into::<web_sys::HtmlCanvasElement>()
// .expect("the_canvas_id was not a HtmlCanvasElement");
let start_result = eframe::WebRunner::new()
.start(
canvas,
web_options,
Box::new(|cc| Ok(Box::new(App::new(cc)))),
)
.await;
// let start_result = eframe::WebRunner::new()
// .start(
// canvas,
// web_options,
// Box::new(|cc| Ok(Box::new(App::new(cc)))),
// )
// .await;
// Remove the loading text and spinner:
if let Some(loading_text) = document.get_element_by_id("loading_text") {
match start_result {
Ok(_) => {
loading_text.remove();
}
Err(e) => {
loading_text.set_inner_html(
"<p> The app has crashed. See the developer console for details. </p>",
);
panic!("Failed to start eframe: {e:?}");
}
}
}
});
}
// // Remove the loading text and spinner:
// if let Some(loading_text) = document.get_element_by_id("loading_text") {
// match start_result {
// Ok(_) => {
// loading_text.remove();
// }
// Err(e) => {
// loading_text.set_inner_html(
// "<p> The app has crashed. See the developer console for details. </p>",
// );
// panic!("Failed to start eframe: {e:?}");
// }
// }
// }
// });
// }
+633 -409
View File
File diff suppressed because it is too large Load Diff
+189 -22
View File
@@ -1,11 +1,19 @@
use egui::{vec2, Color32, InputState, Rect, Response};
use egui::{Color32, InputState, Rect};
use eframe::egui_glow;
use egui_glow::glow;
use std::sync::Arc;
use glam::{Vec3, Mat4, Quat, Vec2};
use glam::{Vec3, Mat4, Quat};
use std::fs::File;
use std::path::Path;
// use std::path::Path;
use std::io::{BufReader, BufRead};
use crate::panes::{Pane, PaneMode, PaneState};
use std::sync::Mutex;
use egui::FontId;
use egui::Align2;
// use egui::Pos2;
use std::time::Instant;
use egui::Stroke;
use egui::Ui;
// Shader sources updated for 3D rendering with fixed-point positions
const VERTEX_SHADER: &str = r#"
@@ -66,13 +74,13 @@ impl Camera {
}
}
pub fn reset(&mut self) {
self.position = Vec3::new(0.0, 0.0, 5.0);
self.orientation = Quat::IDENTITY;
self.distance = 5.0;
// self.point_size_scale = 0.1;
self.update_view();
}
// pub fn reset(&mut self) {
// self.position = Vec3::new(0.0, 0.0, 5.0);
// self.orientation = Quat::IDENTITY;
// self.distance = 5.0;
// // self.point_size_scale = 0.1;
// self.update_view();
// }
pub fn update(&mut self, i: InputState) {
// let response = {
@@ -112,7 +120,7 @@ impl Camera {
}
// Middle mouse button for camera-plane panning
if i.pointer.middle_down() {
if i.pointer.primary_down() {
let delta = i.pointer.delta();
let pan_speed = self.distance * 0.001;
@@ -165,9 +173,9 @@ impl Camera {
)
}
pub fn set_point_size_scale(&mut self, scale: f32) {
self.point_size_scale = scale.clamp(0.1, 10.0);
}
// pub fn set_point_size_scale(&mut self, scale: f32) {
// self.point_size_scale = scale.clamp(0.1, 10.0);
// }
}
@@ -192,7 +200,7 @@ pub struct PointRenderer {
vao: glow::VertexArray,
vbo: glow::Buffer,
points: Vec<i32>,
capacity: usize,
// capacity: usize,
pub camera: Camera,
}
@@ -256,7 +264,7 @@ impl PointRenderer {
vao,
vbo,
points: Vec::with_capacity(initial_capacity * 7),
capacity: initial_capacity,
// capacity: initial_capacity,
camera: Camera::new(),
}
}
@@ -270,11 +278,13 @@ impl PointRenderer {
self.points.clear();
}
pub fn render(&mut self, rect: Rect, input_state: InputState) {
pub fn render(&mut self, rect: Rect, input_state: Option<InputState>) {
use glow::HasContext;
// Update camera
self.camera.update(input_state);
if let Some(i) = input_state{
self.camera.update(i);
}
unsafe {
self.gl.use_program(Some(self.program));
@@ -336,7 +346,7 @@ impl PointRenderer {
// Add method to load points from PLY file
pub fn load_ply(&mut self, path: String) -> Result<(Vec<(i32, i32, i32, Color32)>), String> {
pub fn load_ply(&mut self, path: String) -> Result<Vec<(i32, i32, i32, Color32)>, String> {
let file = File::open(path).map_err(|e| format!("Failed to open file: {}", e))?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
@@ -363,7 +373,7 @@ impl PointRenderer {
let mut vertex_count = 0;
let mut has_colors = false;
let mut is_binary = false;
let mut in_header = true;
let in_header = true;
while in_header {
let line = lines.next()
@@ -400,7 +410,7 @@ impl PointRenderer {
&mut self,
lines: std::io::Lines<B>,
header: PlyHeader,
) -> Result<(Vec<(i32, i32, i32, Color32)>), String> {
) -> Result<Vec<(i32, i32, i32, Color32)>, String> {
let mut vec: Vec<(i32, i32, i32, Color32)> = Vec::new();
for line in lines.take(header.vertex_count) {
@@ -436,7 +446,7 @@ impl PointRenderer {
// self.add_point(x, y, z, color);
}
Ok((vec))
Ok(vec)
}
}
@@ -445,4 +455,161 @@ impl Drop for PointRenderer {
fn drop(&mut self) {
// Clean up GPU resources
}
}
pub struct PointRendererPane {
renderer: Arc<Mutex<PointRenderer>>,
points: Vec<(i32, i32, i32, Color32)>,
file_dialog_open: bool,
cur_path: String,
}
impl Pane for PointRendererPane {
fn new(cc: &eframe::CreationContext<'_>) -> PaneState where Self: Sized {
let mut s = Self {
renderer: Arc::new(Mutex::new(PointRenderer::new(cc.gl.clone(), 1_000_000))),
points: Vec::new(),
file_dialog_open: false,
cur_path: "./".to_string(),
};
PaneState {
id: s.name().to_string(),
mode: PaneMode::Center,
pane: Box::new(s),
}
}
fn name(&mut self) -> &str {"Point Cloud"}
fn render(&mut self, ui: &mut Ui){
let max_rect = ui.max_rect();
let renderer = self.renderer.clone();
renderer.lock().expect("Renderer Not Initialized").clear();
if self.file_dialog_open {
egui::Window::new("Load PLY File")
.show(ui.ctx(), |ui| {
ui.label("Enter PLY file path:");
ui.text_edit_singleline(&mut self.cur_path); // Add proper path handling
ui.horizontal(|ui| {
if ui.button("Load").clicked() {
let renderer = &mut renderer.lock().expect("Renderer Not Initialized");
// Add proper path handling and error reporting
let ply = renderer.load_ply(self.cur_path.clone());
if let Err(e) = ply {
eprintln!("Failed to load PLY: {}", e);
}else{
// self.renderer.lock().camera.reset();
self.points = ply.unwrap();
}
self.file_dialog_open = false;
}
if ui.button("Cancel").clicked() {
self.file_dialog_open = false;
}
});
});
}
let start_time = Instant::now();
let (rect, response) =
ui.allocate_exact_size(egui::Vec2 { x: max_rect.width(), y: max_rect.height() }, egui::Sense::drag());
let input_state: Option<InputState> = ui.input(|input_state|
if response.hovered() { //&& response.has_focus() {
Some(input_state.clone())
}else{None}
);
if self.points.is_empty() {
let radius = 1000i32;
for i in 0..100000 {
// let theta = (i as f32 * 0.1).sin() * std::f32::consts::PI;
// let phi = (i as f32 * 0.1).cos() * std::f32::consts::PI;
let x = (radius as f32 * (i as f32).cos()) as i32;
let y = (radius as f32 * (i as f32).sin()) as i32;
let z = (i as f32 * 0.05) as i32;
// let x = (i as f32 * 0.1) as u32;
// let y = (i as f32 * 0.1) as u32 ;
// let z = (i as f32 * 0.1) as u32;
// Color based on position
let color = Color32::from_rgba_premultiplied(
((x as f32 / radius as f32) * 255.0) as u8,
((y as f32 / radius as f32) * 255.0) as u8,
((z as f32 / radius as f32) * 255.0) as u8,
255,
);
self.points.push((x, y, z, color));
}
}
// let painter = ui.painter();
for &(x, y, z, color) in &self.points {
renderer.lock().expect("Renderer Not Initialized").add_point(x, y, z, color);
}
let o = renderer.lock().expect("Renderer Not Initialized").camera.orientation.clone();
let cb = egui_glow::CallbackFn::new(move |_info, _painter| {
renderer.lock().expect("Renderer Not Initialized").render(max_rect, input_state.clone());
});
let callback = egui::PaintCallback {
rect: max_rect,
callback: Arc::new(cb),
};
ui.painter().add(callback);
let pos1 = o.inverse()*glam::Vec3::X;
let pos2 = o.inverse()*glam::Vec3::Y;
let pos3 = o.inverse()*glam::Vec3::Z;
let line_length:f32 = 20.;
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos1.x, y: -line_length*pos1.y,}], Stroke {
width: 1.5,
color: Color32::RED,
});
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos2.x, y: -line_length*pos2.y,}], Stroke {
width: 1.5,
color: Color32::BLUE,
});
ui.painter().line_segment([rect.center(), rect.center() + egui::Vec2{ x: line_length*pos3.x, y: -line_length*pos3.y,}], Stroke {
width: 1.5,
color: Color32::GREEN,
});
let end_time = Instant::now();
// println!("{}", end_time.duration_since(start_time).as_millis());
let text_size = 12.;
ui.painter().text(max_rect.min, Align2::LEFT_TOP,
format!("{} ms",end_time.duration_since(start_time).as_millis()),
FontId::monospace(text_size), Color32::WHITE);
ui.painter().text(max_rect.min + egui::Vec2 {x:0.,y:text_size}, Align2::LEFT_TOP,
format!("{} points", self.points.len()),
FontId::monospace(text_size), Color32::WHITE);
}
fn context_menu(&mut self, ui: &mut Ui) {
if ui.button("Load PLY").clicked() {
self.file_dialog_open = true;
}
}
}