gloss_renderer/gui.rs
1use crate::{
2 components::{
3 Colors, DiffuseImg, DiffuseTex, Faces, ImgConfig, LightEmit, MeshColorType, ModelMatrix, Name, NormalImg, NormalTex, Normals, PointColorType,
4 PosLookat, Projection, Renderable, RoughnessImg, RoughnessTex, ShadowCaster, ShadowMapDirty, UVs, Verts, VisLines, VisMesh, VisNormals,
5 VisOutline, VisPoints, VisWireframe,
6 },
7 config::Config,
8 selector::Selector,
9 viewer::Runner,
10};
11
12use crate::plugin_manager::gui::window::{GuiWindowType, WindowPivot, WindowPositionType};
13
14use crate::{
15 builders,
16 forward_renderer::Renderer,
17 plugin_manager::plugins::Plugins,
18 scene::{Scene, GLOSS_FLOOR_NAME},
19};
20
21use egui::style::TextCursorStyle;
22use gloss_geometry::geom;
23use gloss_utils::string::float2string;
24use log::debug;
25
26use easy_wgpu::gpu::Gpu;
27use egui_wgpu::ScreenDescriptor;
28
29use egui::style::{HandleShape, NumericColorSpace, ScrollStyle};
30
31use gloss_utils::{
32 abi_stable_aliases::std_types::{ROption::RSome, RString, RVec},
33 memory::get_last_relevant_func_name,
34 tensor::DynamicMatrixOps,
35};
36
37use re_memory::{accounting_allocator, CallstackStatistics, MemoryUse};
38
39use log::error;
40use winit::window::Window;
41
42use crate::plugin_manager::gui::widgets::Widgets as WidgetsFFI;
43use egui::{
44 epaint,
45 epaint::Shadow,
46 scroll_area,
47 style::{Interaction, Selection, Spacing, WidgetVisuals, Widgets},
48 Align, Align2, Color32, FontId, Layout, RichText, Rounding, ScrollArea, Slider, Stroke, Style, Ui, Vec2, Visuals,
49};
50use egui_winit::{self, EventResponse};
51use epaint::Margin;
52
53use gloss_hecs::{CommandBuffer, Entity};
54
55use std::{collections::HashMap, path::Path};
56
57use nalgebra as na;
58
59// check the integration example here: https://docs.rs/egui/latest/egui/
60// info of other people trying to do custom stuff: https://users.rust-lang.org/t/egui-is-it-possible-to-avoid-using-eframe/70470/22
61// some other example of a large codebase using egui: https://github.com/parasyte/cartunes
62// example of egui-winit and egui-wgpu: https://github.com/hasenbanck/egui_example/blob/master/src/main.rs
63// official example of egui-wgpu: https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs
64
65// integration
66const SIDE_PANEL_WIDTH: f32 = 250.0;
67const SPACING_1: f32 = 10.0;
68
69/// Separate the egui ctx from the rest of the gui because borrow checker
70/// complains when we modify state mutably of the gui and also have immutable
71/// reference to `egui_ctx`. having a mutable widget the deal only with state
72/// solved this
73pub struct GuiMainWidget {
74 //contains the gui state
75 pub selected_mesh_name: String,
76 pub selected_entity: Option<Entity>,
77 pub selected_light_name: String,
78 pub selected_light_entity: Option<Entity>,
79 pub wgputex_2_eguitex: HashMap<wgpu::Id<wgpu::Texture>, epaint::TextureId>,
80 pub hovered_diffuse_tex: bool,
81 pub hovered_normal_tex: bool,
82 pub hovered_roughness_tex: bool,
83 default_texture: Option<easy_wgpu::texture::Texture>,
84 //gizmo stuff
85 // gizmo_mode: GizmoMode,
86 // gizmo_orientation: GizmoOrientation,
87}
88impl Default for GuiMainWidget {
89 #[allow(clippy::derivable_impls)]
90 fn default() -> GuiMainWidget {
91 GuiMainWidget {
92 selected_mesh_name: String::new(),
93 selected_light_name: String::new(),
94 selected_entity: None,
95 selected_light_entity: None,
96 wgputex_2_eguitex: HashMap::new(),
97 hovered_diffuse_tex: false,
98 hovered_normal_tex: false,
99 hovered_roughness_tex: false,
100 default_texture: None,
101 // gizmo_mode: GizmoMode::Translate,
102 // gizmo_orientation: GizmoOrientation::Local,
103 }
104 }
105}
106impl GuiMainWidget {
107 pub fn new(gpu: &Gpu) -> Self {
108 let path_tex = concat!(env!("CARGO_MANIFEST_DIR"), "/../../data/uv_checker.png");
109 debug!("path_tex {path_tex}");
110 let default_texture = easy_wgpu::texture::Texture::create_default_texture(gpu.device(), gpu.queue());
111
112 Self {
113 default_texture: Some(default_texture),
114 ..Default::default()
115 }
116 }
117}
118
119type CbFnType = fn(&mut GuiMainWidget, ctx: &egui::Context, ui: &mut Ui, renderer: &Renderer, scene: &mut Scene);
120pub struct Gui {
121 egui_ctx: egui::Context, //we do all the gui rendering inside this context
122 pub egui_state: egui_winit::State, //integrator with winit https://github.com/emilk/egui/blob/master/crates/egui-winit/src/lib.rs#L55
123 //similar to voiding on github
124 egui_renderer: egui_wgpu::Renderer,
125 width: u32,
126 height: u32,
127 pub hidden: bool,
128 gui_main_widget: GuiMainWidget,
129 command_buffer: CommandBuffer, //defer insertions and deletion of scene entities for whenever we apply this command buffer
130 //callbacks
131 //https://users.rust-lang.org/t/callback-with-generic/52426/5
132 //https://stackoverflow.com/questions/66832392/sending-method-as-callback-function-to-field-object-in-rust
133 //https://stackoverflow.com/questions/41081240/idiomatic-callbacks-in-rust
134 //https://www.reddit.com/r/rust/comments/gi2pld/callback_functions_the_right_way/
135 //https://github.com/rhaiscript/rhai/issues/178
136 //https://www.reddit.com/r/rust/comments/ymingb/what_is_the_idiomatic_approach_to_eventscallbacks/iv5pgz9/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
137
138 //callback function that can add gui elements either inside the sidebar or outside the sidebar
139 callbacks: Vec<CbFnType>,
140 callbacks_for_selected_mesh: Vec<CbFnType>,
141}
142
143impl Gui {
144 pub fn new(window: &winit::window::Window, gpu: &Gpu, surface_format: wgpu::TextureFormat) -> Self {
145 #[allow(clippy::cast_possible_truncation)] //it's ok, we don't have very big numbers
146 let native_pixels_per_point = window.scale_factor() as f32;
147
148 let egui_renderer = egui_wgpu::Renderer::new(gpu.device(), surface_format, None, 1, false);
149
150 let egui_ctx = egui::Context::default();
151 let egui_state = egui_winit::State::new(
152 egui_ctx.clone(), //just a shallow clone since it's behind an Arc
153 egui::ViewportId::default(),
154 window,
155 Some(native_pixels_per_point),
156 None,
157 Some(2048), //TODO maybe find the concrete value, for now we leave 2048 because wasm
158 ); //state that gets all the events from the window and gather them
159 //https://github.com/emilk/egui/blob/bdc8795b0476c25faab927fc3c731f2d79f2098f/crates/eframe/src/native/epi_integration.rs#L361
160
161 //size of the gui window. Will get resized automatically
162 let width = 100;
163 let height = 100;
164
165 //Gui state based on what the user does
166 let gui_main_widget = GuiMainWidget::new(gpu);
167
168 // Mutate global style with above changes
169 #[allow(unused_mut)]
170 let mut style = style();
171
172 //on web the view only renders when there is a mouse being dragged or when
173 // there is an input so animations are choppy if you don't move the mouse.
174 // Therefore we disable them
175 cfg_if::cfg_if! {
176 if #[cfg(target_arch = "wasm32")] {
177 style.animation_time = 0.0;
178 }
179 }
180 egui_ctx.set_style(style);
181
182 let command_buffer = CommandBuffer::new();
183
184 Self {
185 egui_ctx,
186 egui_state,
187 egui_renderer,
188 width,
189 height,
190 hidden: false,
191 gui_main_widget,
192 command_buffer,
193 callbacks: Vec::new(),
194 callbacks_for_selected_mesh: Vec::new(),
195 }
196 }
197
198 pub fn resize(&mut self, width: u32, height: u32) {
199 self.width = width;
200 self.height = height;
201 }
202
203 //TODO rename to "process gui event" for coherency with the other event
204 // processing things
205 pub fn on_event(&mut self, window: &Window, event: &winit::event::WindowEvent) -> EventResponse {
206 self.egui_state.on_window_event(window, event)
207 }
208
209 /// # Panics
210 /// Will panic is the path is not valid unicode
211 pub fn on_drop(&mut self, path_buf: &Path, scene: &mut Scene) {
212 self.gui_main_widget.set_default_selected_entity(scene);
213
214 let path = path_buf.to_str().unwrap();
215 let entity = self.gui_main_widget.selected_entity.unwrap();
216 if self.gui_main_widget.hovered_diffuse_tex {
217 scene
218 .world
219 .insert_one(entity, DiffuseImg::new_from_path(path, &ImgConfig::default()))
220 .ok();
221 }
222 if self.gui_main_widget.hovered_normal_tex {
223 scene.world.insert_one(entity, NormalImg::new_from_path(path, &ImgConfig::default())).ok();
224 }
225 if self.gui_main_widget.hovered_roughness_tex {
226 scene
227 .world
228 .insert_one(entity, RoughnessImg::new_from_path(path, &ImgConfig::default()))
229 .ok();
230 }
231 }
232
233 pub fn wants_pointer_input(&self) -> bool {
234 //tryng to solve https://github.com/urholaukkarinen/egui-gizmo/issues/19
235 self.egui_ctx.wants_pointer_input()
236 }
237
238 pub fn is_hovering(&self) -> bool {
239 self.egui_ctx.is_pointer_over_area()
240 }
241
242 //inspiration from voidin renderer on github
243 //https://github.com/pudnax/voidin/blob/91e6b564008879388f3777bcb6154c656bfc533c/crates/app/src/app.rs#L643
244 #[allow(clippy::too_many_arguments)]
245 pub fn render(
246 &mut self,
247 window: &winit::window::Window,
248 gpu: &Gpu,
249 renderer: &Renderer,
250 runner: &Runner,
251 scene: &mut Scene,
252 plugins: &Plugins,
253 config: &mut Config,
254 out_view: &wgpu::TextureView,
255 ) {
256 if self.hidden {
257 return;
258 }
259 self.begin_frame();
260
261 let screen_descriptor = ScreenDescriptor {
262 size_in_pixels: [self.width, self.height],
263 pixels_per_point: self.egui_ctx.pixels_per_point(),
264 };
265
266 let full_output = self.egui_ctx.run(self.egui_state.take_egui_input(window), |ctx: &egui::Context| {
267 // ui_builder(ctx) // THIS ACTUALLY RENDERS GUI
268 self.gui_main_widget.build_gui(
269 self.width,
270 self.height,
271 ctx,
272 renderer,
273 &mut self.egui_renderer,
274 gpu,
275 scene,
276 config,
277 runner,
278 &mut self.command_buffer,
279 &mut self.callbacks,
280 &mut self.callbacks_for_selected_mesh,
281 plugins,
282 );
283 });
284 let paint_jobs = self.egui_ctx.tessellate(full_output.shapes, self.egui_ctx.pixels_per_point());
285 let textures_delta = full_output.textures_delta;
286
287 let mut encoder = gpu
288 .device()
289 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Gui") });
290 {
291 for (texture_id, image_delta) in &textures_delta.set {
292 self.egui_renderer.update_texture(gpu.device(), gpu.queue(), *texture_id, image_delta);
293 }
294 for texture_id in &textures_delta.free {
295 self.egui_renderer.free_texture(texture_id);
296 }
297 self.egui_renderer
298 .update_buffers(gpu.device(), gpu.queue(), &mut encoder, &paint_jobs, &screen_descriptor);
299
300 let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
301 label: Some("UI Pass"),
302 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
303 view: out_view,
304 resolve_target: None,
305 ops: wgpu::Operations {
306 load: wgpu::LoadOp::Load,
307 store: wgpu::StoreOp::Store,
308 },
309 })],
310 depth_stencil_attachment: None,
311 timestamp_writes: None,
312 occlusion_query_set: None,
313 });
314 self.egui_renderer
315 .render(&mut render_pass.forget_lifetime(), paint_jobs.as_slice(), &screen_descriptor);
316 }
317 gpu.queue().submit(Some(encoder.finish()));
318 self.end_frame(scene);
319 }
320
321 fn begin_frame(&self) {}
322
323 fn end_frame(&mut self, scene: &mut Scene) {
324 self.command_buffer.run_on(&mut scene.world);
325 }
326
327 pub fn add_callback(
328 &mut self,
329 f: fn(&mut GuiMainWidget, ctx: &egui::Context, ui: &mut Ui, renderer: &Renderer, scene: &mut Scene),
330 draw_in_global_panel: bool,
331 ) {
332 if draw_in_global_panel {
333 self.callbacks.push(f);
334 } else {
335 self.callbacks_for_selected_mesh.push(f);
336 }
337 }
338}
339
340impl GuiMainWidget {
341 #[allow(clippy::too_many_arguments)]
342 #[allow(clippy::too_many_lines)]
343 fn build_gui(
344 &mut self,
345 screen_width: u32,
346 screen_height: u32,
347 ctx: &egui::Context,
348 renderer: &Renderer,
349 egui_renderer: &mut egui_wgpu::Renderer,
350 gpu: &Gpu,
351 scene: &mut Scene,
352 config: &mut Config,
353 runner: &Runner,
354 command_buffer: &mut CommandBuffer,
355 callbacks: &mut [CbFnType],
356 callbacks_for_selected_mesh: &mut [CbFnType],
357 plugins: &Plugins,
358 ) {
359 self.set_default_selected_entity(scene);
360
361 //draw point indices for the selected entity
362 if let Some(ent) = self.selected_entity {
363 if let Ok(mut c) = scene.get_comp::<&mut VisPoints>(&ent) {
364 self.draw_verts_indices(ctx, scene, screen_width, screen_height, &mut c);
365 }
366 }
367
368 egui::SidePanel::left("my_left_panel").default_width(SIDE_PANEL_WIDTH).show(ctx, |ui| {
369 egui::ScrollArea::vertical().show(ui, |ui| {
370 // Scene
371 // We use this most if not all of the time, might as well leave it open by default
372 egui::CollapsingHeader::new("Scene").default_open(true).show(ui, |ui| {
373 ui.group(|ui| {
374 ScrollArea::vertical()
375 .max_height(200.0)
376 .scroll_bar_visibility(scroll_area::ScrollBarVisibility::AlwaysVisible)
377 .auto_shrink([false, false])
378 .show(ui, |ui| {
379 ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
380 ui.spacing_mut().item_spacing.y = 0.0;
381 ui.spacing_mut().button_padding.y = 4.0;
382
383 //get all entities that are renderable and sort by name
384 let entities = scene.get_renderables(true);
385 /* ---------------------------------- NOTE ---------------------------------- */
386 // We have 2 ways to select an entity - click2select and through this GUI
387 // Both of them should work together and using one should update the state of the other.
388 // Both selection methods are managed by the Selector resource.
389 // Since the click2select supports the idea of deselection, so should the GUI.
390 /* -------------------------------------------------------------------------- */
391
392 // The selector may have been set by click2select, respect that selection instead of starting afresh
393 if let Ok(selector) = scene.get_resource::<&mut Selector>() {
394 let name = selector.current_selected.clone();
395 if self.selected_mesh_name != name.clone() {
396 // println!("Updated Selected entity: {name}");
397 let entity = scene.get_entity_with_name(&name);
398 self.selected_entity = entity;
399 }
400 self.selected_mesh_name = name;
401 } else {
402 // If we dont have a selector resource, leave the selection GUI in a deselected state
403 self.selected_mesh_name = String::new();
404 self.selected_entity = None;
405 }
406
407 // Manage selection via the GUI
408 // Go through all visible meshes and show their names as selectable options
409 for entity in entities {
410 let e_ref = scene.world.entity(entity).unwrap();
411 // get the name of the mesh which acts like a unique id
412 let name = e_ref.get::<&Name>().expect("The entity has no name").0.clone();
413 // GUI for this concrete mesh
414 // if we click we can see options for vis
415 let _res = ui.selectable_value(&mut self.selected_mesh_name, name.clone(), &name);
416
417 if name == self.selected_mesh_name {
418 // First, turn off outline for previous selection
419 if let Some(prev_entity) = self.selected_entity {
420 if let Ok(mut vis_outline) = scene.world.get::<&mut VisOutline>(prev_entity) {
421 vis_outline.show_outline = false;
422 }
423 }
424
425 // Set new selection
426 self.selected_entity = Some(entity);
427
428 // Set selector and update outline for new selection
429 let selector = Selector {
430 current_selected: name.clone(),
431 };
432 scene.add_resource(selector);
433 if let Ok(mut vis_outline) = scene.world.get::<&mut VisOutline>(entity) {
434 vis_outline.show_outline = true;
435 }
436 //make a side window
437 self.draw_vis(ctx, renderer, scene, entity, command_buffer, callbacks_for_selected_mesh);
438 }
439 }
440 });
441 });
442 });
443 });
444
445 // Resources in the scene (global components)
446 egui::CollapsingHeader::new("Resources").show(ui, |ui| {
447 self.draw_comps(ui, scene, scene.get_entity_resource(), command_buffer);
448 });
449
450 // Params
451 egui::CollapsingHeader::new("Textures").show(ui, |ui| {
452 self.draw_textures(ui, scene, egui_renderer, gpu, command_buffer);
453 });
454
455 //Move
456 // egui::CollapsingHeader::new("Move")
457 // .show(ui, |ui| self.draw_move(ui, scene, command_buffer));
458
459 // Params
460 egui::CollapsingHeader::new("Params").show(ui, |ui| {
461 self.draw_params(ui, scene, config, command_buffer);
462 });
463
464 // Lights
465 egui::CollapsingHeader::new("Lights").show(ui, |ui| self.draw_lights(ui, scene, command_buffer));
466
467 // Cam
468 egui::CollapsingHeader::new("Camera").show(ui, |ui| self.draw_cam(ui, scene, command_buffer));
469
470 // Io
471 egui::CollapsingHeader::new("Io").show(ui, |ui| {
472 self.draw_io(ui, scene, command_buffer, self.selected_entity);
473 });
474
475 // profiling
476 egui::CollapsingHeader::new("Profiling").show(ui, |ui| self.draw_profiling(ui, scene, command_buffer));
477
478 // Plugins
479 egui::CollapsingHeader::new("Plugins").show(ui, |ui| {
480 self.draw_plugins(ui, scene, plugins, command_buffer);
481 });
482
483 //fps
484 ui.separator();
485 let dt = runner.dt();
486 let fps = 1.0 / dt.as_secs_f32();
487 let ms = dt.as_millis();
488 let fps_string = format!("{fps:.0}");
489 let ms_string = format!("{ms:.2}");
490 ui.label(egui::RichText::new("FPS: ".to_owned() + &fps_string));
491 ui.label(egui::RichText::new("dt(ms): ".to_owned() + &ms_string));
492
493 // A `scope` creates a temporary [`Ui`] in which you can change settings:
494 // TODO: Change wrap mode?
495 ui.scope(|ui| {
496 ui.visuals_mut().override_text_color = Some(egui::Color32::RED);
497 ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
498 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
499 });
500
501 ui.add(egui::Separator::default());
502 for f in callbacks.iter() {
503 f(self, ctx, ui, renderer, scene);
504 }
505
506 for system_and_metadata in plugins.gui_systems.iter() {
507 let sys = &system_and_metadata.0;
508 let func = sys.f;
509 let gui_window = func(&self.selected_entity.into(), scene);
510 let window_name = gui_window.window_name;
511 let widgets = gui_window.widgets;
512 let window_type = gui_window.window_type;
513 if widgets.is_empty() {
514 continue; //there's no widgets so there's nothing to
515 // draw
516 }
517
518 //recursivelly draw all widgets
519 // https://stackoverflow.com/a/72862424
520 let mut draw_widgets = |ui: &mut Ui| {
521 //we make a helper so that we can call recursivelly
522 fn helper(ui: &mut Ui, widgets: &RVec<WidgetsFFI>, selected_entity: Entity, scene: &mut Scene) {
523 for widget in widgets.iter() {
524 match widget {
525 WidgetsFFI::Slider(slider) => {
526 let mut val = slider.init_val;
527 if let RSome(slider_width) = slider.width {
528 ui.spacing_mut().slider_width = slider_width;
529 //changes size od slider2
530 }
531 let res = ui.add(
532 Slider::new(&mut val, slider.min..=slider.max)
533 .fixed_decimals(3)
534 .text(slider.name.as_str()),
535 );
536 if res.dragged() {
537 (slider.f_change)(val, &slider.name, &selected_entity, scene);
538 }
539 // } else {
540 if res.drag_stopped() {
541 // Updated method here
542 if let RSome(func) = slider.f_no_change {
543 func(&slider.name.clone(), &selected_entity, scene);
544 }
545 }
546 // if res.drag_released() {
547 // if let RSome(func) =
548 // slider.f_no_change {
549 // func(slider.name.clone(),
550 // selected_entity, scene);
551 // }
552 // }
553 }
554 WidgetsFFI::Checkbox(checkbox) => {
555 let mut val = checkbox.init_val;
556 let res = ui.add(egui::Checkbox::new(&mut val, checkbox.name.as_str()));
557 if res.clicked() {
558 (checkbox.f_clicked)(val, &checkbox.name, &selected_entity, scene);
559 }
560 }
561 WidgetsFFI::Button(button) => {
562 if ui.add(egui::Button::new(button.name.as_str())).clicked() {
563 (button.f_clicked)(&button.name, &selected_entity, scene);
564 }
565 }
566 WidgetsFFI::SelectableList(selectable_list) => {
567 let mut draw_selectables = |ui: &mut Ui| {
568 for item in selectable_list.items.iter() {
569 if ui.add(egui::SelectableLabel::new(item.is_selected, item.name.to_string())).clicked() {
570 (item.f_clicked)(&item.name, &selected_entity, scene);
571 }
572 }
573 };
574
575 if selectable_list.is_horizontal {
576 ui.horizontal(draw_selectables);
577 } else {
578 draw_selectables(ui);
579 }
580 }
581 WidgetsFFI::Horizontal(widgets) => {
582 ui.horizontal(|ui| {
583 helper(ui, widgets, selected_entity, scene);
584 });
585 }
586 }
587 }
588 }
589 if let Some(selected_entity) = self.selected_entity {
590 //finally call the helper function so that we start the recursion
591 helper(ui, &widgets, selected_entity, scene);
592 } else {
593 // If we don't have a selected entity, we create a dummy entity and pass it to the helper function
594 // This is to make sure that the gui is still drawn even for a GuiSystem that doesn't need an entity (SceneAnimation in smpl-rs)
595 // GuiSystems that do need an entity will not run since the dummy entity has no components
596 let dummy_entity = scene.get_or_create_hidden_entity("DummyEntity").entity();
597 helper(ui, &widgets, dummy_entity, scene);
598 }
599 };
600
601 match window_type {
602 #[allow(clippy::cast_precision_loss)]
603 GuiWindowType::FloatWindow(pivot, position, position_type) => {
604 // egui::Window::new(window_name.to_string()).show(ctx, &mut draw_widgets);
605 let pos_x = (screen_width as f32 - SIDE_PANEL_WIDTH) * position.0[0];
606 let pos_y = (screen_height as f32) * position.0[1];
607 let pivot = match pivot {
608 WindowPivot::LeftBottom => Align2::LEFT_BOTTOM,
609 WindowPivot::LeftCenter => Align2::LEFT_CENTER,
610 WindowPivot::LeftTop => Align2::LEFT_TOP,
611 WindowPivot::CenterBottom => Align2::CENTER_BOTTOM,
612 WindowPivot::CenterCenter => Align2::CENTER_CENTER,
613 WindowPivot::CenterTop => Align2::CENTER_TOP,
614 WindowPivot::RightBottom => Align2::RIGHT_BOTTOM,
615 WindowPivot::RightCenter => Align2::RIGHT_CENTER,
616 WindowPivot::RightTop => Align2::RIGHT_TOP,
617 };
618
619 let mut win = egui::Window::new(window_name.to_string()).pivot(pivot);
620 match position_type {
621 WindowPositionType::Fixed => {
622 win = win.fixed_pos([pos_x, pos_y]);
623 }
624 WindowPositionType::Initial => {
625 win = win.default_pos([pos_x, pos_y]);
626 }
627 }
628 // win.show(ctx, &mut draw_widgets);
629 win.show(ctx, &mut draw_widgets);
630 }
631 GuiWindowType::Sidebar => {
632 egui::CollapsingHeader::new(window_name.to_string()).show(ui, &mut draw_widgets);
633 }
634 };
635 }
636 });
637 });
638 }
639
640 // if no selected mesh is set yet, any query with the name will fail.
641 // we can use this function to set it to some default value(usually the first
642 // mesh in my list)
643 fn set_default_selected_entity(&mut self, scene: &Scene) {
644 for e_ref in scene.world.iter() {
645 // let entity = e_ref.entity();
646 let is_renderable = e_ref.has::<Renderable>();
647 if is_renderable {
648 //get the name of the mesh which acts like a unique id
649 let name = e_ref.get::<&Name>().expect("The entity has no name").0.clone();
650
651 //if it's the first time we encounter a renderable mesh, we set the selected
652 // name to this one
653 if self.selected_mesh_name.is_empty() && name != GLOSS_FLOOR_NAME {
654 self.selected_mesh_name.clone_from(&name);
655 self.selected_entity = Some(e_ref.entity());
656 }
657 }
658 }
659 }
660
661 #[allow(clippy::too_many_arguments)]
662 fn draw_vis(
663 &mut self,
664 ctx: &egui::Context,
665 renderer: &Renderer,
666 scene: &mut Scene,
667 entity: Entity,
668 command_buffer: &mut CommandBuffer,
669 callbacks_for_selected_mesh: &mut [CbFnType],
670 ) {
671 let e_ref = scene.world.entity(entity).unwrap();
672 let has_vis_points = e_ref.has::<VisPoints>();
673 let has_vis_lines = e_ref.has::<VisLines>();
674 let has_vis_outline = e_ref.has::<VisOutline>();
675 let _has_vis_wireframe = e_ref.has::<VisWireframe>();
676 let has_vis_mesh = e_ref.has::<VisMesh>();
677 let _has_vis_normals = e_ref.has::<VisNormals>();
678 let mut _window = egui::Window::new("vis_points")
679 // .auto_sized()
680 .default_width(100.0)
681 // .min_height(600.0)
682 .resizable(false)
683 // .collapsible(true)
684 .title_bar(false)
685 .scroll([false, false])
686 .anchor(Align2::LEFT_TOP, Vec2::new(SIDE_PANEL_WIDTH + 12.0, 0.0))
687 .show(ctx, |ui| {
688 //dummy vis options that we use to draw invisible widgets
689 //we need this because when we don't have a component we still want to draw an
690 // empty space for it and we use this dummy widget to figure out how much space
691 // we need points
692 if has_vis_points {
693 ui.add_space(SPACING_1);
694 let mut c = scene.get_comp::<&mut VisPoints>(&entity).unwrap();
695 self.draw_vis_points(ui, scene, entity, command_buffer, has_vis_points, &mut c);
696 }
697 //mesh
698 if has_vis_mesh {
699 ui.add_space(SPACING_1);
700 // let mut c = scene.get_comp::<&mut VisMesh>(&entity);
701 self.draw_vis_mesh(ui, scene, entity, command_buffer, has_vis_mesh);
702 }
703 //lines
704 if has_vis_lines {
705 ui.add_space(SPACING_1);
706 let mut c = scene.get_comp::<&mut VisLines>(&entity).unwrap();
707 self.draw_vis_lines(ui, scene, entity, command_buffer, has_vis_lines, &mut c);
708 }
709 //outline
710 if has_vis_outline {
711 ui.add_space(SPACING_1);
712 let mut c = scene.get_comp::<&mut VisOutline>(&entity).unwrap();
713 self.draw_vis_outline(ui, scene, entity, command_buffer, has_vis_outline, &mut c);
714 }
715 // TODO: Keep this?
716 //wireframe
717 // if has_vis_wireframe {
718 // ui.add_space(SPACING_1);
719 // let mut c = scene.get_comp::<&mut VisWireframe>(&entity);
720 // self.draw_vis_wireframe(
721 // // ctx,
722 // ui,
723 // // renderer,
724 // scene,
725 // entity,
726 // command_buffer,
727 // has_vis_wireframe,
728 // &mut c,
729 // );
730 // }
731 //normals
732 // if has_vis_normals {
733 // ui.add_space(SPACING_1);
734 // let mut c = scene.get_comp::<&mut VisNormals>(&entity);
735 // self.draw_vis_normals(
736 // // ctx,
737 // ui,
738 // // renderer,
739 // scene,
740 // entity,
741 // command_buffer,
742 // has_vis_wireframe,
743 // &mut c,
744 // );
745 // }
746
747 ui.label("Components");
748 ui.separator();
749 self.draw_comps(ui, scene, entity, command_buffer);
750
751 for f in callbacks_for_selected_mesh.iter() {
752 f(self, ctx, ui, renderer, scene);
753 }
754 });
755 }
756
757 #[allow(clippy::cast_precision_loss)]
758 fn draw_verts_indices(&self, ctx: &egui::Context, scene: &Scene, screen_width: u32, screen_height: u32, c: &mut VisPoints) {
759 if !c.show_points_indices {
760 return;
761 }
762 //TODO remove all these unwraps
763 egui::Area::new(egui::Id::new("verts_indices"))
764 .interactable(false)
765 .anchor(Align2::LEFT_TOP, egui::vec2(0.0, 0.0))
766 .show(ctx, |ui| {
767 if let Some(ent) = self.selected_entity {
768 let verts = scene.get_comp::<&Verts>(&ent).unwrap();
769 let model_matrix = scene.get_comp::<&ModelMatrix>(&ent).unwrap();
770 let cam = scene.get_current_cam().unwrap();
771 let view = cam.view_matrix(scene);
772 let proj = cam.proj_matrix(scene);
773 for (idx, vert) in verts.0.to_dmatrix().row_iter().enumerate() {
774 let point_world = model_matrix.0 * na::Point3::from(vert.fixed_columns::<3>(0).transpose());
775 let point_screen = cam.project(
776 point_world,
777 view,
778 proj,
779 na::Vector2::<f32>::new(screen_width as f32, screen_height as f32),
780 );
781
782 let widget_max_size = egui::vec2(35.0, 35.0);
783 let widget_rect = egui::Rect::from_min_size(
784 egui::pos2(
785 point_screen.x / ctx.pixels_per_point() - widget_max_size.x / 2.0,
786 (screen_height as f32 - point_screen.y) / ctx.pixels_per_point(),
787 ),
788 widget_max_size,
789 );
790 ui.put(widget_rect, egui::Label::new(idx.to_string()));
791 }
792 };
793 });
794 }
795
796 fn draw_vis_points(&self, ui: &mut Ui, _scene: &Scene, entity: Entity, command_buffer: &mut CommandBuffer, is_visible: bool, c: &mut VisPoints) {
797 // VIS POINTS
798 ui.label("Points");
799 ui.separator();
800 ui.add_enabled_ui(is_visible, |ui| {
801 let res = ui.checkbox(&mut c.show_points, "Show points");
802 ui.checkbox(&mut c.show_points_indices, "Show point indices");
803 if res.clicked() {
804 command_buffer.insert_one(entity, ShadowMapDirty);
805 }
806 //all the other guis are disabled if we don't show points
807 if c.show_points {
808 //point_color
809 ui.horizontal(|ui| {
810 ui.color_edit_button_rgba_premultiplied(&mut c.point_color.data.0.as_mut_slice()[0]);
811 ui.label("Point color");
812 });
813 //point_size
814 ui.horizontal(|ui| {
815 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
816 ui.add(Slider::new(&mut c.point_size, 0.0..=30.0).text("Size"))
817 });
818 ui.checkbox(&mut c.is_point_size_in_world_space, "isSizeInWorld");
819 // zbuffer
820 ui.checkbox(&mut c.zbuffer, "Use Z-Buffer");
821 //colortype
822 egui::ComboBox::new(0, "Color") //the id has to be unique to other comboboxes
823 .selected_text(format!("{:?}", c.color_type))
824 .show_ui(ui, |ui| {
825 // ui.style_mut().wrap = Some(false);
826 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
827 ui.set_min_width(60.0);
828 ui.selectable_value(&mut c.color_type, PointColorType::Solid, "Solid");
829 ui.selectable_value(&mut c.color_type, PointColorType::PerVert, "PerVert");
830 });
831 }
832 });
833 }
834
835 fn draw_vis_mesh(&mut self, ui: &mut Ui, scene: &mut Scene, entity: Entity, command_buffer: &mut CommandBuffer, is_visible: bool) {
836 let mut c = scene.get_comp::<&mut VisMesh>(&entity).unwrap();
837
838 // VIS MESH
839 ui.label("Mesh");
840 ui.separator();
841
842 // Use `add_enabled_ui` to conditionally enable UI elements
843 ui.add_enabled_ui(is_visible, |ui| {
844 let res = ui.checkbox(&mut c.show_mesh, "Show mesh");
845 if res.clicked() {
846 command_buffer.insert_one(entity, ShadowMapDirty);
847 }
848
849 let _name = scene.get_comp::<&Name>(&entity).unwrap().0.clone();
850
851 // The following settings are only enabled if `show_mesh` is true
852 if c.show_mesh {
853 // Solid color editor
854 ui.horizontal(|ui| {
855 ui.color_edit_button_rgba_unmultiplied(&mut c.solid_color.data.0.as_mut_slice()[0]);
856 ui.label("Solid color");
857 });
858
859 // Metalness slider
860 ui.horizontal(|ui| {
861 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
862 ui.add(Slider::new(&mut c.metalness, 0.0..=1.0).text("Metal"));
863 });
864
865 // Roughness slider
866 ui.horizontal(|ui| {
867 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
868 ui.add(Slider::new(&mut c.perceptual_roughness, 0.0..=1.0).text("Rough"));
869 });
870
871 // Roughness black level slider
872 ui.horizontal(|ui| {
873 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
874 ui.add(Slider::new(&mut c.roughness_black_lvl, 0.0..=1.0).text("RoughBlackLvl"));
875 });
876
877 // Opacity slider
878 ui.horizontal(|ui| {
879 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
880 ui.add(Slider::new(&mut c.opacity, 0.0..=1.0).text("Opacity"));
881 });
882
883 // SSS checkbox
884 ui.checkbox(&mut c.needs_sss, "Needs SSS");
885
886 // Color type selection combo box
887 egui::ComboBox::new(1, "Color")
888 .selected_text(format!("{:?}", c.color_type))
889 .show_ui(ui, |ui| {
890 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap);
891 ui.set_min_width(60.0);
892 ui.selectable_value(&mut c.color_type, MeshColorType::Solid, "Solid");
893 ui.selectable_value(&mut c.color_type, MeshColorType::PerVert, "PerVert");
894 ui.selectable_value(&mut c.color_type, MeshColorType::Texture, "Texture");
895 ui.selectable_value(&mut c.color_type, MeshColorType::UV, "UV");
896 ui.selectable_value(&mut c.color_type, MeshColorType::Normal, "Normal");
897 ui.selectable_value(&mut c.color_type, MeshColorType::NormalViewCoords, "NormalViewCoords");
898 });
899
900 // UV scale slider
901 ui.horizontal(|ui| {
902 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
903 ui.add(Slider::new(&mut c.uv_scale, 0.0..=3000.0).text("UVScale"));
904 });
905 }
906 });
907 }
908 fn draw_vis_outline(
909 &mut self,
910 ui: &mut Ui,
911 _scene: &Scene,
912 _entity: Entity,
913 _command_buffer: &mut CommandBuffer,
914 is_visible: bool,
915 c: &mut VisOutline,
916 ) {
917 ui.label("Outline");
918 ui.separator();
919
920 // Use `add_enabled_ui` to conditionally enable UI elements
921 ui.add_enabled_ui(is_visible, |ui| {
922 // Do not expose this to the user, we will use this internally to toggle the outline
923 // let res = ui.checkbox(&mut c.show_outline, "Show outline");
924 // if res.clicked() {
925 // command_buffer.insert_one(entity, ShadowMapDirty);
926 // }
927
928 if c.show_outline {
929 // Outline color editor
930 ui.horizontal(|ui| {
931 ui.color_edit_button_rgba_unmultiplied(&mut c.outline_color.data.0.as_mut_slice()[0]);
932 ui.label("Outline color");
933 });
934
935 // Outline width slider
936 ui.horizontal(|ui| {
937 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; // Adjust slider width
938 ui.add(Slider::new(&mut c.outline_width, 0.0..=25.0).text("Width"));
939 });
940 }
941 });
942 }
943 fn draw_vis_lines(
944 &mut self,
945 ui: &mut Ui,
946 _scene: &Scene,
947 entity: Entity,
948 command_buffer: &mut CommandBuffer,
949 is_visible: bool,
950 c: &mut VisLines,
951 ) {
952 ui.label("Lines");
953 ui.separator();
954
955 // Use `add_enabled_ui` to conditionally enable UI elements
956 ui.add_enabled_ui(is_visible, |ui| {
957 let res = ui.checkbox(&mut c.show_lines, "Show lines");
958 if res.clicked() {
959 command_buffer.insert_one(entity, ShadowMapDirty);
960 }
961
962 if c.show_lines {
963 // Solid color editor
964 ui.horizontal(|ui| {
965 ui.color_edit_button_rgba_unmultiplied(&mut c.line_color.data.0.as_mut_slice()[0]);
966 ui.label("Solid color");
967 });
968
969 // Line width slider
970 ui.horizontal(|ui| {
971 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; // Adjust slider width
972 ui.add(Slider::new(&mut c.line_width, 0.0..=30.0).text("Width"));
973 });
974
975 // Z-buffer and antialiasing options
976 ui.checkbox(&mut c.zbuffer, "Use Z-Buffer");
977 ui.checkbox(&mut c.antialias_edges, "Antialias");
978 }
979 });
980 }
981
982 // TODO: Probably remove this, can always add back later if needed
983 // fn draw_vis_wireframe(
984 // &mut self,
985 // // ctx: &egui::Context,
986 // ui: &mut Ui,
987 // // renderer: &Renderer,
988 // _scene: &Scene,
989 // entity: Entity,
990 // command_buffer: &mut CommandBuffer,
991 // is_visible: bool,
992 // c: &mut VisWireframe,
993 // ) {
994 // //VIS Wireframe
995 // ui.label("Wireframe");
996 // ui.separator();
997 // ui.add_visible_ui(is_visible, |ui| {
998 // let res = ui.checkbox(&mut c.show_wireframe, "Show wireframe");
999 // if res.clicked() {
1000 // command_buffer.insert_one(entity, ShadowMapDirty);
1001 // }
1002 // if c.show_wireframe {
1003 // // ui.add_enabled_ui(c.show_wireframe, |ui| {
1004 // //solid_color
1005 // ui.horizontal(|ui| {
1006 // ui.color_edit_button_rgba_unmultiplied(
1007 // &mut c.wire_color.data.0.as_mut_slice()[0],
1008 // );
1009 // ui.label("Wire color");
1010 // });
1011 // //width
1012 // ui.horizontal(|ui| {
1013 // ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
1014 // //changes size od slider ui.add(Slider::new(&mut
1015 // c.wire_width, 0.0..=8.0).text("Width")) });
1016 // // });
1017 // }
1018 // });
1019 // }
1020
1021 // fn draw_vis_normals(
1022 // &mut self,
1023 // // ctx: &egui::Context,
1024 // ui: &mut Ui,
1025 // // renderer: &Renderer,
1026 // _scene: &Scene,
1027 // entity: Entity,
1028 // command_buffer: &mut CommandBuffer,
1029 // is_visible: bool,
1030 // c: &mut VisNormals,
1031 // ) {
1032 // //VIS Normals
1033 // ui.label("Normals");
1034 // ui.separator();
1035 // ui.add_visible_ui(is_visible, |ui| {
1036 // let res = ui.checkbox(&mut c.show_normals, "Show Normals");
1037 // if res.clicked() {
1038 // command_buffer.insert_one(entity, ShadowMapDirty);
1039 // }
1040 // if c.show_normals {
1041 // // ui.add_enabled_ui(c.show_wireframe, |ui| {
1042 // //solid_color
1043 // ui.horizontal(|ui| {
1044 // ui.color_edit_button_rgba_unmultiplied(
1045 // &mut c.normals_color.data.0.as_mut_slice()[0],
1046 // );
1047 // ui.label("Normal color");
1048 // });
1049 // //width
1050 // ui.horizontal(|ui| {
1051 // ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
1052 // //changes size od slider ui.add(Slider::new(&mut
1053 // c.normals_width, 0.0..=8.0).text("Width")) });
1054 // //scale
1055 // ui.horizontal(|ui| {
1056 // ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0;
1057 // //changes size od slider ui.add(Slider::new(&mut
1058 // c.normals_scale, 0.0..=1.0).text("Scale")) });
1059 // }
1060 // });
1061 // }
1062
1063 fn draw_comps(&mut self, ui: &mut Ui, scene: &Scene, entity: Entity, _command_buffer: &mut CommandBuffer) {
1064 let e_ref = scene.world.entity(entity).unwrap();
1065 //print component names
1066 let comp_infos: Vec<gloss_hecs::TypeInfo> = e_ref.component_infos().collect();
1067 let comp_full_names: Vec<String> = comp_infos.iter().map(gloss_hecs::TypeInfo::name).collect();
1068 // let mut comp_names: Vec<String> = comp_full_names
1069 // .iter()
1070 // .map(|n| n.split("::").last().unwrap().to_string())
1071 // .collect();
1072 // Some of our comps are now generic so they need to be handled
1073 let mut comp_names: Vec<String> = comp_full_names
1074 .iter()
1075 .map(|n| {
1076 // Split at the '<' character if it exists, otherwise split at "::" and take the
1077 // last part
1078 if let Some(pos) = n.find('<') {
1079 n[..pos].split("::").last().unwrap().to_string()
1080 } else {
1081 n.split("::").last().unwrap().to_string()
1082 }
1083 })
1084 .collect();
1085
1086 //concat also the type id
1087 let mut comp_names_and_type = Vec::new();
1088 for (comp_name, comp_info) in comp_names.iter().zip(comp_infos.iter()) {
1089 let comp_info_str = format!("{:?}", comp_info.id());
1090 comp_names_and_type.push(comp_name.to_owned() + &comp_info_str);
1091 }
1092 comp_names.sort();
1093 comp_names_and_type.sort();
1094
1095 for name in comp_names {
1096 ui.label(RichText::new(name).font(FontId::proportional(10.0)));
1097 }
1098 }
1099
1100 fn draw_textures(
1101 &mut self,
1102 ui: &mut Ui,
1103 scene: &mut Scene,
1104 egui_renderer: &mut egui_wgpu::Renderer,
1105 gpu: &Gpu,
1106 _command_buffer: &mut CommandBuffer,
1107 ) {
1108 if scene.nr_renderables() == 0 {
1109 return;
1110 }
1111 let Some(entity) = scene.get_entity_with_name(&self.selected_mesh_name) else {
1112 error!("Selected mesh does not exist with name {}", self.selected_mesh_name);
1113 return;
1114 };
1115
1116 //get diffuse tex
1117 ui.label("Diffuse");
1118 ui.separator();
1119 let diffuse_tex = scene.get_comp::<&DiffuseTex>(&entity).unwrap();
1120 let (view, id) = if scene.world.has::<DiffuseImg>(entity).unwrap() {
1121 let diffuse_id = diffuse_tex.0.texture.global_id();
1122 (&diffuse_tex.0.view, diffuse_id)
1123 } else {
1124 let diffuse_tex = self.default_texture.as_ref().unwrap();
1125 let diffuse_id = diffuse_tex.texture.global_id();
1126 (&diffuse_tex.view, diffuse_id)
1127 };
1128 //get egui textureid
1129 let diffuse_egui_tex_id = self
1130 .wgputex_2_eguitex
1131 .entry(id)
1132 .or_insert_with(|| egui_renderer.register_native_texture(gpu.device(), view, wgpu::FilterMode::Linear));
1133 //show img
1134 let res = ui.add(egui::Image::from_texture((*diffuse_egui_tex_id, Vec2::new(120.0, 120.0))));
1135 self.hovered_diffuse_tex = res.hovered();
1136
1137 //get normal tex
1138 ui.label("Normal");
1139 ui.separator();
1140 let normal_tex = scene.get_comp::<&NormalTex>(&entity).unwrap();
1141 let (view, id) = if scene.world.has::<NormalImg>(entity).unwrap() {
1142 let normal_id = normal_tex.0.texture.global_id();
1143 (&normal_tex.0.view, normal_id)
1144 } else {
1145 let normal_tex = self.default_texture.as_ref().unwrap();
1146 let normal_id = normal_tex.texture.global_id();
1147 (&normal_tex.view, normal_id)
1148 };
1149 //get egui textureid
1150 let normal_egui_tex_id = self
1151 .wgputex_2_eguitex
1152 .entry(id)
1153 .or_insert_with(|| egui_renderer.register_native_texture(gpu.device(), view, wgpu::FilterMode::Linear));
1154 //show img
1155 let res = ui.add(egui::Image::from_texture((*normal_egui_tex_id, Vec2::new(120.0, 120.0))));
1156 self.hovered_normal_tex = res.hovered();
1157
1158 //get normal tex
1159 ui.label("Roughness");
1160 ui.separator();
1161 let roughness_tex = scene.get_comp::<&RoughnessTex>(&entity).unwrap();
1162 let (view, id) = if scene.world.has::<RoughnessImg>(entity).unwrap() {
1163 let roughness_id = roughness_tex.0.texture.global_id();
1164 (&roughness_tex.0.view, roughness_id)
1165 } else {
1166 let roughness_tex = self.default_texture.as_ref().unwrap();
1167 let roughness_id = roughness_tex.texture.global_id();
1168 (&roughness_tex.view, roughness_id)
1169 };
1170 //get egui textureid
1171 let roughness_egui_tex_id = self
1172 .wgputex_2_eguitex
1173 .entry(id)
1174 .or_insert_with(|| egui_renderer.register_native_texture(gpu.device(), view, wgpu::FilterMode::Linear));
1175 //show img
1176 let res = ui.add(egui::Image::from_texture((*roughness_egui_tex_id, Vec2::new(120.0, 120.0))));
1177 self.hovered_roughness_tex = res.hovered();
1178 }
1179
1180 // TODO: Keep or remove?
1181 // #[allow(clippy::similar_names)]
1182 // fn draw_move(&mut self, ui: &mut Ui, scene: &Scene, command_buffer: &mut
1183 // CommandBuffer) { egui::ComboBox::from_label("Mode")
1184 // .selected_text(format!("{:?}", self.gizmo_mode))
1185 // .show_ui(ui, |ui| {
1186 // ui.selectable_value(&mut self.gizmo_mode, GizmoMode::Rotate,
1187 // "Rotate"); ui.selectable_value(&mut self.gizmo_mode,
1188 // GizmoMode::Translate, "Translate"); ui.selectable_value(&mut
1189 // self.gizmo_mode, GizmoMode::Scale, "Scale"); });
1190
1191 // egui::ComboBox::from_label("Orientation")
1192 // .selected_text(format!("{:?}", self.gizmo_orientation))
1193 // .show_ui(ui, |ui| {
1194 // ui.selectable_value(
1195 // &mut self.gizmo_orientation,
1196 // GizmoOrientation::Global,
1197 // "Global",
1198 // );
1199 // ui.selectable_value(
1200 // &mut self.gizmo_orientation,
1201 // GizmoOrientation::Local,
1202 // "Local",
1203 // );
1204 // });
1205
1206 // //get camera
1207 // let cam = scene.get_current_cam().unwrap();
1208 // if !cam.is_initialized(scene) {
1209 // return;
1210 // }
1211 // let view = cam.view_matrix(scene);
1212 // let proj = cam.proj_matrix(scene);
1213 // let v: [[f32; 4]; 4] = view.into();
1214 // let p: [[f32; 4]; 4] = proj.into();
1215
1216 // //model matrix
1217 // // let entity = scene.get_entity_with_name(&self.selected_mesh_name);
1218 // if let Some(entity) = self.selected_entity {
1219 // let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap();
1220 // let mm: [[f32; 4]; 4] = model_matrix.0.to_homogeneous().into();
1221
1222 // let gizmo = Gizmo::new("My gizmo")
1223 // .view_matrix(v.into())
1224 // .projection_matrix(p.into())
1225 // .model_matrix(mm.into())
1226 // .mode(self.gizmo_mode)
1227 // .orientation(self.gizmo_orientation);
1228
1229 // let possible_gizmo_response = gizmo.interact(ui);
1230
1231 // if let Some(gizmo_response) = possible_gizmo_response {
1232 // let _new_model_matrix = gizmo_response.transform();
1233
1234 // //TODO you can actually do gizmo_response.translation and
1235 // gizmo_reponse.quat directly...
1236
1237 // //get T
1238 // let new_t_mint = gizmo_response.translation;
1239 // let mut new_t = na::Translation3::<f32>::identity();
1240 // new_t.x = new_t_mint.x;
1241 // new_t.y = new_t_mint.y;
1242 // new_t.z = new_t_mint.z;
1243 // //get R
1244 // let new_q_mint = gizmo_response.rotation;
1245 // let mut new_quat = na::Quaternion::<f32>::identity();
1246 // new_quat.i = new_q_mint.v.x;
1247 // new_quat.j = new_q_mint.v.y;
1248 // new_quat.k = new_q_mint.v.z;
1249 // new_quat.w = new_q_mint.s;
1250 // let new_quat_unit =
1251 // na::UnitQuaternion::from_quaternion(new_quat); let new_rot =
1252 // new_quat_unit.into(); //get scale
1253 // let new_scale_mint = gizmo_response.scale;
1254 // let new_scale =
1255 // new_scale_mint.x.max(new_scale_mint.y).max(new_scale_mint.z); //TODO For now
1256 // we only get one scale value
1257
1258 // // let new_scale = gizmo_response.scale.max_element(); //TODO For
1259 // now we only get one scale value //
1260 // // println!("gizmo scale is {:?}", gizmo_response.scale); //
1261 // // println!("new_scale {:?}", new_scale); //combine
1262 // let mut new_model_mat = na::SimilarityMatrix3::<f32>::identity();
1263 // new_model_mat.append_rotation_mut(&new_rot);
1264 // new_model_mat.append_translation_mut(&new_t);
1265 // new_model_mat.set_scaling(new_scale);
1266
1267 // //set
1268 // // mesh.set_model_matrix(scene, new_isometry);
1269 // let new_model_matrix = ModelMatrix(new_model_mat);
1270 // command_buffer.insert_one(entity, new_model_matrix);
1271
1272 // //if it has pos lookat we also modify that
1273 // if scene.world.has::<PosLookat>(entity).unwrap() {
1274 // let pos_lookat =
1275 // scene.get_comp::<&PosLookat>(&entity).unwrap(); // TODO
1276 // make poslookat clone let pos_lookat =
1277 // PosLookat::new_from_model_matrix(new_model_mat,
1278 // pos_lookat.dist_lookat());
1279 // command_buffer.insert_one(entity, pos_lookat); }
1280 // }
1281 // }
1282 // }
1283
1284 fn draw_params(&mut self, ui: &mut Ui, _scene: &mut Scene, config: &mut Config, _command_buffer: &mut CommandBuffer) {
1285 //TODO get all things from config
1286
1287 //ambient factor
1288 ui.horizontal(|ui| {
1289 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1290 ui.add(Slider::new(&mut config.render.ambient_factor, 0.0..=1.0).text("AmbientFactor"))
1291 });
1292 //environment_factor
1293 ui.horizontal(|ui| {
1294 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1295 ui.add(Slider::new(&mut config.render.environment_factor, 0.0..=5.0).text("EnvFactor"))
1296 });
1297 //bg_color
1298 ui.horizontal(|ui| {
1299 ui.color_edit_button_rgba_premultiplied(&mut config.render.bg_color.data.0.as_mut_slice()[0]);
1300 ui.label("bg_color");
1301 });
1302 //distance fade
1303 ui.checkbox(config.render.enable_distance_fade.as_mut().unwrap_or(&mut false), "DistanceFade");
1304 //distance fade start
1305 ui.horizontal(|ui| {
1306 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1307 ui.add(Slider::new(config.render.distance_fade_start.as_mut().unwrap_or(&mut 0.0), 1.0..=100.0).text("FadeStart"))
1308 });
1309 //distance fade end
1310 ui.horizontal(|ui| {
1311 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1312 ui.add(Slider::new(config.render.distance_fade_end.as_mut().unwrap_or(&mut 0.0), 1.0..=100.0).text("FadeEnd"))
1313 });
1314 //saturation
1315 ui.horizontal(|ui| {
1316 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1317 ui.add(Slider::new(&mut config.render.saturation, 0.0..=2.0).text("Saturation"))
1318 });
1319 //gamma
1320 ui.horizontal(|ui| {
1321 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1322 ui.add(Slider::new(&mut config.render.gamma, 0.5..=1.5).text("Gamma"))
1323 });
1324 //exposure
1325 ui.horizontal(|ui| {
1326 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1327 ui.add(Slider::new(&mut config.render.exposure, -5.0..=5.0).text("Exposure"))
1328 });
1329 }
1330
1331 #[allow(clippy::too_many_lines)]
1332 fn draw_lights(&mut self, ui: &mut Ui, scene: &mut Scene, command_buffer: &mut CommandBuffer) {
1333 // //get all entities that are renderable and sort by name
1334 let entities = scene.get_lights(true);
1335
1336 //go through all the lights and show their name
1337 ui.group(|ui| {
1338 ScrollArea::vertical()
1339 .max_height(200.0)
1340 .scroll_bar_visibility(scroll_area::ScrollBarVisibility::AlwaysVisible)
1341 .auto_shrink([false, false])
1342 .show(ui, |ui| {
1343 ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
1344 ui.spacing_mut().item_spacing.y = 0.0;
1345 ui.spacing_mut().button_padding.y = 4.0;
1346 for entity in entities {
1347 let e_ref = scene.world.entity(entity).unwrap();
1348
1349 //get the name of the mesh which acts like a unique id
1350 let name = e_ref.get::<&Name>().expect("The entity has no name").0.clone();
1351
1352 //if it's the first time we encounter a renderable mesh, we set the selected
1353 // name to this one
1354 if self.selected_light_name.is_empty() {
1355 self.selected_light_name.clone_from(&name);
1356 }
1357
1358 //GUI for this concrete mesh
1359 //if we click we can see options for vis
1360 let _res = ui.selectable_value(&mut self.selected_light_name, name.clone(), &name);
1361
1362 if name == self.selected_light_name {
1363 self.selected_light_entity = Some(entity);
1364 }
1365 }
1366 });
1367 });
1368 });
1369
1370 if let Some(entity) = self.selected_light_entity {
1371 ui.label("LightEmit");
1372 ui.separator();
1373 //color
1374 ui.horizontal(|ui| {
1375 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1376 ui.color_edit_button_rgb(&mut comp_light_emit.color.data.0.as_mut_slice()[0]);
1377 ui.label("color");
1378 });
1379 //intensity
1380 ui.horizontal(|ui| {
1381 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1382 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1383 ui.add(Slider::new(&mut comp_light_emit.intensity, 0.0..=1_000_000.0).text("intensity"))
1384 });
1385 //cast shadows
1386 let mut is_shadow_casting: bool = scene.world.has::<ShadowCaster>(entity).unwrap();
1387 let res = ui.checkbox(&mut is_shadow_casting, "CastShadows");
1388 if res.changed() {
1389 if is_shadow_casting {
1390 //TODO adding this parameter just adds it with these daault. Maybe we need
1391 // another way to temporarily disable shadows
1392 command_buffer.insert_one(
1393 entity,
1394 ShadowCaster {
1395 shadow_res: 2048,
1396 shadow_bias_fixed: 2e-5,
1397 shadow_bias: 0.15,
1398 shadow_bias_normal: 1.5,
1399 },
1400 );
1401 } else {
1402 command_buffer.remove_one::<ShadowCaster>(entity);
1403 }
1404 }
1405 // shadow_res
1406 if scene.world.has::<ShadowCaster>(entity).unwrap() {
1407 //we only mutate the component if we modify the slider because we want the
1408 // change detection of hecs to only pick-up and detect when we actually change
1409 // the value
1410 let shadow_caster_read = scene.get_comp::<&ShadowCaster>(&entity).unwrap();
1411 let mut shadow_res = shadow_caster_read.shadow_res;
1412 let mut shadow_bias_fixed = shadow_caster_read.shadow_bias_fixed;
1413 let mut shadow_bias = shadow_caster_read.shadow_bias;
1414 let mut shadow_bias_normal = shadow_caster_read.shadow_bias_normal;
1415 drop(shadow_caster_read);
1416 // ui.horizontal(|ui| {
1417 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1418 let res = ui.add(Slider::new(&mut shadow_res, 128..=4096).text("shadow_res"));
1419 if res.changed() {
1420 //now we actually mutate the component
1421 let mut shadow_caster = scene.get_comp::<&mut ShadowCaster>(&entity).unwrap();
1422 shadow_caster.shadow_res = shadow_res;
1423 }
1424 //shadow bias fixed
1425 let res = ui.add(Slider::new(&mut shadow_bias_fixed, 0.0..=0.5).text("shadow_bias_fixed"));
1426 if res.changed() {
1427 //now we actually mutate the component
1428 let mut shadow_caster = scene.get_comp::<&mut ShadowCaster>(&entity).unwrap();
1429 shadow_caster.shadow_bias_fixed = shadow_bias_fixed;
1430 }
1431
1432 //shadow bias light
1433 let res = ui.add(Slider::new(&mut shadow_bias, 0.0..=0.4).text("shadow_bias"));
1434 if res.changed() {
1435 //now we actually mutate the component
1436 let mut shadow_caster = scene.get_comp::<&mut ShadowCaster>(&entity).unwrap();
1437 shadow_caster.shadow_bias = shadow_bias;
1438 }
1439 //shadow bias normal
1440 let res = ui.add(Slider::new(&mut shadow_bias_normal, 0.0..=50.0).text("shadow_bias_normal"));
1441 if res.changed() {
1442 //now we actually mutate the component
1443 let mut shadow_caster = scene.get_comp::<&mut ShadowCaster>(&entity).unwrap();
1444 shadow_caster.shadow_bias_normal = shadow_bias_normal;
1445 }
1446 // });
1447 }
1448 //range
1449 ui.horizontal(|ui| {
1450 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1451 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1452 ui.add(Slider::new(&mut comp_light_emit.range, 0.0..=10000.0).text("range"))
1453 });
1454 //radius
1455 ui.horizontal(|ui| {
1456 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1457 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1458 ui.add(Slider::new(&mut comp_light_emit.radius, 0.0..=10.0).text("radius"))
1459 });
1460 //inner_angle
1461 ui.horizontal(|ui| {
1462 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1463 let outer_angle = comp_light_emit.outer_angle;
1464 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1465 ui.add(Slider::new(&mut comp_light_emit.inner_angle, 0.0..=outer_angle).text("inner_angle"))
1466 });
1467 //outer_angle
1468 ui.horizontal(|ui| {
1469 let mut comp_light_emit = scene.get_comp::<&mut LightEmit>(&entity).unwrap();
1470 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1471 ui.add(Slider::new(&mut comp_light_emit.outer_angle, 0.0..=std::f32::consts::PI / 2.0).text("outer_angle"))
1472 });
1473 ui.horizontal(|ui| {
1474 let mut comp_proj = scene.get_comp::<&mut Projection>(&entity).unwrap();
1475 let (mut near, _) = comp_proj.near_far();
1476 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1477 let res = ui.add(Slider::new(&mut near, 0.0..=20.0).text("near"));
1478 if res.changed() {
1479 comp_proj.set_near(near);
1480 }
1481 });
1482 //far
1483 ui.horizontal(|ui| {
1484 let mut comp_proj = scene.get_comp::<&mut Projection>(&entity).unwrap();
1485 let (_, mut far) = comp_proj.near_far();
1486 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1487 let res = ui.add(Slider::new(&mut far, 0.0..=2000.0).text("far"));
1488 if res.changed() {
1489 comp_proj.set_far(far);
1490 }
1491 });
1492 {
1493 let mut poslookat = scene.get_comp::<&mut PosLookat>(&entity).unwrap();
1494 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1495 ui.add(Slider::new(&mut poslookat.position.x, -40.0..=40.0).text("x"));
1496 ui.add(Slider::new(&mut poslookat.position.y, -40.0..=40.0).text("y"));
1497 ui.add(Slider::new(&mut poslookat.position.z, -40.0..=40.0).text("z"));
1498 }
1499 //view plane that cna be used to manipulate the light
1500 let mut has_mesh = scene.world.has::<Renderable>(entity).unwrap();
1501 let res = ui.checkbox(&mut has_mesh, "ShowLight");
1502 if res.changed() {
1503 if has_mesh {
1504 //add plane
1505 let center = scene.get_comp::<&PosLookat>(&entity).unwrap().position;
1506 let normal = scene.get_comp::<&PosLookat>(&entity).unwrap().direction();
1507 //move the plane a bit behind the light so it doesn't cast a shadow
1508 let center = center - normal * 0.03;
1509 let mut builder = builders::build_plane(center, normal, 0.3, 0.3, false);
1510 let _ = scene.world.insert(entity, builder.build());
1511 let _ = scene.world.insert_one(
1512 entity,
1513 VisMesh {
1514 solid_color: na::Vector4::<f32>::new(1.0, 1.0, 1.0, 1.0),
1515 ..Default::default()
1516 },
1517 );
1518 let _ = scene.world.insert_one(entity, Renderable);
1519 } else {
1520 command_buffer.remove_one::<Renderable>(entity);
1521 }
1522 }
1523 }
1524 }
1525
1526 #[allow(clippy::too_many_lines)]
1527 fn draw_cam(&mut self, ui: &mut Ui, scene: &mut Scene, _command_buffer: &mut CommandBuffer) {
1528 // get all entities that are renderable and sort by name
1529 let _entities = scene.get_lights(true);
1530 let cam = scene.get_current_cam().unwrap();
1531
1532 if let Ok(mut projection) = scene.world.get::<&mut Projection>(cam.entity) {
1533 let (mut near, mut far) = projection.near_far();
1534 ui.label("Projection");
1535 ui.separator();
1536 ui.spacing_mut().slider_width = SIDE_PANEL_WIDTH / 3.0; //changes size od slider
1537 let res = ui.add(Slider::new(&mut near, 1e-5..=1.0).text("near"));
1538 if res.changed() {
1539 projection.set_near(near);
1540 }
1541 let res = ui.add(Slider::new(&mut far, near..=10000.0).text("far"));
1542 if res.changed() {
1543 projection.set_far(far);
1544 }
1545 }
1546 }
1547
1548 #[allow(clippy::too_many_lines)]
1549 #[allow(clippy::cast_precision_loss)]
1550 fn draw_plugins(&mut self, ui: &mut Ui, _scene: &mut Scene, plugins: &Plugins, _command_buffer: &mut CommandBuffer) {
1551 // //get all entities that are renderable and sort by name
1552 // let entities = scene.get_lights(true);
1553 egui::Grid::new("grid_plugins").show(ui, |ui| {
1554 for system_and_metadata in plugins.logic_systems.iter() {
1555 let metadata = &system_and_metadata.1;
1556 let sys = &system_and_metadata.0;
1557 let name = sys.name.clone().unwrap_or(RString::from("unknown_name"));
1558 let ms = metadata.execution_time.as_nanos() as f32 / 1_000_000.0;
1559 let ms_string = format!("{ms:.2}");
1560 //draw in green if the system took more than 0.01ms (todo we need a better way
1561 // to check if the system was ran)
1562 let was_ran = ms > 0.07;
1563 // ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
1564 if was_ran {
1565 ui.label(egui::RichText::new(name).strong());
1566 ui.label(egui::RichText::new(ms_string).strong());
1567 } else {
1568 ui.label(egui::RichText::new(name).weak());
1569 ui.label(egui::RichText::new(ms_string).weak());
1570 }
1571 ui.end_row();
1572 }
1573 });
1574
1575 // //go through all the lights and show their name
1576 // TODO: Keep or remove?
1577 // ui.group(|ui| {
1578 // ScrollArea::vertical()
1579 // .max_height(200.0)
1580 // .scroll_bar_visibility(scroll_area::ScrollBarVisibility::AlwaysVisible)
1581 // .auto_shrink([false, false])
1582 // .show(ui, |ui| {
1583 // ui.with_layout(Layout::top_down_justified(Align::LEFT),
1584 // |ui| { ui.spacing_mut().item_spacing.y = 0.0;
1585 // ui.spacing_mut().button_padding.y = 4.0;
1586 // for entity in entities {
1587 // let e_ref = scene.world.entity(entity).unwrap();
1588
1589 // //get the name of the mesh which acts like a
1590 // unique id let name = e_ref
1591 // .get::<&Name>()
1592 // .expect("The entity has no name")
1593 // .0
1594 // .clone();
1595
1596 // //if it's the first time we encounter a
1597 // renderable mesh, we set the selected name to this one
1598 // if self.selected_light_name.is_empty() {
1599 // self.selected_light_name = name.clone();
1600 // }
1601
1602 // //GUI for this concrete mesh
1603 // //if we click we can see options for vis
1604 // let _res = ui.selectable_value(
1605 // &mut self.selected_light_name,
1606 // name.clone(),
1607 // &name,
1608 // );
1609
1610 // if name == self.selected_light_name {
1611 // self.selected_light_entity = Some(entity);
1612 // }
1613 // }
1614 // });
1615 // });
1616 // });
1617 }
1618
1619 #[allow(clippy::too_many_lines)]
1620 fn draw_io(&mut self, ui: &mut Ui, scene: &mut Scene, _command_buffer: &mut CommandBuffer, selected_entity: Option<Entity>) {
1621 //save obj
1622 if let Some(selected_entity) = selected_entity {
1623 if ui.add(egui::Button::new("Save Obj")).clicked() {
1624 //get v, f and possibly uv from the entity
1625 if scene.world.has::<Verts>(selected_entity).unwrap() && scene.world.has::<Faces>(selected_entity).unwrap() {
1626 let v = scene.get_comp::<&Verts>(&selected_entity).unwrap();
1627 // let uv = scene.get_comp::<&UVs>(&selected_entity);
1628
1629 //transform vertices
1630 let mm = scene.get_comp::<&ModelMatrix>(&selected_entity).unwrap();
1631 let v = geom::transform_verts(&v.0.to_dmatrix(), &mm.0);
1632
1633 let f = scene.get_comp::<&Faces>(&selected_entity).ok().map(|f| f.0.clone());
1634
1635 let uv = scene.get_comp::<&UVs>(&selected_entity).ok().map(|uv| uv.0.clone());
1636
1637 let normals = scene
1638 .get_comp::<&Normals>(&selected_entity)
1639 .ok()
1640 .map(|normals| geom::transform_vectors(&normals.0.to_dmatrix(), &mm.0));
1641
1642 //TODO make the path parametrizable
1643 geom::save_obj(
1644 &v,
1645 f.map(|faces| faces.to_dmatrix()).as_ref(),
1646 // None,
1647 uv.map(|faces| faces.to_dmatrix()).as_ref(),
1648 normals.as_ref(),
1649 "./saved_obj.obj",
1650 );
1651 }
1652 }
1653 }
1654
1655 //save ply
1656 if let Some(selected_entity) = selected_entity {
1657 if ui.add(egui::Button::new("Save Ply")).clicked() {
1658 //get v, f and possibly uv from the entity
1659 if scene.world.has::<Verts>(selected_entity).unwrap() && scene.world.has::<Faces>(selected_entity).unwrap() {
1660 let v = scene.get_comp::<&Verts>(&selected_entity).unwrap();
1661
1662 //transform vertices
1663 let mm = scene.get_comp::<&ModelMatrix>(&selected_entity).unwrap();
1664 let v = geom::transform_verts(&v.0.to_dmatrix(), &mm.0);
1665
1666 let f = scene.get_comp::<&Faces>(&selected_entity).ok().map(|f| f.0.clone());
1667
1668 let uv = scene.get_comp::<&UVs>(&selected_entity).ok().map(|uv| uv.0.clone());
1669
1670 let normals = scene
1671 .get_comp::<&Normals>(&selected_entity)
1672 .ok()
1673 .map(|normals| geom::transform_vectors(&normals.0.to_dmatrix(), &mm.0));
1674
1675 let colors = scene.get_comp::<&Colors>(&selected_entity).ok().map(|colors| colors.0.clone());
1676
1677 //TODO make the path parametrizable
1678 geom::save_ply(
1679 &v,
1680 f.map(|faces| faces.to_dmatrix()).as_ref(),
1681 // None,
1682 uv.map(|uvs| uvs.to_dmatrix()).as_ref(),
1683 normals.as_ref(),
1684 colors.map(|colors| colors.to_dmatrix()).as_ref(),
1685 "./saved_ply.ply",
1686 );
1687 }
1688 }
1689 }
1690 }
1691
1692 #[allow(clippy::too_many_lines)]
1693 #[allow(unused_variables)]
1694 fn draw_profiling(&mut self, ui: &mut Ui, _scene: &mut Scene, _command_buffer: &mut CommandBuffer) {
1695 // TODO: Keep or remove?
1696 // cfg_if::cfg_if! {
1697 // if #[cfg(feature = "peak-alloc")] {
1698 // let current_mem = PEAK_ALLOC.current_usage_as_mb() as u32;
1699 // let peak_mem = PEAK_ALLOC.peak_usage_as_mb() as u32;
1700 // ui.label("current_mem (MB): ".to_owned() + ¤t_mem.to_string());
1701 // ui.label("peak_mem(MB): ".to_owned() + &peak_mem.to_string());
1702 // }
1703 // }
1704
1705 // cfg_if::cfg_if! {
1706 // if #[cfg(feature = "talc")] {
1707
1708 // cfg_if::cfg_if! {
1709 // if #[cfg(target_arch = "wasm32")] {
1710 // use crate::ALLOCATOR;
1711 // let talc = ALLOCATOR.lock();
1712 // let counters = talc.get_counters();
1713 // ui.label("available to claim MB: ".to_owned() +
1714 // &(counters.available_bytes/(1024*1024)).to_string());
1715 // ui.label("nr gaps: ".to_owned() + &counters.fragment_count.to_string());
1716 // ui.label("claimed MB: ".to_owned() +
1717 // &(counters.claimed_bytes/(1024*1024)).to_string());
1718 // ui.label("unavailable MB: ".to_owned() +
1719 // &(counters.overhead_bytes()/(1024*1024)).to_string());
1720 // ui.label("total_freed MB: ".to_owned() +
1721 // &(counters.total_freed_bytes()/(1024*1024)).to_string()); }}
1722 // }
1723 // }
1724
1725 // cfg_if::cfg_if! {
1726 // if #[cfg(feature = "jemallocator")] {
1727
1728 // ui.label("current_mem (MB): ".to_owned());
1729 // }
1730 // }
1731
1732 // cfg_if::cfg_if! {
1733 // if #[cfg(feature = "mimalloc")] {
1734 // ui.label("mimalloc");
1735
1736 // ui.label("current_mem (MB): ".to_owned());
1737 // }
1738 // }
1739
1740 //for accounting allocator
1741 let memory_usage = MemoryUse::capture();
1742 if let Some(mem_resident) = memory_usage.resident {
1743 ui.label(egui::RichText::new(
1744 "MB resident: ".to_owned() + &(mem_resident / (1024 * 1024)).to_string(),
1745 ));
1746 }
1747 if let Some(mem_counted) = memory_usage.counted {
1748 ui.label(egui::RichText::new(
1749 "MB counted: ".to_owned() + &(mem_counted / (1024 * 1024)).to_string(),
1750 ));
1751 }
1752
1753 //are we tracking callstacks
1754 let mut is_tracking = re_memory::accounting_allocator::is_tracking_callstacks();
1755 let res = ui.checkbox(&mut is_tracking, "Track memory callstacks");
1756 if res.clicked() {
1757 re_memory::accounting_allocator::set_tracking_callstacks(is_tracking);
1758 }
1759
1760 // //make a memory bar where we show all the memory and the parts that are free in
1761 // // red, parts that are allocated in red only works in wasm because it
1762 // // uses a linear memory model
1763 // let size_per_mb = 1.0;
1764 // // #[cfg(target_arch = "wasm32")]
1765 // {
1766 // use egui::{epaint::RectShape, Sense, Shape};
1767 // let unknown_mem = Color32::from_rgb(150, 150, 150);
1768 // let allocated_mem = Color32::from_rgb(255, 10, 10);
1769
1770 // //make per mb colors
1771 // let mut mb2color: Vec<egui::Color32> = Vec::new();
1772 // if let Some(mem_resident) = memory_usage.resident {
1773 // let mb_resident = mem_resident / (1024 * 1024);
1774 // mb2color.resize(mb_resident.try_into().unwrap(), unknown_mem);
1775
1776 // // Older usage was
1777 // // let live_allocs = accounting_allocator::live_allocs_list();
1778 // // re_memory does not seem to expose this the same way as before. We can't get the memory addresses.
1779 // // Using backtrace string as a unique identifier since re_memory doesn't expose memory addresses anymore.
1780 // // This maintains similar functionality while working with the available API.
1781 // // let tracking_stats = accounting_allocator::tracking_stats().unwrap();
1782 // // let top_callstacks = tracking_stats.top_callstacks;
1783 // // let live_allocs = top_callstacks.;
1784 // // let live_allocs = GLOBAL.tracking_stats().unwrap().top_callstacks;
1785
1786 // // use re_memory::allocation
1787 // // tracking_stats.
1788 // // let live_allocs_big = accounting_allocator::BIG_ALL &BIG_ALLOCATION_TRACKER.lock();
1789 // // use re_memory::accounting_allocator::;
1790 // let live_allocs: Vec<(usize, usize)> = accounting_allocator::tracking_stats()
1791 // .map(|stats| {
1792 // stats
1793 // .top_callstacks
1794 // .iter()
1795 // .map(|cs| (cs.readable_backtrace.to_string().len(), cs.extant.size))
1796 // .collect()
1797 // })
1798 // .unwrap_or_default();
1799
1800 // // Older usage was
1801 // // let min_ptr = accounting_allocator::min_ptr_alloc_memory();
1802 // // We dont have this fn anymore, so we do something similar to above
1803 // // Using first tracked allocation as minimum pointer since re_memory doesn't expose global min ptr.
1804 // #[allow(clippy::get_first)] // getting borrow issues when using .first()
1805 // let min_ptr = accounting_allocator::tracking_stats().map_or(0, |stats| {
1806 // stats.top_callstacks.get(0).map_or(0, |cs| cs.readable_backtrace.to_string().len())
1807 // });
1808
1809 // //for each live allow, paint the value with Red( allocated )
1810 // for (ptr, size) in live_allocs {
1811 // let ptr_mb = (ptr - min_ptr) / (1024 * 1024);
1812 // let size_mb = size / (1024 * 1024);
1813 // //for each mb paint it allocated
1814 // for local_mb_idx in 0..size_mb {
1815 // let idx = ptr_mb + local_mb_idx;
1816 // if idx < mb2color.len() {
1817 // mb2color[idx] = allocated_mem;
1818 // }
1819 // }
1820 // }
1821 // }
1822
1823 // //draw bar
1824 // if let Some(mem_resident) = memory_usage.resident {
1825 // let mb_resident = mem_resident / (1024 * 1024);
1826 // ui.horizontal(|ui| {
1827 // ui.spacing_mut().item_spacing = egui::vec2(0.5, 0.0);
1828 // // for i in 0..mb_resident {
1829 // #[allow(clippy::cast_sign_loss)]
1830 // #[allow(clippy::cast_possible_truncation)]
1831 // for i in 0..(mb_resident as usize) {
1832 // // let cursor = Ui::cursor(ui);
1833 // let rect_size = egui::Vec2::new(size_per_mb, 10.0);
1834 // let rect = ui.allocate_exact_size(rect_size, Sense::click()).0;
1835 // let rounding = Rounding::default();
1836 // #[allow(arithmetic_overflow)]
1837 // // let fill_color = egui::Color32::from_rgb(50 * 3, 0, 0);
1838 // // let fill_color =
1839 // let fill_color = if i < mb2color.len() {
1840 // mb2color[i]
1841 // } else {
1842 // egui::Color32::from_rgb(50, 0, 0)
1843 // };
1844 // let stroke = Stroke::default();
1845 // let rect_shape = RectShape::new(rect, rounding, fill_color, stroke);
1846 // ui.painter().add(Shape::Rect(rect_shape));
1847 // }
1848 // });
1849 // }
1850 // }
1851
1852 // TODO: Find where and how in the re_memory API to get the memory addresses
1853 // TODO: Should we factor in sampling rate here? should be a reasonable estimate regardless?
1854
1855 //make a memory bar showing proportion of allocated vs free memory
1856 let size_per_mb = 1.0;
1857 #[cfg(target_arch = "wasm32")]
1858 {
1859 use egui::{epaint::RectShape, Sense, Shape};
1860 let unknown_mem = Color32::from_rgb(150, 150, 150);
1861 let allocated_mem = Color32::from_rgb(255, 10, 10);
1862
1863 //make per mb colors
1864 let mut mb2color: Vec<egui::Color32> = Vec::new();
1865 if let Some(mem_resident) = memory_usage.resident {
1866 let mb_resident = mem_resident / (1024 * 1024);
1867 mb2color.resize(mb_resident.try_into().unwrap(), unknown_mem);
1868
1869 // Calculate total allocated memory from tracking stats
1870 let total_allocated: usize =
1871 accounting_allocator::tracking_stats().map_or(0, |stats| stats.top_callstacks.iter().map(|cs| cs.extant.size).sum());
1872
1873 // Convert to MB and fill that proportion of the bar with red
1874 let allocated_mb = total_allocated / (1024 * 1024);
1875 for i in 0..allocated_mb {
1876 if i < mb2color.len() {
1877 mb2color[i] = allocated_mem;
1878 }
1879 }
1880 }
1881
1882 //draw bar
1883 if let Some(mem_resident) = memory_usage.resident {
1884 let mb_resident = mem_resident / (1024 * 1024);
1885 ui.horizontal(|ui| {
1886 ui.spacing_mut().item_spacing = egui::vec2(0.5, 0.0);
1887 #[allow(clippy::cast_sign_loss)]
1888 #[allow(clippy::cast_possible_truncation)]
1889 for i in 0..(mb_resident as usize) {
1890 let rect_size = egui::Vec2::new(size_per_mb, 10.0);
1891 let rect = ui.allocate_exact_size(rect_size, Sense::click()).0;
1892 let rounding = Rounding::default();
1893 let fill_color = if i < mb2color.len() {
1894 mb2color[i]
1895 } else {
1896 egui::Color32::from_rgb(50, 0, 0)
1897 };
1898 let stroke = Stroke::default();
1899 let rect_shape = RectShape::new(rect, rounding, fill_color, stroke);
1900 ui.painter().add(Shape::Rect(rect_shape));
1901 }
1902 });
1903 }
1904 }
1905
1906 ui.add_space(15.0);
1907 if let Some(tracks) = accounting_allocator::tracking_stats() {
1908 #[allow(clippy::cast_precision_loss)]
1909 for (i, cb) in tracks.top_callstacks.iter().enumerate() {
1910 //callstack name will be the nr mb
1911 let mb_total = cb.extant.size as f32 / (1024.0 * 1024.0);
1912
1913 let cb_stack = cb.readable_backtrace.to_string();
1914 let last_relevant_func_name = get_last_relevant_func_name(&cb_stack);
1915
1916 let text_header = egui::RichText::new(float2string(mb_total, 1) + " MB: " + &last_relevant_func_name).size(12.0);
1917
1918 ui.push_id(i, |ui| {
1919 egui::CollapsingHeader::new(text_header).show(ui, |ui| self.draw_callstack_profiling(ui, cb));
1920 });
1921 }
1922 }
1923 }
1924
1925 #[allow(clippy::too_many_lines)]
1926 #[allow(unused_variables)]
1927 fn draw_callstack_profiling(&mut self, ui: &mut Ui, cb: &CallstackStatistics) {
1928 let text = "cb: ".to_owned() + &cb.readable_backtrace.to_string();
1929 ui.label(egui::RichText::new(text).size(11.0));
1930 ui.label("count".to_owned() + &(cb.extant.count).to_string());
1931 ui.label("size".to_owned() + &(cb.extant.size / (1024 * 1024)).to_string());
1932 ui.label("stochastic_rate".to_owned() + &(cb.stochastic_rate).to_string());
1933 }
1934}
1935
1936// Generated by egui-themer (https://github.com/grantshandy/egui-themer).
1937#[allow(clippy::too_many_lines)]
1938pub fn style() -> Style {
1939 Style {
1940 spacing: Spacing {
1941 item_spacing: Vec2 { x: 8.0, y: 3.0 },
1942 window_margin: Margin {
1943 left: 6.0,
1944 right: 6.0,
1945 top: 6.0,
1946 bottom: 6.0,
1947 },
1948 button_padding: Vec2 { x: 4.0, y: 1.0 },
1949 menu_margin: Margin {
1950 left: 6.0,
1951 right: 6.0,
1952 top: 6.0,
1953 bottom: 6.0,
1954 },
1955 indent: 18.0,
1956 interact_size: Vec2 { x: 40.0, y: 18.0 },
1957 slider_width: 100.0,
1958 combo_width: 100.0,
1959 text_edit_width: 280.0,
1960 icon_width: 14.0,
1961 icon_width_inner: 8.0,
1962 icon_spacing: 4.0,
1963 tooltip_width: 600.0,
1964 indent_ends_with_horizontal_line: false,
1965 combo_height: 200.0,
1966 scroll: ScrollStyle {
1967 bar_width: 6.0,
1968 handle_min_length: 12.0,
1969 bar_inner_margin: 4.0,
1970 bar_outer_margin: 0.0,
1971 ..Default::default()
1972 },
1973 ..Default::default()
1974 },
1975 interaction: Interaction {
1976 resize_grab_radius_side: 5.0,
1977 resize_grab_radius_corner: 10.0,
1978 show_tooltips_only_when_still: true,
1979 tooltip_delay: 0.5,
1980 ..Default::default()
1981 },
1982 visuals: Visuals {
1983 dark_mode: true,
1984 override_text_color: None,
1985 widgets: Widgets {
1986 noninteractive: WidgetVisuals {
1987 bg_fill: Color32::from_rgba_premultiplied(27, 27, 27, 255),
1988 weak_bg_fill: Color32::from_rgba_premultiplied(27, 27, 27, 255),
1989 bg_stroke: Stroke {
1990 width: 1.0,
1991 color: Color32::from_rgba_premultiplied(60, 60, 60, 255),
1992 },
1993 rounding: Rounding {
1994 nw: 0.0,
1995 ne: 0.0,
1996 sw: 0.0,
1997 se: 0.0,
1998 },
1999 fg_stroke: Stroke {
2000 width: 1.0,
2001 color: Color32::from_rgba_premultiplied(140, 140, 140, 255),
2002 },
2003 expansion: 0.0,
2004 },
2005 inactive: WidgetVisuals {
2006 bg_fill: Color32::from_rgba_premultiplied(60, 60, 60, 255),
2007 weak_bg_fill: Color32::from_rgba_premultiplied(60, 60, 60, 255),
2008 bg_stroke: Stroke {
2009 width: 0.0,
2010 color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
2011 },
2012 rounding: Rounding {
2013 nw: 0.0,
2014 ne: 0.0,
2015 sw: 0.0,
2016 se: 0.0,
2017 },
2018 fg_stroke: Stroke {
2019 width: 1.0,
2020 color: Color32::from_rgba_premultiplied(180, 180, 180, 255),
2021 },
2022 expansion: 0.0,
2023 },
2024 hovered: WidgetVisuals {
2025 bg_fill: Color32::from_rgba_premultiplied(70, 70, 70, 255),
2026 weak_bg_fill: Color32::from_rgba_premultiplied(70, 70, 70, 255),
2027 bg_stroke: Stroke {
2028 width: 1.0,
2029 color: Color32::from_rgba_premultiplied(150, 150, 150, 255),
2030 },
2031 rounding: Rounding {
2032 nw: 0.0,
2033 ne: 0.0,
2034 sw: 0.0,
2035 se: 0.0,
2036 },
2037 fg_stroke: Stroke {
2038 width: 1.5,
2039 color: Color32::from_rgba_premultiplied(240, 240, 240, 255),
2040 },
2041 expansion: 1.0,
2042 },
2043 active: WidgetVisuals {
2044 bg_fill: Color32::from_rgba_premultiplied(55, 55, 55, 255),
2045 weak_bg_fill: Color32::from_rgba_premultiplied(55, 55, 55, 255),
2046 bg_stroke: Stroke {
2047 width: 1.0,
2048 color: Color32::from_rgba_premultiplied(255, 255, 255, 255),
2049 },
2050 rounding: Rounding {
2051 nw: 0.0,
2052 ne: 0.0,
2053 sw: 0.0,
2054 se: 0.0,
2055 },
2056 fg_stroke: Stroke {
2057 width: 2.0,
2058 color: Color32::from_rgba_premultiplied(255, 255, 255, 255),
2059 },
2060 expansion: 1.0,
2061 },
2062 open: WidgetVisuals {
2063 bg_fill: Color32::from_rgba_premultiplied(27, 27, 27, 255),
2064 weak_bg_fill: Color32::from_rgba_premultiplied(27, 27, 27, 255),
2065 bg_stroke: Stroke {
2066 width: 1.0,
2067 color: Color32::from_rgba_premultiplied(60, 60, 60, 255),
2068 },
2069 rounding: Rounding {
2070 nw: 0.0,
2071 ne: 0.0,
2072 sw: 0.0,
2073 se: 0.0,
2074 },
2075 fg_stroke: Stroke {
2076 width: 1.0,
2077 color: Color32::from_rgba_premultiplied(210, 210, 210, 255),
2078 },
2079 expansion: 0.0,
2080 },
2081 },
2082 selection: Selection {
2083 bg_fill: Color32::from_rgba_premultiplied(0, 92, 128, 255),
2084 stroke: Stroke {
2085 width: 1.0,
2086 color: Color32::from_rgba_premultiplied(192, 222, 255, 255),
2087 },
2088 },
2089 hyperlink_color: Color32::from_rgba_premultiplied(90, 170, 255, 255),
2090 faint_bg_color: Color32::from_rgba_premultiplied(0, 0, 0, 0),
2091 extreme_bg_color: Color32::from_rgba_premultiplied(17, 17, 17, 255),
2092 code_bg_color: Color32::from_rgba_premultiplied(64, 64, 64, 255),
2093 warn_fg_color: Color32::from_rgba_premultiplied(255, 143, 0, 255),
2094 error_fg_color: Color32::from_rgba_premultiplied(255, 0, 0, 255),
2095 window_rounding: Rounding {
2096 nw: 0.0,
2097 ne: 0.0,
2098 sw: 0.0,
2099 se: 0.0,
2100 },
2101 window_shadow: Shadow {
2102 // extrusion: 5.0,
2103 color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
2104 ..Default::default()
2105 },
2106 window_fill: Color32::from_rgba_premultiplied(27, 27, 27, 255),
2107 window_stroke: Stroke {
2108 width: 0.0,
2109 color: Color32::from_rgba_premultiplied(71, 71, 71, 255),
2110 },
2111 menu_rounding: Rounding {
2112 nw: 6.0,
2113 ne: 6.0,
2114 sw: 6.0,
2115 se: 6.0,
2116 },
2117 panel_fill: Color32::from_rgba_premultiplied(20, 20, 20, 255),
2118 popup_shadow: Shadow {
2119 // extrusion: 16.0,
2120 color: Color32::from_rgba_premultiplied(0, 0, 0, 96),
2121 ..Default::default()
2122 },
2123 resize_corner_size: 12.0,
2124 // text_cursor_width: 2.0,
2125 text_cursor: TextCursorStyle::default(),
2126 // text_cursor_preview: false,
2127 clip_rect_margin: 3.0,
2128 button_frame: true,
2129 collapsing_header_frame: false,
2130 indent_has_left_vline: true,
2131 striped: false,
2132 slider_trailing_fill: true,
2133 window_highlight_topmost: true,
2134 handle_shape: HandleShape::Circle,
2135 interact_cursor: None,
2136 image_loading_spinners: true,
2137 numeric_color_space: NumericColorSpace::GammaByte,
2138 },
2139 animation_time: 0.08333,
2140 explanation_tooltips: false,
2141 ..Default::default()
2142 }
2143}