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