// License: Apache 2.0. See LICENSE file in root directory. // Copyright(c) 2017 Intel Corporation. All Rights Reserved. #ifdef _MSC_VER #ifndef NOMINMAX #define NOMINMAX #endif #endif #include "viewer.h" #include "os.h" #include "udev-rules.h" #include #include #define ARCBALL_CAMERA_IMPLEMENTATION #include #include #include #include #include namespace rs2 { template T non_negative(const T& input) { return std::max(static_cast(0), input); } // Allocates a frameset from points and texture frames frameset_allocator::frameset_allocator(viewer_model* viewer) : owner(viewer), filter([this](frame f, frame_source& s) { std::vector frame_vec; auto tex = owner->get_last_texture()->get_last_frame(true); if (tex) { frame_vec.push_back(tex); frame_vec.push_back(f); auto frame = s.allocate_composite_frame(frame_vec); if (frame) s.frame_ready(std::move(frame)); } else s.frame_ready(std::move(f)); }) {} // Need out of class declaration to take reference const rs2_option save_to_ply::OPTION_IGNORE_COLOR; const rs2_option save_to_ply::OPTION_PLY_MESH; const rs2_option save_to_ply::OPTION_PLY_BINARY; const rs2_option save_to_ply::OPTION_PLY_NORMALS; void viewer_model::set_export_popup(ImFont* large_font, ImFont* font, rect stream_rect, std::string& error_message, config_file& temp_cfg) { float font_size = (float)temp_cfg.get( configurations::window::font_size ); float w = 32.f * font_size; float h = 20.f * font_size; float x0 = stream_rect.x + stream_rect.w / 3; float y0 = stream_rect.y + stream_rect.h / 3; ImGui::SetNextWindowPos({ x0, y0 }); ImGui::SetNextWindowSize({ w, h }); auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings; ImGui_ScopePushFont(font); ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15, 15)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 1); static export_type tab = export_type::ply; if (ImGui::BeginPopupModal("Export", nullptr, flags)) { ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg); ImGui::PushFont(large_font); for (auto& exporter : exporters) { ImGui::PushStyleColor(ImGuiCol_Text, tab != exporter.first ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, tab != exporter.first ? light_grey : light_blue); ImGui::SameLine(); if (ImGui::Button(exporter.second.name.c_str(), { w / exporters.size() - 50, 30 })) { config_file::instance().set(configurations::viewer::settings_tab, tab); temp_cfg.set(configurations::viewer::settings_tab, tab); tab = exporter.first; } ImGui::PopStyleColor(2); } ImGui::PopFont(); if (tab == export_type::ply) { bool mesh = temp_cfg.get(configurations::ply::mesh); bool use_normals = temp_cfg.get(configurations::ply::use_normals); if (!mesh) use_normals = false; int encoding = temp_cfg.get(configurations::ply::encoding); ImGui::PushStyleColor(ImGuiCol_Text, grey); ImGui::Text("Polygon File Format defines a flexible systematic scheme for storing 3D data"); ImGui::PopStyleColor(); ImGui::NewLine(); ImGui::Separator(); if (ImGui::Checkbox("Meshing", &mesh)) { temp_cfg.set(configurations::ply::mesh, mesh); } ImGui::PushStyleColor(ImGuiCol_Text, grey); ImGui::Text(" Use faces for meshing by connecting each group of 3 adjacent points"); ImGui::PopStyleColor(); ImGui::Separator(); if (!mesh) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, black); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, black); } if (ImGui::Checkbox("Normals", &use_normals)) { if (!mesh) use_normals = false; else temp_cfg.set(configurations::ply::use_normals, use_normals); } if (!mesh) { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Enable meshing to allow vertex normals calculation"); } ImGui::PopStyleColor(2); ImGui::PopStyleVar(); } ImGui::PushStyleColor(ImGuiCol_Text, grey); ImGui::Text(" Calculate vertex normals and add them to the PLY"); ImGui::PopStyleColor(); ImGui::Separator(); ImGui::Text("Encoding:"); ImGui::PushStyleColor(ImGuiCol_Text, grey); ImGui::Text("Save PLY as binary, or as a larger textual human-readable file"); ImGui::PopStyleColor(); if (ImGui::RadioButton("Textual", encoding == configurations::ply::textual)) { encoding = configurations::ply::textual; temp_cfg.set(configurations::ply::encoding, encoding); } if (ImGui::RadioButton("Binary", encoding == configurations::ply::binary)) { encoding = configurations::ply::binary; temp_cfg.set(configurations::ply::encoding, encoding); } auto curr_exporter = exporters.find(tab); if (curr_exporter == exporters.end()) // every tab should have a corresponding exporter error_message = "Exporter not implemented"; else { curr_exporter->second.options[rs2::save_to_ply::OPTION_PLY_MESH] = mesh; curr_exporter->second.options[rs2::save_to_ply::OPTION_PLY_NORMALS] = use_normals; curr_exporter->second.options[rs2::save_to_ply::OPTION_PLY_BINARY] = encoding; } } ImGui::PopStyleColor(2); // button color auto apply = [&]() { config_file::instance() = temp_cfg; update_configuration(); }; ImGui::PushStyleColor(ImGuiCol_Button, button_color); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_color + 0.1f); ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_color + 0.1f); ImGui::SetCursorScreenPos({ (float)(x0 + w / 2), (float)(y0 + h - 30) }); if( ImGui::Button( "Export", ImVec2( font_size * 8.f, 0 ) ) ) { apply(); if (!last_points) error_message = "No depth data available"; else { auto curr_exporter = exporters.find(tab); if (curr_exporter == exporters.end()) // every tab should have a corresponding exporter error_message = "Exporter not implemented"; else if (auto ret = file_dialog_open(save_file, curr_exporter->second.filters.data(), NULL, NULL)) { auto model = ppf.get_points(); frame tex; if (selected_tex_source_uid >= 0 && streams.find(selected_tex_source_uid) != streams.end()) { tex = streams[selected_tex_source_uid].texture->get_last_frame(true); if (tex) ppf.update_texture(tex); } std::string fname(ret); if (!ends_with(rsutils::string::to_lower(fname), curr_exporter->second.extension)) fname += curr_exporter->second.extension; std::unique_ptr exporter; if (tab == export_type::ply) exporter = std::unique_ptr(new rs2::save_to_ply(fname)); auto data = frameset_alloc.process(last_points); for (auto& option : curr_exporter->second.options) { exporter->set_option(option.first, static_cast(option.second)); } export_frame(fname, std::move(exporter), *not_model, data); } } ImGui::CloseCurrentPopup(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Save settings and export file"); } ImGui::SameLine(); if( ImGui::Button( "Cancel", ImVec2( font_size * 8.f, 0 ) ) ) { ImGui::CloseCurrentPopup(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Close window without saving any changes to the settings"); } ImGui::PopStyleColor(3); ImGui::EndPopup(); } ImGui::PopStyleVar(2); ImGui::PopStyleColor(3); } bool big_button(bool* status, ux_window& win, float x, float y, const char* icon, const char* label, bool dropdown, bool enabled, const char* description, ImVec4 text_color = light_grey ) { auto disabled = !enabled; auto font = win.get_font(); auto large_font = win.get_large_font(); float font_size = (float)win.get_font_size(); bool hovered = false; bool clicked = false; if (!disabled) { if (*status) { ImGui::PushStyleColor(ImGuiCol_Text, light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_blue); } else { ImGui::PushStyleColor(ImGuiCol_Text, text_color); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, text_color); } } else { ImGui::PushStyleColor(ImGuiCol_Text, header_color); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, header_color); } float button_width = font_size * 3.8f; ImGui::SetCursorPos( { x, y } ); ImGui::PushFont(large_font); clicked = clicked || ImGui::Button( icon, { button_width, 50 } ); ImGui::PopFont(); hovered = hovered || ImGui::IsItemHovered(); ImGui::SetCursorPos( { x + button_width - font_size, y } ); ImGui::PushFont(font); if (dropdown) { clicked = clicked || ImGui::Button(u8"\uf078", { font_size, 55 } ); hovered = hovered || ImGui::IsItemHovered(); } ImGui::SetCursorPos( { x, y + 35 } ); clicked = clicked || ImGui::Button( label, { button_width, 20 } ); ImGui::PopFont(); hovered = hovered || ImGui::IsItemHovered(); if (hovered) { win.link_hovered(); ImGui::SetTooltip("%s", description); } if (clicked && !disabled) { *status = !(*status); } ImGui::PopStyleColor(2); return clicked && !disabled; } // Get both font and large_font for the export pop-up void viewer_model::show_3dviewer_header(ux_window& win, rs2::rect stream_rect, bool& paused, std::string& error_message) { auto font = win.get_font(); auto large_font = win.get_large_font(); // Draw pose header if pose stream exists bool pose_render = false; for (auto&& s : streams) { if (s.second.is_stream_visible() && s.second.profile.stream_type() == RS2_STREAM_POSE) { pose_render = true; break; } } // Initialize and prepare depth and texture sources int selected_depth_source = -1; std::vector depth_sources_str; std::vector depth_sources; init_depth_uid(selected_depth_source, depth_sources_str, depth_sources); int selected_tex_source = 0; std::vector tex_sources_str; std::vector tex_sources; std::vector tex_profiles; int i = 0; for (auto&& s : streams) { if (s.second.is_stream_visible() && (s.second.profile.stream_type() == RS2_STREAM_COLOR || s.second.profile.stream_type() == RS2_STREAM_INFRARED || s.second.profile.stream_type() == RS2_STREAM_CONFIDENCE || s.second.profile.stream_type() == RS2_STREAM_DEPTH || s.second.profile.stream_type() == RS2_STREAM_FISHEYE)) { auto profile_unique_id = s.second.profile.unique_id(); auto stream_origin_iter = streams_origin.find(profile_unique_id); auto profile_found = stream_origin_iter != streams_origin.end(); if( selected_tex_source_uid == -1 && selected_depth_source_uid != -1 ) { if( profile_found && streams.find(stream_origin_iter->second ) != streams.end() ) { selected_tex_source_uid = streams_origin[profile_unique_id]; } } if( ( profile_found && stream_origin_iter->second == selected_tex_source_uid ) ) { selected_tex_source = i; } if( profile_found ) { // The texture source shall always refer to the raw (original) streams tex_sources.push_back(streams_origin[profile_unique_id]); tex_profiles.push_back(s.second.profile); auto dev_name = s.second.dev ? s.second.dev->dev.get_info(RS2_CAMERA_INFO_NAME) : "Unknown"; std::string stream_name = rs2_stream_to_string(s.second.profile.stream_type()); if (s.second.profile.stream_index()) stream_name += "_" + std::to_string(s.second.profile.stream_index()); tex_sources_str.push_back( rsutils::string::from() << dev_name << " " << stream_name ); i++; } } } for (int i = 0; i < tex_sources.size(); i++) { auto id = tex_sources[i]; auto it = std::find(begin(last_tex_sources), end(last_tex_sources), id); if (it == last_tex_sources.end()) { // Don't auto-switch to IR stream if (tex_profiles[i].format() != RS2_FORMAT_Y8) selected_tex_source_uid = id; texture_update_time = glfwGetTime(); } } last_tex_sources = tex_sources; const auto top_bar_height = 60.f; ImGui::PushFont(font); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Button, header_window_bg); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, header_window_bg); ImGui::PushStyleColor(ImGuiCol_ButtonActive, header_window_bg); std::string label = "header of 3dviewer"; ImGui::GetWindowDrawList()->AddRectFilled({ stream_rect.x, stream_rect.y }, { stream_rect.x + stream_rect.w, stream_rect.y + top_bar_height }, ImColor(sensor_bg)); ImGui::SetCursorPos({ 0, 0 }); auto cursor = ImGui::GetCursorScreenPos(); float left = 5.f; float button_width = win.get_font_size() * 3.8f; const auto has_stream = tex_sources_str.size() && depth_sources_str.size(); // ------------ Pause Stream -------------- if (paused) { bool active = true; if (big_button(&active, win, 5 + left, 0, textual_icons::play, "Resume", false, has_stream, "Resume streaming")) { for(auto&& s : streams) if (s.second.dev) s.second.dev->resume(); paused = false; } } else { bool active = false; if (big_button(&active, win, 5 + left, 0, textual_icons::pause, "Pause", false, has_stream, "Pause streaming")) { for(auto&& s : streams) if (s.second.dev) s.second.dev->pause(); paused = true; } } left += button_width; // ------------ Reset Viewport --------------- bool default_view = (pos - float3{ 0.f, 0.f, -1.f }).length() < 0.001f && (target - float3{ 0.f, 0.f, 0.f }).length() < 0.001f; bool active = false; if (big_button(&active, win, 5 + left, 0, u8"\uf01e", "Reset", false, !default_view, "Reset 3D viewport to initial state")) { reset_camera(); } left += button_width; // ------------ Lock Mode --------------- if (synchronization_enable) { bool active = true; if (big_button(&active, win, 5 + left, 0, textual_icons::lock, "Unlock", false, support_non_syncronized_mode && has_stream, "Unlock texture data from pointcloud")) { synchronization_enable = false; } } else { bool active = false; if (big_button(&active, win, 5 + left, 0, textual_icons::unlock, "Lock", false, support_non_syncronized_mode && has_stream, "Lock pointcloud and texture data together")) { synchronization_enable = true; } } left += button_width + 10; ImGui::GetWindowDrawList()->AddLine({ cursor.x + left - 1, cursor.y + 5 }, { cursor.x + left - 1, cursor.y + top_bar_height - 5 }, ImColor(grey)); // ------------ Depth Selection -------------- const auto source_selection_popup = "Source Selection"; if (big_button(&select_3d_source, win, left, 0, u8"\uf1b2", "Source", true, has_stream, "List of available 3D data sources")) { ImGui::OpenPopup(source_selection_popup); } ImGui::PushStyleColor(ImGuiCol_Text, black); ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::SetNextWindowPos({ cursor.x + left + 5, cursor.y + 60 }); if (ImGui::BeginPopup(source_selection_popup)) { select_3d_source = true; i = 0; for (auto&& s : streams) { if (s.second.is_stream_visible() && s.second.texture->get_last_frame() && s.second.profile.stream_type() == RS2_STREAM_DEPTH) { std::string id = rsutils::string::from() << depth_sources_str[i] << "##DepthSource-" << i; bool selected = i == selected_depth_source; if (ImGui::MenuItem(id.c_str(), nullptr, &selected)) { if (selected) { auto stream_origin_iter = streams_origin.find(s.second.profile.unique_id()); if( stream_origin_iter != streams_origin.end() ) selected_depth_source_uid = stream_origin_iter->second; } } i++; } } ImGui::EndPopup(); } else { select_3d_source = false; } left += button_width + 20; // ------------ Texture Selection -------------- auto t = single_wave(float(glfwGetTime() - texture_update_time) * 2); ImVec4 text_color = light_grey * (1.f - t) + light_blue * t; const auto tex_selection_popup = "Tex Selection"; if (big_button(&select_tex_source, win, left, 0, u8"\uf576", "Texture", true, has_stream, "List of available texture sources", text_color)) { ImGui::OpenPopup(tex_selection_popup); } ImGui::SetNextWindowPos({ cursor.x + left + 5, cursor.y + 60 }); if (ImGui::BeginPopup(tex_selection_popup)) { select_tex_source = true; for (int i = 0; i < tex_sources_str.size(); i++) { std::string id = rsutils::string::from() << tex_sources_str[i] << "##TexSource-" << i; bool selected = i == selected_tex_source; if (ImGui::MenuItem(id.c_str(), nullptr, &selected)) { if (selected) { selected_tex_source_uid = tex_sources[i]; } } } ImGui::EndPopup(); } else { select_tex_source = false; } left += button_width + 20; // ------------ Shader Selection -------------- const auto shader_selection_popup = "Shading Selection"; if (big_button(&select_shader_source, win, left, 0, u8"\uf5aa", "Shading", true, true, "List of available shading modes")) { ImGui::OpenPopup(shader_selection_popup); } ImGui::SetNextWindowPos({ cursor.x + left + 5, cursor.y + 60 }); if (ImGui::BeginPopup(shader_selection_popup)) { select_shader_source = true; bool selected = selected_shader == shader_type::points; if (ImGui::MenuItem("Raw Point-Cloud", nullptr, &selected)) { if (selected) selected_shader = shader_type::points; } selected = selected_shader == shader_type::flat; if (ImGui::MenuItem("Flat-Shaded Mesh", nullptr, &selected)) { if (selected) selected_shader = shader_type::flat; } selected = selected_shader == shader_type::diffuse; if (ImGui::MenuItem("With Diffuse Lighting", nullptr, &selected, glsl_available)) { if (selected) selected_shader = shader_type::diffuse; } ImGui::EndPopup(); } else { select_shader_source = false; } left += button_width + 20; //----------------------------- ImGui::PopStyleColor(4); ImGui::GetWindowDrawList()->AddLine({ cursor.x + left - 1, cursor.y + 5 }, { cursor.x + left - 1, cursor.y + top_bar_height - 5 }, ImColor(grey)); // -------------------- Measure ---------------- std::string measure_tooltip = "Measure distance between points\nHold shift to connect more than 2 points and measure area"; if (!glsl_available) measure_tooltip += "\nRequires GLSL acceleration! \nEnable 2 checkboxes in Settings - Performance: \n- Use GLSL for Rendering \n- Use GLSL for Processing "; if (_measurements.is_enabled()) { bool active = true; if (big_button(&active, win, 5 + left, 0, textual_icons::measure, "Measure", false, glsl_available, measure_tooltip.c_str())) { _measurements.disable(); } } else { bool active = false; if (big_button(&active, win, 5 + left, 0, textual_icons::measure, "Measure", false, glsl_available, measure_tooltip.c_str())) { _measurements.enable(); } } left += button_width; // -------------------- Export ------------------ static config_file temp_cfg; set_export_popup(large_font, font, stream_rect, error_message, temp_cfg); active = false; if (big_button(&active, win, 5 + left, 0, textual_icons::floppy, "Export", false, last_points, "Export 3D model to 3rd-party application")) { _measurements.disable(); temp_cfg = config_file::instance(); ImGui::OpenPopup("Export"); } left += button_width; ImGui::PopStyleColor(5); ImGui::PopFont(); } void viewer_model::check_permissions() { #ifdef __linux__ if (directory_exists("/etc/udev/rules.d") || directory_exists("/lib/udev/rules.d/")) { const std::string udev_rules_man("/etc/udev/rules.d/99-realsense-libusb.rules"); const std::string udev_rules_deb("/lib/udev/rules.d/60-librealsense2-udev-rules.rules"); std::ifstream f_man(udev_rules_man); std::ifstream f_deb(udev_rules_deb); std::string message = "UDEV-Rules permissions configuration \n for RealSense devices.`\n" "Missing/outdated UDEV-Rules will cause 'Permissions Denied' errors\nunless the application is running under 'sudo' (not recommended)\n" "In case of Debians use: \n" "sudo apt-get upgrade/install librealsense2-udev-rules\n" "To manually install UDEV-Rules in terminal run:\n" "$ sudo cp ~/.99-realsense-libusb.rules /etc/udev/rules.d/99-realsense-libusb.rules && sudo udevadm control --reload-rules && udevadm trigger\n"; bool create_file = false; if(!(f_man.good() || f_deb.good())) { message = "RealSense UDEV-Rules are missing!\n" + message; auto n = not_model->add_notification({ message, RS2_LOG_SEVERITY_WARN, RS2_NOTIFICATION_CATEGORY_COUNT }); create_file = true; n->enable_complex_dismiss = true; n->delay_id = "missing-udev"; if (n->is_delayed()) n->dismiss(true); } else { std::ifstream f; std::string udev_fname; if(f_man.good()) { if (f_deb.good()) { std::string duplicates = "Multiple realsense udev-rules were found! :\n1:" + udev_rules_man + "\n2: " + udev_rules_deb+ "\nMake sure to remove redundancies!"; auto n = not_model->add_notification({ duplicates, RS2_LOG_SEVERITY_WARN, RS2_NOTIFICATION_CATEGORY_COUNT }); n->enable_complex_dismiss = true; n->delay_id = "multiple-udev"; if (n->is_delayed()) n->dismiss(true); } f.swap(f_man); udev_fname = udev_rules_man; create_file = true; } else { f.swap(f_deb); udev_fname = udev_rules_deb; } const std::string str((std::istreambuf_iterator(f)), std::istreambuf_iterator()); std::string tmp = realsense_udev_rules; tmp.erase(tmp.find_last_of("\n") + 1); const std::string udev = tmp; float udev_file_ver{0}, built_in_file_ver{0}; // The udev-rules file shall start with version token expressed as ##Version=xx.yy## std::regex udev_ver_regex("^##Version=(\\d+\\.\\d+)##"); std::smatch match; if (std::regex_search(udev.begin(), udev.end(), match, udev_ver_regex)) built_in_file_ver = std::stof(std::string(match[1])); if (std::regex_search(str.begin(), str.end(), match, udev_ver_regex)) udev_file_ver = std::stof(std::string(match[1])); if (built_in_file_ver > udev_file_ver) { std::stringstream s; s << "RealSense UDEV-Rules file:\n " << udev_fname <<"\n is not up-to date! Version " << built_in_file_ver << " can be applied\n"; auto n = not_model->add_notification({ s.str() + message, RS2_LOG_SEVERITY_WARN, RS2_NOTIFICATION_CATEGORY_COUNT }); n->enable_complex_dismiss = true; n->delay_id = "udev-version"; if (n->is_delayed()) n->dismiss(true); } } if (create_file) { std::string tmp_filename = rsutils::os::get_special_folder( rsutils::os::special_folder::app_data ) // ~/. + "99-realsense-libusb.rules"; std::ofstream out(tmp_filename.c_str()); out << realsense_udev_rules; out.close(); } } #endif } // Hide options from both the DQT and Viewer applications void viewer_model::hide_common_options() { _hidden_options.emplace(RS2_OPTION_STREAM_FILTER); _hidden_options.emplace(RS2_OPTION_STREAM_FORMAT_FILTER); _hidden_options.emplace(RS2_OPTION_STREAM_INDEX_FILTER); _hidden_options.emplace(RS2_OPTION_FRAMES_QUEUE_SIZE); _hidden_options.emplace(RS2_OPTION_SENSOR_MODE); _hidden_options.emplace(RS2_OPTION_NOISE_ESTIMATION); } void viewer_model::update_configuration() { rs2_error* e = nullptr; auto version = rs2_get_api_version(&e); if (e) rs2::error::handle(e); int saved_version = config_file::instance().get_or_default( configurations::viewer::sdk_version, 0); // Great the user once upon upgrading to a new version if (version > saved_version) { auto n = std::make_shared(version); not_model->add_notification(n); config_file::instance().set(configurations::viewer::sdk_version, version); } continue_with_current_fw = config_file::instance().get_or_default( configurations::viewer::continue_with_current_fw, false); is_3d_view = config_file::instance().get_or_default( configurations::viewer::is_3d_view, true); if (bool measurement_enabled = config_file::instance().get_or_default( configurations::viewer::is_measuring, false)) _measurements.enable(); _measurements.log_function = [this](std::string message) { not_model->add_log(message); }; _measurements.is_metric = [this]() { return metric_system; }; glsl_available = config_file::instance().get( configurations::performance::glsl_for_rendering); occlusion_invalidation = config_file::instance().get_or_default( configurations::performance::occlusion_invalidation, true); ground_truth_r = config_file::instance().get_or_default( configurations::viewer::ground_truth_r, 1200); selected_shader = (shader_type)config_file::instance().get_or_default( configurations::viewer::shading_mode, 2); #ifdef BUILD_EASYLOGGINGPP auto min_severity = (rs2_log_severity)config_file::instance().get_or_default( configurations::viewer::log_severity, 2); if( ! _disable_log_to_console ) { if( config_file::instance().get_or_default( configurations::viewer::log_to_console, false ) ) { rs2::log_to_console( min_severity ); } else { rs2::log_to_console( RS2_LOG_SEVERITY_NONE ); } } if (config_file::instance().get_or_default( configurations::viewer::log_to_file, false)) { std::string filename = config_file::instance().get( configurations::viewer::log_filename); rs2::log_to_file(min_severity, filename.c_str()); } #endif show_skybox = config_file::instance().get_or_default( configurations::performance::show_skybox, true); } viewer_model::viewer_model( context & ctx_, bool disable_log_to_console ) : ppf( *this ) , ctx( ctx_ ) , frameset_alloc( this ) , synchronization_enable( true ) , synchronization_enable_prev_state(true) , _support_ir_reflectivity( false ) , _disable_log_to_console( disable_log_to_console ) { syncer = std::make_shared(); updates = std::make_shared(); reset_camera(); rs2_error* e = nullptr; not_model->add_log( "librealsense version: " + api_version_to_string( rs2_get_api_version( &e ) ) + "\n" ); update_configuration(); check_permissions(); export_model exp_model = export_model::make_exporter("PLY", ".ply", "Polygon File Format (PLY)\0*.ply\0"); exporters.insert(std::pair(export_type::ply, exp_model)); hide_common_options(); // hide this options from both Viewer and DQT applications } void viewer_model::gc_streams() { std::lock_guard lock(streams_mutex); std::vector streams_to_remove; for (auto&& kvp : streams) { if (!kvp.second.is_stream_visible() && (!kvp.second.dev || (!kvp.second.dev->is_paused() && !kvp.second.dev->streaming))) { if (kvp.first == selected_depth_source_uid) { ppf.depth_stream_active = false; } streams_to_remove.push_back(kvp.first); } } for (auto&& i : streams_to_remove) { if(selected_depth_source_uid == i) { last_points = points(); selected_depth_source_uid = -1; } if (selected_tex_source_uid == i) { last_texture.reset(); selected_tex_source_uid = -1; } streams.erase(i); if(ppf.frames_queue.find(i) != ppf.frames_queue.end()) { ppf.frames_queue.erase(i); } } } bool rs2::viewer_model::is_option_skipped(rs2_option opt) const { return (_hidden_options.find(opt) != _hidden_options.end()); } void rs2::viewer_model::disable_measurements() { _measurements.disable(); } void rs2::viewer_model::show_popup(const ux_window& window, const popup& p) { auto font_dynamic = window.get_font(); ImGui_ScopePushFont(font_dynamic); ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 1); ImGui::SetNextWindowSize({520, 180}); ImGui::OpenPopup(p.header.c_str()); if (ImGui::BeginPopupModal(p.header.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::PushStyleColor(ImGuiCol_Button, transparent); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, transparent); ImGui::PushStyleColor(ImGuiCol_ButtonActive, transparent); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); p.custom_command(); ImGui::EndPopup(); } ImGui::PopStyleColor(3); ImGui::PopStyleVar(2); } void viewer_model::popup_if_error(const ux_window& window, std::string& message) { if (message == "") return; // The list of errors the user asked not to show again: static std::set errors_not_to_show; static bool dont_show_this_error = false; auto simplify_error_message = [](const std::string& s) { std::regex e("\\b(0x)([^ ,]*)"); return std::regex_replace(s, e, "address"); }; auto it = std::find_if(_active_popups.begin(), _active_popups.end(), [&](const popup& p) { return message == p.message; }); if (it != _active_popups.end()) return; auto simplified_error_message = simplify_error_message(message); if (errors_not_to_show.count(simplified_error_message)) { not_model->add_notification({ message, RS2_LOG_SEVERITY_ERROR, RS2_NOTIFICATION_CATEGORY_UNKNOWN_ERROR }); return; } std::string header = std::string(textual_icons::exclamation_triangle) + " Oops, something went wrong!"; auto custom_command = [&]() { auto msg = _active_popups.front().message; // Wrap the text to feet the error pop-up window std::string wrapped_msg; try { auto trimmed_msg = rsutils::string::trim_newlines(msg); wrapped_msg = utilities::imgui::wrap(trimmed_msg, 500); } catch (...) { wrapped_msg = msg; // Revert to original text on wrapping failure not_model->output.add_log( RS2_LOG_SEVERITY_WARN, __FILE__, __LINE__, "Wrapping of error message text failed!" ); } ImGui::Text("RealSense error calling:"); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, regular_blue); ImGui::InputTextMultiline("##error", const_cast(wrapped_msg.c_str()), msg.size() + 1, { 500,95 }, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly); ImGui::PopStyleColor(); //ImGui::SetCursorPos({ 10, 130 }); ImGui::PopStyleColor(5); if (ImGui::Button("OK", ImVec2(120, 0))) { if (dont_show_this_error) { errors_not_to_show.insert(simplify_error_message(msg)); } ImGui::CloseCurrentPopup(); _active_popups.erase(_active_popups.begin()); dont_show_this_error = false; } ImGui::SameLine(); ImGui::Checkbox("Don't show this error again", &dont_show_this_error); }; popup p = {header, message, custom_command }; _active_popups.push_back(p); } std::vector read_fw_file(std::string file_path) { std::vector rv; std::ifstream file(file_path, std::ios::in | std::ios::binary | std::ios::ate); if (file.is_open()) { rv.resize(file.tellg()); file.seekg(0, std::ios::beg); file.read((char*)rv.data(), rv.size()); file.close(); } return rv; } void rs2::viewer_model::popup_firmware_update_progress(const ux_window& window, const float progress) { std::string header = "Firmware update in progress"; std::stringstream message; message << std::endl << "Progress: " << (int)(progress * 100.0) << " [%]"; auto custom_command = [&]() { ImGui::SetCursorPos({ 10, 100 }); ImGui::PopStyleColor(5); ImGui::ProgressBar(progress, { 300 , 25 }, "Firmware update"); }; popup p = { header, message.str(), custom_command }; _active_popups.push_back(p); } void viewer_model::show_icon(ImFont* font_18, const char* label_str, const char* text, int x, int y, int id, const ImVec4& text_color, const std::string& tooltip) { ImGui_ScopePushFont(font_18); std::string label = rsutils::string::from() << label_str << id; ImGui::SetCursorScreenPos({(float)x, (float)y}); ImGui::PushStyleColor(ImGuiCol_Text, text_color); ImGui::Text("%s", text); ImGui::PopStyleColor(); if (ImGui::IsItemHovered() && tooltip != "") ImGui::SetTooltip("%s", tooltip.c_str()); } void viewer_model::show_paused_icon(ImFont* font_18, int x, int y, int id) { show_icon(font_18, "paused_icon", textual_icons::pause, x, y, id, white); } void viewer_model::show_recording_icon(ImFont* font_18, int x, int y, int id, float alpha_delta) { show_icon(font_18, "recording_icon", textual_icons::circle, x, y, id, from_rgba(255, 46, 54, static_cast(alpha_delta * 255))); } void viewer_model::show_no_stream_overlay(ImFont* font_18, int min_x, int min_y, int max_x, int max_y) { ImGui::PushFont(font_18); auto pos = ImGui::GetCursorScreenPos(); ImGui::SetCursorScreenPos({ (min_x + max_x) / 2.f - 150, (min_y + max_y) / 2.f - 20 }); ImGui::PushStyleColor(ImGuiCol_Text, sensor_header_light_blue); std::string text = rsutils::string::from() << "Nothing is streaming! Toggle " << textual_icons::toggle_off << " to start"; ImGui::Text("%s", text.c_str()); ImGui::PopStyleColor(); ImGui::SetCursorScreenPos(pos); ImGui::PopFont(); } void viewer_model::show_rendering_not_supported(ImFont* font_18, int min_x, int min_y, int max_x, int max_y, rs2_format format) { static rsutils::time::periodic_timer update_string(std::chrono::milliseconds(200)); static int counter = 0; static std::string to_print; auto pos = ImGui::GetCursorScreenPos(); ImGui::PushFont(font_18); ImGui::SetCursorScreenPos({ min_x + max_x / 2.f - 210, min_y + max_y / 2.f - 20 }); ImGui::PushStyleColor(ImGuiCol_Text, yellowish); std::string text = rsutils::string::from() << textual_icons::exclamation_triangle; ImGui::Text("%s", text.c_str()); ImGui::SetCursorScreenPos({ min_x + max_x / 2.f - 180, min_y + max_y / 2.f - 20 }); text = rsutils::string::from() << " The requested format " << format << " is not supported for rendering "; if (update_string) { to_print.clear(); for (int i = 0; i < text.size(); i++) to_print += text[(i + counter) % text.size()]; counter++; } ImGui::Text("%s", to_print.c_str()); ImGui::PopStyleColor(); ImGui::SetCursorScreenPos(pos); ImGui::PopFont(); } void viewer_model::show_no_device_overlay(ImFont* font_18, int x, int y) { auto pos = ImGui::GetCursorScreenPos(); ImGui::SetCursorScreenPos({ float(x), float(y) }); ImGui::PushFont(font_18); ImGui::PushStyleColor(ImGuiCol_Text, from_rgba(0x70, 0x8f, 0xa8, 0xff)); ImGui::Text("Connect a RealSense Camera\nor Add Source"); ImGui::PopStyleColor(); ImGui::PopFont(); ImGui::SetCursorScreenPos(pos); } // Generate streams layout, creates a grid-like layout with factor amount of columns std::map generate_layout(const rect& r, int top_bar_height, size_t factor, const std::set& active_streams, std::map& stream_index ) { std::map results; if (factor == 0) return results; // Calc the number of rows auto complement = ceil((float)active_streams.size() / factor); auto cell_width = static_cast(r.w / factor); auto cell_height = static_cast(r.h / complement); // using the active streams sorted acc to stream type and stream index // typical order will then be: depth, color, ir1, ir2, motion.... std::vector active_streams_ordered; for (auto&& active_stream : active_streams) { active_streams_ordered.push_back(active_stream); } std::sort(active_streams_ordered.begin(), active_streams_ordered.end(), [](const stream_model* sm1, const stream_model* sm2) { return (sm1->profile.stream_type() < sm2->profile.stream_type()) || ((sm1->profile.stream_type() == sm2->profile.stream_type()) && (sm1->profile.stream_index() < sm2->profile.stream_index())); }); auto it = active_streams_ordered.begin(); for (auto x = 0; x < factor; x++) { for (auto y = 0; y < complement; y++) { // There might be spare boxes at the end (3 streams in 2x2 array for example) if (it == active_streams_ordered.end()) break; rect rxy = { r.x + x * cell_width, r.y + y * cell_height + top_bar_height, cell_width, cell_height - top_bar_height }; // Generate box to display the stream in results[stream_index[*it]] = rxy.adjust_ratio((*it)->size); ++it; } } return results; } // Return the total display area of the layout // The bigger it is, the more details we can see float evaluate_layout(const std::map& l) { float res = 0.f; for (auto&& kvp : l) res += kvp.second.area(); return res; } std::map viewer_model::calc_layout(const rect& r) { const int top_bar_height = 32; std::set active_streams; std::map stream_index; for (auto&& stream : streams) { if (stream.second.is_stream_visible()) { active_streams.insert(&stream.second); stream_index[&stream.second] = stream.first; } } if (fullscreen) { if (active_streams.count(selected_stream) == 0) fullscreen = false; } std::map results; if (fullscreen) { results[stream_index[selected_stream]] = { r.x, r.y + top_bar_height, r.w, r.h - top_bar_height }; } else { // Go over all available fx(something) layouts for (size_t f = 1; f <= active_streams.size(); f++) { auto l = generate_layout(r, top_bar_height, f, active_streams, stream_index); // Keep the "best" layout in result if (evaluate_layout(l) > evaluate_layout(results)) results = l; } } return get_interpolated_layout(results); } rs2::frame viewer_model::handle_ready_frames(const rect& viewer_rect, ux_window& window, int devices, std::string& error_message) { std::shared_ptr texture_frame = nullptr; points p; frame f{}; gc_streams(); std::map last_frames; try { size_t index = 0; while (ppf.resulting_queue.poll_for_frame(&f) && ++index < ppf.resulting_queue_max_size) { // Open the frame-set and validate the incoming frame originated from one of the source streams // and save the frames on last_frames // if one of the streams is missing we will use the last frame arrived // point cloud is not a stream type yet it's a frame we want to display if (f.is()) { for (auto frame : f.as()) { auto profile_id = frame.get_profile().unique_id(); if (frame.is< points >() || streams.find(profile_id) != streams.end()) { last_frames[profile_id] = frame; continue; } auto stream_origin_iter = streams_origin.find(profile_id); if( (stream_origin_iter != streams_origin.end() && streams.find( stream_origin_iter->second ) != streams.end() )) { last_frames[ profile_id ] = frame; } } } else { auto profile_id = f.get_profile().unique_id(); if (f.is< points >() || streams.find(profile_id) != streams.end()) { last_frames[profile_id] = f; continue; } auto stream_origin_iter = streams_origin.find(profile_id); if ((stream_origin_iter != streams_origin.end() && streams.find(stream_origin_iter->second) != streams.end())) { last_frames[profile_id] = f; } } } for(auto&& f : last_frames) not_model->output.update_dashboards(f.second); for( auto && frame : last_frames ) { auto f = frame.second; if (f.is< points >()) // find and store the 3d points frame for later use { if (!paused) p = f.as< points >(); continue; } auto texture = upload_frame( std::move( f ) ); if( ( selected_tex_source_uid == -1 && f.get_profile().format() == RS2_FORMAT_Z16 ) || ( f.get_profile().format() != RS2_FORMAT_ANY && is_3d_texture_source( f ) ) ) { texture_frame = texture; } } } catch (const error& ex) { error_message = error_to_string(ex); } catch (const std::exception& ex) { error_message = ex.what(); } window.begin_viewport(); auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 5, 5 }); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::SetNextWindowPos({ viewer_rect.x, viewer_rect.y }); ImGui::SetNextWindowSize({ viewer_rect.w, viewer_rect.h }); ImGui::Begin("Viewport", nullptr, { viewer_rect.w, viewer_rect.h }, 0.f, flags); try { draw_viewport( viewer_rect, window, devices, error_message, texture_frame, p ); modal_notification_on = not_model->draw( window, static_cast< int >( window.width() ), static_cast< int >( window.height() ), error_message ); } catch( const error & e ) { error_message = error_to_string( e ); } catch( const std::exception & e ) { error_message = e.what(); } popup_if_error(window, error_message); if (!_active_popups.empty()) show_popup(window, _active_popups.front()); ImGui::End(); ImGui::PopStyleVar(2); error_message = ""; return f; } void viewer_model::reset_camera(float3 p) { target = { 0.0f, 0.0f, 0.0f }; pos = p; // initialize "up" to be tangent to the sphere! // up = cross(cross(look, world_up), look) { float3 look = { target.x - pos.x, target.y - pos.y, target.z - pos.z }; look = look.normalized(); float world_up[3] = { 0.0f, 1.0f, 0.0f }; float across[3] = { look.y * world_up[2] - look.z * world_up[1], look.z * world_up[0] - look.x * world_up[2], look.x * world_up[1] - look.y * world_up[0], }; up.x = across[1] * look.z - across[2] * look.y; up.y = across[2] * look.x - across[0] * look.z; up.z = across[0] * look.y - across[1] * look.x; float up_len = up.length(); up.x /= -up_len; up.y /= -up_len; up.z /= -up_len; } } void viewer_model::draw_color_ruler(const mouse_info& mouse, const stream_model& s_model, const rect& stream_rect, std::vector rgb_per_distance_vec, float ruler_length, const std::string& ruler_units) { if (rgb_per_distance_vec.empty() || (ruler_length <= 0.f)) return; ruler_length = std::ceil(ruler_length); std::sort(rgb_per_distance_vec.begin(), rgb_per_distance_vec.end(), [](const rgb_per_distance& a, const rgb_per_distance& b) { return a.depth_val < b.depth_val; }); const auto stream_height = stream_rect.y + stream_rect.h; const auto stream_width = stream_rect.x + stream_rect.w; static const auto ruler_distance_offset = 10; auto bottom_y_ruler = stream_height - ruler_distance_offset; if (s_model.texture->zoom_preview) { bottom_y_ruler = s_model.texture->curr_preview_rect.y - ruler_distance_offset; } static const auto top_y_offset = 50; auto top_y_ruler = stream_rect.y + top_y_offset; if (s_model.show_stream_details) { top_y_ruler = s_model.curr_info_rect.y + s_model.curr_info_rect.h + ruler_distance_offset; } static const auto left_x_colored_ruler_offset = 55; static const auto colored_ruler_width = 20; const auto left_x_colored_ruler = stream_width - left_x_colored_ruler_offset; const auto right_x_colored_ruler = stream_width - (left_x_colored_ruler_offset - colored_ruler_width); assert((bottom_y_ruler - top_y_ruler) != 0.f); const auto ratio = (bottom_y_ruler - top_y_ruler) / ruler_length; // Draw numbered ruler float y_ruler_val = top_y_ruler; static const auto numbered_ruler_width = 20.f; const auto right_x_numbered_ruler = right_x_colored_ruler + numbered_ruler_width; static const auto hovered_numbered_ruler_opac = 0.8f; static const auto unhovered_numbered_ruler_opac = 0.6f; float colored_ruler_opac = unhovered_numbered_ruler_opac; float numbered_ruler_background_opac = unhovered_numbered_ruler_opac; bool is_ruler_hovered = false; if (mouse.cursor.x >= left_x_colored_ruler && mouse.cursor.x <= right_x_numbered_ruler && mouse.cursor.y >= top_y_ruler && mouse.cursor.y <= bottom_y_ruler) is_ruler_hovered = true; if (is_ruler_hovered) { std::stringstream ss; auto relative_mouse_y = ImGui::GetMousePos().y - top_y_ruler; auto y = (bottom_y_ruler - top_y_ruler) - relative_mouse_y; ss << std::fixed << std::setprecision(2) << (y / ratio) << ruler_units; ImGui::SetTooltip("%s", ss.str().c_str()); colored_ruler_opac = 1.f; numbered_ruler_background_opac = hovered_numbered_ruler_opac; } // Draw a background to the numbered ruler glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.0, 0.0, 0.0, numbered_ruler_background_opac); glBegin(GL_POLYGON); glVertex2f(right_x_colored_ruler, top_y_ruler); glVertex2f(right_x_numbered_ruler , top_y_ruler); glVertex2f(right_x_numbered_ruler , bottom_y_ruler); glVertex2f(right_x_colored_ruler, bottom_y_ruler); glEnd(); const float x_ruler_val = right_x_colored_ruler + 4.0f; ImGui::SetCursorScreenPos({ x_ruler_val, y_ruler_val }); const auto font_size = ImGui::GetFontSize(); ImGui::TextUnformatted(std::to_string(static_cast(ruler_length)).c_str()); const auto skip_numbers = ((ruler_length / 10.f) - 1.f); auto to_skip = (skip_numbers < 0.f)?0.f: skip_numbers; for (int i = static_cast(ruler_length - 1); i > 0; --i) { y_ruler_val += ((bottom_y_ruler - top_y_ruler) / ruler_length); ImGui::SetCursorScreenPos({ x_ruler_val, y_ruler_val - font_size / 2 }); if (((to_skip--) > 0)) continue; ImGui::TextUnformatted(std::to_string(i).c_str()); to_skip = skip_numbers; } y_ruler_val += ((bottom_y_ruler - top_y_ruler) / ruler_length); ImGui::SetCursorScreenPos({ x_ruler_val, y_ruler_val - font_size }); ImGui::Text("0"); auto total_depth_scale = rgb_per_distance_vec.back().depth_val - rgb_per_distance_vec.front().depth_val; static const auto sensitivity_factor = 0.01f; auto sensitivity = sensitivity_factor * total_depth_scale; // Draw colored ruler auto last_y = bottom_y_ruler; auto last_depth_value = 0.f; size_t last_index = 0; for (size_t i = 1; i < rgb_per_distance_vec.size(); ++i) { auto curr_depth = rgb_per_distance_vec[i].depth_val; if ((((curr_depth - last_depth_value) < sensitivity) && (i != rgb_per_distance_vec.size() - 1))) continue; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_QUADS); glColor4f(rgb_per_distance_vec[last_index].rgb_val.r / 255.f, rgb_per_distance_vec[last_index].rgb_val.g / 255.f, rgb_per_distance_vec[last_index].rgb_val.b / 255.f, colored_ruler_opac); glVertex2f(left_x_colored_ruler, last_y); glVertex2f(right_x_colored_ruler, last_y); last_depth_value = curr_depth; last_index = i; auto y = bottom_y_ruler - ((rgb_per_distance_vec[i].depth_val) * ratio); if ((i == (rgb_per_distance_vec.size() - 1)) || (std::ceil(curr_depth) > ruler_length)) y = top_y_ruler; glColor4f(rgb_per_distance_vec[i].rgb_val.r / 255.f, rgb_per_distance_vec[i].rgb_val.g / 255.f, rgb_per_distance_vec[i].rgb_val.b / 255.f, colored_ruler_opac); glVertex2f(right_x_colored_ruler, y); glVertex2f(left_x_colored_ruler, y); last_y = y; glEnd(); } // Draw ruler border static const auto top_line_offset = 0.5f; static const auto right_line_offset = top_line_offset / 2; glColor4f(0.0, 0.0, 0.0, colored_ruler_opac); glBegin(GL_LINE_LOOP); glVertex2f(left_x_colored_ruler - top_line_offset, top_y_ruler - top_line_offset); glVertex2f(right_x_numbered_ruler + right_line_offset / 2, top_y_ruler - top_line_offset); glVertex2f(right_x_numbered_ruler + right_line_offset / 2, bottom_y_ruler + top_line_offset); glVertex2f(left_x_colored_ruler - top_line_offset, bottom_y_ruler + top_line_offset); glEnd(); } float viewer_model::calculate_ruler_max_distance(const std::vector& distances) const { assert(!distances.empty()); float mean = std::accumulate(distances.begin(), distances.end(), 0.0f) / distances.size(); float e = 0; float inverse = 1.f / distances.size(); for (auto elem : distances) { e += static_cast(pow(elem - mean, 2)); } auto standard_deviation = sqrt(inverse * e); static const auto length_jump = 4.f; return std::ceil((mean + 1.5f * standard_deviation) / length_jump) * length_jump; } void viewer_model::render_2d_view(const rect& view_rect, ux_window& win, int output_height, ImFont *font1, ImFont *font2, size_t dev_model_num, const mouse_info &mouse, std::string& error_message) { static rsutils::time::periodic_timer every_sec(std::chrono::seconds(1)); static bool icon_visible = false; if (every_sec) icon_visible = !icon_visible; float alpha = icon_visible ? 1.f : 0.2f; glViewport(0, 0, static_cast(win.framebuf_width()), static_cast(win.framebuf_height())); glLoadIdentity(); glOrtho(0, win.width(), win.height(), 0, -1, +1); auto layout = calc_layout(view_rect); if ((layout.size() == 0) && (dev_model_num > 0)) { show_no_stream_overlay(font2, static_cast(view_rect.x), static_cast(view_rect.y), static_cast(win.width()), static_cast(win.height() - output_height)); } for (auto &&kvp : layout) { auto&& view_rect = kvp.second; auto stream = kvp.first; auto&& stream_mv = streams[stream]; auto&& stream_size = stream_mv.size; auto stream_rect = view_rect.adjust_ratio(stream_size).grow(-3); stream_mv.show_frame(stream_rect, mouse, error_message); auto p = stream_mv.dev->dev.as(); float posX = stream_rect.x + 9; float posY = stream_rect.y - 26; if (!stream_mv.is_stream_alive()) { std::string message = rsutils::string::from() << textual_icons::exclamation_triangle << " No Frames Received!"; show_icon(font2, "warning_icon", message.c_str(), static_cast(stream_rect.center().x - 100), static_cast(stream_rect.center().y - 25), stream_mv.profile.unique_id(), blend(dark_red, alpha), "Did not receive frames from the platform within a reasonable time window,\nplease try reducing the FPS or the resolution"); } stream_mv.show_stream_header(font1, stream_rect, *this); stream_mv.show_stream_footer(font1, stream_rect, mouse, streams, *this); if (val_in_range(stream_mv.profile.format(), { RS2_FORMAT_RAW10 , RS2_FORMAT_RAW16, RS2_FORMAT_MJPEG })) { show_rendering_not_supported(font2, static_cast(stream_rect.x), static_cast(stream_rect.y), static_cast(stream_rect.w), static_cast(stream_rect.h), stream_mv.profile.format()); } if (stream_mv.dev->_is_being_recorded) { show_recording_icon(font2, static_cast(posX), static_cast(posY), stream_mv.profile.unique_id(), alpha); posX += 23; } if (stream_mv.dev->is_paused() || (p && p.current_status() == RS2_PLAYBACK_STATUS_PAUSED)) show_paused_icon(font2, static_cast(posX), static_cast(posY), stream_mv.profile.unique_id()); auto stream_type = stream_mv.profile.stream_type(); if (streams[stream].is_stream_visible()) switch (stream_type) { { case RS2_STREAM_GYRO: /* Fall Through */ case RS2_STREAM_ACCEL: { auto motion = streams[stream].texture->get_last_frame().as(); if (motion.get()) { auto axis = motion.get_motion_data(); stream_mv.show_stream_imu(font1, stream_rect, axis, mouse); } break; } case RS2_STREAM_POSE: { if (streams[stream].show_stream_details) { auto pose = streams[stream].texture->get_last_frame().as(); if (pose.get()) { auto pose_data = pose.get_pose_data(); if (stream_rect.contains(win.get_mouse().cursor)) stream_mv.show_stream_pose(font1, stream_rect, pose_data, stream_type, (*this).fullscreen, 30.0f, *this); } } break; } } } //switch( stream_mv.profile.stream_type() ) { static std::vector< std::pair< ImColor, bool > > colors = { { ImColor( 1.f, 1.f, 1.f, 1.f ), false }, // the default color { ImColor( 1.f, 0.f, 0.f, 1.f ), false }, { ImColor( 0.f, 1.f, 0.f, 1.f ), false }, { ImColor( 0.f, 0.f, 1.f, 1.f ), false }, { ImColor( 1.f, 0.f, 1.f, 1.f ), false }, { ImColor( 1.f, 1.f, 0.f, 1.f ), false }, }; typedef size_t ColorIdx; static std::map< size_t, ColorIdx > id2color; // Returns the color (from those pre-defined above) for the given object, based on its ID auto get_color = [&]( object_in_frame const & object ) -> ImColor { ColorIdx & color = id2color[object.id]; // Creates it with 0 as default if not already there if( color < int( colors.size() )) { if( color > 0 ) return colors[color].first; // Return an already-assigned color // Find the next available color size_t x = 0; for( auto & p : colors ) { bool & in_use = p.second; if( !in_use ) { in_use = true; color = x; return p.first; } ++x; } // No available color; use the default and mark the object so we don't do this again color = 100; } // If we're here it's because there're more objects than colors return colors[0].first; }; glColor3f( 1, 1, 1 ); auto p_objects = stream_mv.dev->detected_objects; std::lock_guard< std::mutex > lock( p_objects->mutex ); // Mark colors that are no longer in use for( auto it = id2color.begin(); it != id2color.end(); ) { size_t id = it->first; bool found = false; for( object_in_frame & object : *p_objects ) { if( object.id == id ) { found = true; break; } } if( !found ) { colors[it->second].second = false; // no longer in use auto it_to_erase = it++; id2color.erase( it_to_erase ); } else { ++it; } } for( object_in_frame & object : *p_objects ) { rect const & normalized_bbox = stream_mv.profile.stream_type() == RS2_STREAM_DEPTH ? object.normalized_depth_bbox : object.normalized_color_bbox; rect bbox = normalized_bbox.unnormalize( stream_rect ); bbox.grow( 10, 5 ); // Allow more text, and easier identification of the face float const max_depth = 2.f; float const min_depth = 0.8f; float const depth_range = max_depth - min_depth; float usable_depth = std::min( object.mean_depth, max_depth ); float a = 0.75f * (max_depth - usable_depth) / depth_range + 0.25f; // Don't draw text in boxes that are too small... auto h = bbox.h; ImGui::PushStyleColor( ImGuiCol_Text, ImColor( 1.f, 1.f, 1.f, a ) ); ImColor bg( dark_sensor_bg.x, dark_sensor_bg.y, dark_sensor_bg.z, dark_sensor_bg.w * a ); if( fabs(object.mean_depth) > 0.f ) { std::string str = rsutils::string::from() << std::setprecision( 2 ) << object.mean_depth << " m"; auto size = ImGui::CalcTextSize( str.c_str() ); if( size.y < h && size.x < bbox.w ) { ImGui::GetWindowDrawList()->AddRectFilled( { bbox.x + 1, bbox.y + 1 }, { bbox.x + size.x + 20, bbox.y + size.y + 6 }, bg ); ImGui::SetCursorScreenPos( { bbox.x + 10, bbox.y + 3 } ); ImGui::Text("%s", str.c_str() ); h -= size.y; } } if( ! object.name.empty() ) { auto size = ImGui::CalcTextSize( object.name.c_str() ); if( size.y < h && size.x < bbox.w ) { ImGui::GetWindowDrawList()->AddRectFilled( { bbox.x + bbox.w - size.x - 20, bbox.y + bbox.h - size.y - 6 }, { bbox.x + bbox.w - 1, bbox.y + bbox.h - 1 }, bg ); ImGui::SetCursorScreenPos( { bbox.x + bbox.w - size.x - 10, bbox.y + bbox.h - size.y - 4 } ); ImGui::Text("%s", object.name.c_str() ); h -= size.y; } } ImGui::PopStyleColor(); // The rectangle itself is always drawn, in the same color as the text auto frame_color = get_color( object ); glColor3f( a * frame_color.Value.x, a * frame_color.Value.y, a * frame_color.Value.z ); draw_rect( bbox ); } } glColor3f(header_window_bg.x, header_window_bg.y, header_window_bg.z); stream_rect.y -= 32; stream_rect.h += 32; stream_rect.w += 1; draw_rect(stream_rect); auto frame = streams[stream].texture->get_last_frame().as(); auto textured_frame = streams[stream].texture->get_last_frame(true).as(); if (streams[stream].show_map_ruler && frame && textured_frame && RS2_STREAM_DEPTH == stream_mv.profile.stream_type() && RS2_FORMAT_Z16 == stream_mv.profile.format()) { if(RS2_FORMAT_RGB8 == textured_frame.get_profile().format()) { static const std::string depth_units = "m"; float ruler_length = 0.f; auto depth_vid_profile = stream_mv.profile.as(); auto depth_width = depth_vid_profile.width(); auto depth_height = depth_vid_profile.height(); auto depth_data = static_cast(frame.get_data()); auto textured_depth_data = static_cast(textured_frame.get_data()); static const auto skip_pixels_factor = 30; std::vector rgb_per_distance_vec; std::vector distances; for (uint64_t i = 0; i < depth_height; i+= skip_pixels_factor) { for (uint64_t j = 0; j < depth_width; j+= skip_pixels_factor) { auto depth_index = i*depth_width + j; auto length = depth_data[depth_index] * stream_mv.dev->depth_units; if (length > 0.f) { auto textured_depth_index = depth_index * 3; auto r = textured_depth_data[textured_depth_index]; auto g = textured_depth_data[textured_depth_index + 1]; auto b = textured_depth_data[textured_depth_index + 2]; rgb_per_distance_vec.push_back({ length, { r, g, b } }); distances.push_back(length); } } } if (!distances.empty()) { ruler_length = calculate_ruler_max_distance(distances); draw_color_ruler(mouse, streams[stream], stream_rect, rgb_per_distance_vec, ruler_length, depth_units); } } } } } void viewer_model::render_3d_view(const rect& viewer_rect, ux_window& win, std::shared_ptr texture, rs2::points points) { auto top_bar_height = 60.f; if (points) { last_points = points; } if (texture) { last_texture = texture; } auto bottom_y = win.framebuf_height() - viewer_rect.y - viewer_rect.h; glViewport(static_cast(non_negative(viewer_rect.x)), static_cast(non_negative(bottom_y)), static_cast(non_negative(viewer_rect.w)), static_cast(non_negative(viewer_rect.h - top_bar_height))); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); gluPerspective(45, non_negative(viewer_rect.w / win.framebuf_height()), 0.001f, 100.0f); matrix4 perspective_mat; glGetFloatv(GL_PROJECTION_MATRIX, perspective_mat); glPopMatrix(); check_gl_error(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf((float*)perspective_mat.mat); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(view); matrix4 view_mat; memcpy(&view_mat, view, sizeof(matrix4)); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); if (show_skybox) _skybox.render(pos); glDepthMask(GL_TRUE); auto r1 = matrix4::identity(); auto r2 = matrix4::identity(); if (draw_plane && !paused) { glPushAttrib(GL_LINE_BIT); glLineWidth(2); glBegin(GL_LINES); if (is_valid(roi_rect)) { glColor4f(yellow.x, yellow.y, yellow.z, 1.f); auto rects = subdivide(roi_rect); for (auto&& r : rects) { for (int i = 0; i < 4; i++) { auto j = (i + 1) % 4; glVertex3f(r[i].x, r[i].y, r[i].z); glVertex3f(r[j].x, r[j].y, r[j].z); } } } glEnd(); glPopAttrib(); } auto x = static_cast(-M_PI / 2); float _rx[4][4] = { { 1 , 0, 0, 0 }, { 0, static_cast(cos(x)), static_cast(-sin(x)), 0 }, { 0, static_cast(sin(x)), static_cast(cos(x)), 0 }, { 0, 0, 0, 1 } }; static const double z = M_PI; static float _rz[4][4] = { { float(cos(z)), float(-sin(z)),0, 0 }, { float(sin(z)), float(cos(z)), 0, 0 }, { 0 , 0, 1, 0 }, { 0, 0, 0, 1 } }; { float tiles = 24; if (!metric_system) tiles *= 1.f / FEET_TO_METER; glTranslatef(0, 0, -1); glLineWidth(1); // Render "floor" grid glBegin(GL_LINES); auto T = tiles * 0.5f; if (!metric_system) T *= FEET_TO_METER; for (int i = 0; i <= ceil(tiles); i++) { float I = float(i); if (!metric_system) I *= FEET_TO_METER; if (i == tiles / 2) glColor4f(0.7f, 0.7f, 0.7f, 1.f); else glColor4f(0.4f, 0.4f, 0.4f, 1.f); glVertex3f(I - T, 1, -T); glVertex3f(I - T, 1, T); glVertex3f(-T, 1, I - T); glVertex3f(T, 1, I - T); } glEnd(); } check_gl_error(); if (!_pc_selected) { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(r1 * view_mat); texture_buffer::draw_axes(0.4f, 1); glPopMatrix(); } check_gl_error(); glColor4f(1.f, 1.f, 1.f, 1.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(r1 * view_mat); if (draw_frustrum && last_points) { glLineWidth(1.f); glBegin(GL_LINES); auto intrin = last_points.get_profile().as().get_intrinsics(); if (_pc_selected) glColor4f(light_blue.x, light_blue.y, light_blue.z, 0.5f); else glColor4f(sensor_bg.x, sensor_bg.y, sensor_bg.z, 0.5f); for (float d = 1; d < 6; d += 2) { auto get_point = [&](float x, float y) -> float3 { float point[3]; float pixel[2]{ x, y }; rs2_deproject_pixel_to_point(point, &intrin, pixel, d); glVertex3f(0.f, 0.f, 0.f); glVertex3fv(point); return{ point[0], point[1], point[2] }; }; auto top_left = get_point(0, 0); auto top_right = get_point(static_cast(intrin.width), 0); auto bottom_right = get_point(static_cast(intrin.width), static_cast(intrin.height)); auto bottom_left = get_point(0, static_cast(intrin.height)); glVertex3fv(&top_left.x); glVertex3fv(&top_right.x); glVertex3fv(&top_right.x); glVertex3fv(&bottom_right.x); glVertex3fv(&bottom_right.x); glVertex3fv(&bottom_left.x); glVertex3fv(&bottom_left.x); glVertex3fv(&top_left.x); } glEnd(); glColor4f(1.f, 1.f, 1.f, 1.f); } check_gl_error(); if (last_points && last_texture) { auto vf_profile = last_points.get_profile().as(); // Non-linear correspondence customized for non-flat surface exploration if (vf_profile.width() <= 0) throw std::runtime_error("Profile width must be greater than 0."); glPointSize(std::sqrt(viewer_rect.w / vf_profile.width())); auto tex = last_texture->get_gl_handle(); glBindTexture(GL_TEXTURE_2D, tex); glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texture_border_mode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texture_border_mode); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_FILLED, selected_shader != shader_type::points ? 1.f : 0.f); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_SHADED, selected_shader == shader_type::diffuse ? 1.f : 0.f); _pc_renderer.set_matrix(RS2_GL_MATRIX_CAMERA, r2 * view_mat); _pc_renderer.set_matrix(RS2_GL_MATRIX_PROJECTION, perspective_mat); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_SELECTED, _pc_selected ? 1.f : 0.f); auto viewport_rect = viewer_rect; auto cursor = win.get_mouse().cursor; auto scaled_cursor = cursor; scaled_cursor.x *= win.get_scale_factor(); scaled_cursor.y *= win.get_scale_factor(); if (viewport_rect.contains(scaled_cursor)) { _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_MOUSE_PICK, 1.f); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_SCALE_FACTOR, win.get_scale_factor()); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_MOUSE_X, cursor.x); _pc_renderer.set_option(gl::pointcloud_renderer::OPTION_MOUSE_Y, win.framebuf_height() / win.get_scale_factor() - cursor.y); } // Render Point-Cloud last_points.apply_filter(_pc_renderer); if (_pc_renderer.get_option(gl::pointcloud_renderer::OPTION_PICKED_ID) > 0.f) { float3 p { _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_PICKED_X), _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_PICKED_Y), _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_PICKED_Z), }; float3 normal { _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_NORMAL_X), _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_NORMAL_Y), _pc_renderer.get_option(gl::pointcloud_renderer::OPTION_NORMAL_Z), }; _measurements.mouse_pick(win, p, normal); // Adjust track-ball controller based on picked position // 1. Place target at the closest point to p, along (pos, target) interval // 2. When zooming-in, move camera target and position toward p if (!win.get_mouse().mouse_down[0]) { auto x1x2 = target - pos; auto x1x0 = p - pos; auto t = (x1x2 * x1x0) / (x1x2 * x1x2); auto p1 = pos + x1x2* t; if (t > 0) { // Don't adjust if pointcloud is behind us target = lerp(p1, target, 0.9f); if (win.get_mouse().mouse_wheel > 0) { pos = lerp(p, pos, 0.9f); target = lerp(p, target, 0.9f); } } } //try_select_pointcloud(win); } glDisable(GL_TEXTURE_2D); _cam_renderer.set_matrix(RS2_GL_MATRIX_CAMERA, r2 * view_mat); _cam_renderer.set_matrix(RS2_GL_MATRIX_PROJECTION, perspective_mat); if (streams.find(selected_depth_source_uid) != streams.end()) { auto source_frame = streams[selected_depth_source_uid].texture->get_last_frame(); if (source_frame) { glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); // _cam_renderer.set_option(gl::camera_renderer::OPTION_SELECTED, _pc_selected ? 1.f : 0.f); // if (viewer_rect.contains(cursor)) // { // _cam_renderer.set_option(gl::camera_renderer::OPTION_MOUSE_PICK, 1.f); // _cam_renderer.set_option(gl::camera_renderer::OPTION_MOUSE_X, cursor.x); // _cam_renderer.set_option(gl::camera_renderer::OPTION_MOUSE_Y, cursor.y); // } // only display camera when it does not occlude point cloud origin_occluded.add_value(_pc_renderer.get_option(gl::pointcloud_renderer::OPTION_ORIGIN_PICKED) <= 0.f); _cam_renderer.set_option(RS2_OPTION_FILTER_MAGNITUDE, powf(origin_occluded.get_stat(), 2.f)); source_frame.apply_filter(_cam_renderer); // if (_cam_renderer.get_option(gl::camera_renderer::OPTION_WAS_PICKED) > 0.f) // { // try_select_pointcloud(win); // } glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } } } check_gl_error(); _measurements.draw(win); glPopMatrix(); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glDisable(GL_DEPTH_TEST); if (ImGui::IsKeyPressed('R') || ImGui::IsKeyPressed('r')) { reset_camera(); } } void viewer_model::show_top_bar(ux_window& window, const rect& viewer_rect, const device_models_list& devices) { auto flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings; ImGui::SetNextWindowPos({ panel_width, 0 }); ImGui::SetNextWindowSize({ window.width() - panel_width, panel_y }); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::PushStyleColor(ImGuiCol_WindowBg, button_color); ImGui::Begin("Toolbar Panel", nullptr, flags); ImGui::PushFont(window.get_large_font()); ImGui::PushStyleColor(ImGuiCol_Border, black); int buttons = window.is_fullscreen() ? 4 : 3; ImGui::SetCursorPosX(window.width() - panel_width - panel_y * (buttons)); ImGui::PushStyleColor(ImGuiCol_Text, is_3d_view ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, is_3d_view ? light_grey : light_blue); if (ImGui::Button("2D", { panel_y, panel_y })) { is_3d_view = false; config_file::instance().set(configurations::viewer::is_3d_view, is_3d_view); } ImGui::PopStyleColor(2); ImGui::SameLine(); ImGui::SetCursorPosX(window.width() - panel_width - panel_y * (buttons - 1)); auto pos1 = ImGui::GetCursorScreenPos(); ImGui::PushStyleColor(ImGuiCol_Text, !is_3d_view ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, !is_3d_view ? light_grey : light_blue); if (ImGui::Button("3D", { panel_y,panel_y })) { is_3d_view = true; config_file::instance().set(configurations::viewer::is_3d_view, is_3d_view); update_3d_camera(window, viewer_rect, true); } ImGui::PopStyleColor(2); ImGui::SameLine(); ImGui::SetCursorPosX(window.width() - panel_width - panel_y * (buttons - 2)); static bool settings_open = false; ImGui::PushStyleColor(ImGuiCol_Text, !settings_open ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, !settings_open ? light_grey : light_blue); if (ImGui::Button(u8"\uf013", { panel_y,panel_y })) { ImGui::OpenPopup("More Options"); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "More Options..."); } if (window.is_fullscreen()) { ImGui::SameLine(); ImGui::SetCursorPosX(window.width() - panel_width - panel_y * (buttons - 4)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_color); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, light_red); if (ImGui::Button(textual_icons::exit, { panel_y,panel_y })) { exit(0); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Exit the App"); window.link_hovered(); } ImGui::PopStyleColor(3); } ImGui::PopFont(); ImGui::PushStyleColor(ImGuiCol_Text, black); ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5)); ImGui::PushFont(window.get_font()); const char* menu_items[] = { "Report Issue", "Intel Store", "Settings", "About" }; bool open_settings_popup = false; bool open_about_popup = false; ImGui::SetNextWindowPos({ window.width() - 130, panel_y }); auto separator_height = 2; auto menu_items_len = sizeof( menu_items ) / sizeof( menu_items[0] ); auto popup_height = ( ImGui::GetTextLineHeightWithSpacing() + 2 ) * menu_items_len + separator_height; ImVec2 popup_size = { 6.f * window.get_font_size(), popup_height }; ImGui::SetNextWindowSize( popup_size ); if (ImGui::BeginPopup("More Options")) { settings_open = true; if( ImGui::Selectable( menu_items[0] ) ) { open_issue(devices); } if( ImGui::Selectable( menu_items[1] ) ) { open_url("https://store.intelrealsense.com/"); } if( ImGui::Selectable( menu_items[2] ) ) { open_settings_popup = true; } ImGui::Separator(); if( ImGui::Selectable( menu_items[3] ) ) { open_about_popup = true; } ImGui::EndPopup(); } else { settings_open = false; } static config_file temp_cfg; static bool reload_required = false; static bool refresh_required = false; static bool refresh_updates = false; static int tab = 0; if (open_settings_popup) { _measurements.disable(); temp_cfg = config_file::instance(); ImGui::OpenPopup(menu_items[2]); reload_required = false; refresh_required = false; tab = config_file::instance().get_or_default(configurations::viewer::settings_tab, 0); } { float w = window.width() * 0.6f; float h = window.height() * 0.6f; float x0 = window.width() * 0.2f; float y0 = window.height() * 0.2f; ImGui::SetNextWindowPos({ x0, y0 }); ImGui::SetNextWindowSize({ w, h }); flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings; ImGui_ScopePushFont(window.get_font()); ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15, 15)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 1); if (ImGui::BeginPopupModal(menu_items[2], nullptr, flags)) { if (ImGui::IsWindowHovered()) window.set_hovered_over_input(); ImGui::SetCursorScreenPos({ (float)(x0 + w / 2 - 280), (float)(y0 + 27) }); ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg); ImGui::PushFont(window.get_large_font()); ImGui::PushStyleColor(ImGuiCol_Text, tab != 0 ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, tab != 0 ? light_grey : light_blue); if (ImGui::Button("Playback & Record", { 170, 30})) { tab = 0; config_file::instance().set(configurations::viewer::settings_tab, tab); temp_cfg.set(configurations::viewer::settings_tab, tab); } ImGui::PopStyleColor(2); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, tab != 1 ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, tab != 1 ? light_grey : light_blue); if (ImGui::Button("Performance", { 150, 30})) { tab = 1; config_file::instance().set(configurations::viewer::settings_tab, tab); temp_cfg.set(configurations::viewer::settings_tab, tab); } ImGui::PopStyleColor(2); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, tab != 2 ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, tab != 2 ? light_grey : light_blue); if (ImGui::Button("General", { 100, 30})) { tab = 2; config_file::instance().set(configurations::viewer::settings_tab, tab); temp_cfg.set(configurations::viewer::settings_tab, tab); } ImGui::PopStyleColor(2); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, tab != 3 ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, tab != 3 ? light_grey : light_blue); if (ImGui::Button("Updates", { 120, 30 })) { tab = 3; config_file::instance().set(configurations::viewer::settings_tab, tab); temp_cfg.set(configurations::viewer::settings_tab, tab); } ImGui::PopStyleColor(2); ImGui::PopFont(); ImGui::PopStyleColor(2); // button color ImGui::SetCursorScreenPos({ (float)(x0 + 15), (float)(y0 + 65) }); ImGui::Separator(); if (tab == 0) { int recording_setting = temp_cfg.get(configurations::record::file_save_mode); ImGui::Text("When starting a new recording:"); if (ImGui::RadioButton("Select filename automatically", recording_setting == 0)) { recording_setting = 0; temp_cfg.set(configurations::record::file_save_mode, recording_setting); } if (ImGui::RadioButton("Ask me every time", recording_setting == 1)) { recording_setting = 1; temp_cfg.set(configurations::record::file_save_mode, recording_setting); } ImGui::Text("Default recording folder: "); ImGui::SameLine(); static char path[256]; memset(path, 0, 256); std::string path_str = temp_cfg.get(configurations::record::default_path); memcpy(path, path_str.c_str(), std::min(255, (int)path_str.size())); if (ImGui::InputText("##default_record_path", path, 255)) { path_str = path; temp_cfg.set(configurations::record::default_path, path_str); } ImGui::Separator(); ImGui::Text("ROS-bag Compression:"); int recording_compression = temp_cfg.get(configurations::record::compression_mode); if (ImGui::RadioButton("Always Compress (might cause frame drops)", recording_compression == 0)) { recording_compression = 0; temp_cfg.set(configurations::record::compression_mode, recording_compression); } if (ImGui::RadioButton("Never Compress (larger .bag file size)", recording_compression == 1)) { recording_compression = 1; temp_cfg.set(configurations::record::compression_mode, recording_compression); } if (ImGui::RadioButton("Use device defaults", recording_compression == 2)) { recording_compression = 2; temp_cfg.set(configurations::record::compression_mode, recording_compression); } } if (tab == 1) { // Font oversample slider int font_samples = temp_cfg.get(configurations::performance::font_oversample); ImGui::Text("Font Samples: "); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Increased font samples produce nicer text, but require more GPU memory, sometimes resulting in boxes instead of font characters"); ImGui::SameLine(); ImGui::PushItemWidth(80); float slider_position_x = ImGui::GetCursorPosX(); if (ImGui::SliderInt("##font_samples", &font_samples, 1, 8)) { reload_required = true; temp_cfg.set(configurations::performance::font_oversample, font_samples); } ImGui::PopItemWidth(); // Font slider int font_size = temp_cfg.get( configurations::window::font_size ); ImGui::Text( "Font Size: " ); if( ImGui::IsItemHovered() ) ImGui::SetTooltip( "Viewer Font Size" ); ImGui::SameLine(); ImGui::PushItemWidth( 80 ); ImGui::SetCursorPosX( slider_position_x ); if( ImGui::SliderInt( "##font_size", &font_size, 16, 20 ) ) { reload_required = true; temp_cfg.set( configurations::window::font_size, font_size ); } ImGui::PopItemWidth(); #ifndef __APPLE__ // Not available at the moment on Mac bool gpu_rendering = temp_cfg.get(configurations::performance::glsl_for_rendering); if (ImGui::Checkbox("Use GLSL for Rendering", &gpu_rendering)) { refresh_required = true; temp_cfg.set(configurations::performance::glsl_for_rendering, gpu_rendering); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Using OpenGL 3 shaders is a widely supported way to boost rendering speeds on modern GPUs."); bool gpu_processing = temp_cfg.get(configurations::performance::glsl_for_processing); if (ImGui::Checkbox("Use GLSL for Processing", &gpu_processing)) { refresh_required = true; temp_cfg.set(configurations::performance::glsl_for_processing, gpu_processing); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Using OpenGL 3 shaders for depth data processing can reduce CPU utilisation."); if (gpu_processing && !gpu_rendering) { ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::Text(u8"\uf071 Using GLSL for processing but not for rendering can reduce CPU utilisation, but is likely to hurt overall performance!"); ImGui::PopStyleColor(); } #endif bool msaa = temp_cfg.get(configurations::performance::enable_msaa); if (ImGui::Checkbox("Enable Multisample Anti-Aliasing (MSAA)", &msaa)) { reload_required = true; temp_cfg.set(configurations::performance::enable_msaa, msaa); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("MSAA will improve the rendering quality of edges at expense of greater GPU memory utilisation."); if (msaa) { int samples = temp_cfg.get(configurations::performance::msaa_samples); ImGui::Text("MSAA Samples: "); ImGui::SameLine(); ImGui::PushItemWidth(160); if (ImGui::SliderInt("##samples", &samples, 2, 16)) { reload_required = true; temp_cfg.set(configurations::performance::msaa_samples, samples); } ImGui::PopItemWidth(); } bool show_fps = temp_cfg.get(configurations::performance::show_fps); if (ImGui::Checkbox("Show Application FPS (rendering FPS)", &show_fps)) { reload_required = true; temp_cfg.set(configurations::performance::show_fps, show_fps); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Show application refresh rate in window title\nThis rate is unrelated to camera FPS and measures application responsivness"); bool vsync = temp_cfg.get(configurations::performance::vsync); if (ImGui::Checkbox("Enable VSync", &vsync)) { reload_required = true; temp_cfg.set(configurations::performance::vsync, vsync); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Vertical sync will try to synchronize application framerate to the monitor refresh-rate (usually limiting the framerate to 60)"); bool fullscreen = temp_cfg.get(configurations::window::is_fullscreen); if (ImGui::Checkbox("Fullscreen (F8)", &fullscreen)) { reload_required = true; temp_cfg.set(configurations::window::is_fullscreen, fullscreen); } bool show_skybox = temp_cfg.get(configurations::performance::show_skybox); if (ImGui::Checkbox("Show Skybox in 3D View", &show_skybox)) { temp_cfg.set(configurations::performance::show_skybox, show_skybox); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("When enabled, this option provides background to the 3D view, instead of leaving it blank.\nThis is purely cosmetic"); bool enable_occlusion_invalidation = temp_cfg.get(configurations::performance::occlusion_invalidation); if (ImGui::Checkbox("Perform Occlusion Invalidation", &enable_occlusion_invalidation)) { temp_cfg.set(configurations::performance::occlusion_invalidation, enable_occlusion_invalidation); } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Occlusions are a natural side-effect of having multiple sensors\nWhen this option is enabled, the SDK will filter out occluded pixels"); } if( tab == 2 ) { ImGui::Text( "Units of Measurement: " ); ImGui::SameLine(); int metric_system = temp_cfg.get( configurations::viewer::metric_system ); std::vector unit_systems; unit_systems.push_back( "Imperial System" ); unit_systems.push_back( "Metric System" ); ImGui::PushItemWidth( 150 ); if( draw_combo_box( "##units_system", unit_systems, metric_system ) ) { temp_cfg.set( configurations::viewer::metric_system, metric_system ); } ImGui::PopItemWidth(); ImGui::Separator(); ImGui::Text( "librealsense has built-in logging capabilities." ); ImGui::Text( "Logs may contain API calls, timing of frames, OS error messages and file-system links, but no actual frame content." ); bool log_to_console = temp_cfg.get( configurations::viewer::log_to_console ); if( _disable_log_to_console ) ImGui::PushStyleVar( ImGuiStyleVar_Alpha, 0.6f ); if( ImGui::Checkbox( "Output librealsense log to console", &log_to_console ) ) temp_cfg.set( configurations::viewer::log_to_console, log_to_console ); if( _disable_log_to_console ) { ImGui::PopStyleVar(); if( ImGui::IsItemHovered() ) { ImGui::SetTooltip( "%s", "--debug was specified; this cannot be applied without a restart" ); } } bool log_to_file = temp_cfg.get(configurations::viewer::log_to_file); if (ImGui::Checkbox("Output librealsense log to file", &log_to_file)) { temp_cfg.set(configurations::viewer::log_to_file, log_to_file); } if (log_to_file) { ImGui::Text("Log file name:"); ImGui::SameLine(); static char logpath[256]; memset(logpath, 0, 256); std::string path_str = temp_cfg.get(configurations::viewer::log_filename); memcpy(logpath, path_str.c_str(), std::min(255, (int)path_str.size())); if (ImGui::InputText("##default_log_path", logpath, 255)) { path_str = logpath; temp_cfg.set(configurations::viewer::log_filename, path_str); } } if (log_to_console || log_to_file) { int new_severity = temp_cfg.get(configurations::viewer::log_severity); std::vector severities; for (int i = 0; i < RS2_LOG_SEVERITY_COUNT; i++) severities.push_back(rs2_log_severity_to_string((rs2_log_severity)i)); ImGui::Text("Minimal log severity:"); ImGui::SameLine(); ImGui::PushItemWidth(150); if (draw_combo_box("##log_severity", severities, new_severity)) { temp_cfg.set(configurations::viewer::log_severity, new_severity); } ImGui::PopItemWidth(); } ImGui::Separator(); { ImGui::Text("Commands.xml Path:"); ImGui::SameLine(); static char logpath[256]; memset(logpath, 0, 256); std::string path_str = temp_cfg.get(configurations::viewer::commands_xml); memcpy(logpath, path_str.c_str(), std::min(255, (int)path_str.size())); if (ImGui::InputText("##commands_xml_path", logpath, 255)) { path_str = logpath; temp_cfg.set(configurations::viewer::commands_xml, path_str); } } { ImGui::Text("HWLoggerEvents.xml Path:"); ImGui::SameLine(); static char logpath[256]; memset(logpath, 0, 256); std::string path_str = temp_cfg.get(configurations::viewer::hwlogger_xml); memcpy(logpath, path_str.c_str(), std::min(255, (int)path_str.size())); if (ImGui::InputText("##fw_log_xml_path", logpath, 255)) { path_str = logpath; temp_cfg.set(configurations::viewer::hwlogger_xml, path_str); } } ImGui::Separator(); ImGui::Text("RealSense tools settings capture the state of UI, and not of the hardware:"); if (ImGui::Button(" Restore Defaults ")) { reload_required = true; temp_cfg = config_file(); } ImGui::SameLine(); if (ImGui::Button(" Export Settings ")) { auto ret = file_dialog_open(save_file, "JavaScript Object Notation (JSON)\0*.json\0", NULL, NULL); if (ret) { try { std::string filename = ret; filename = rsutils::string::to_lower(filename); if (!ends_with(filename, ".json")) filename += ".json"; temp_cfg.save(filename.c_str()); } catch (...){} } } ImGui::SameLine(); if (ImGui::Button(" Import Settings ")) { auto ret = file_dialog_open(open_file, "JavaScript Object Notation (JSON)\0*.json\0", NULL, NULL); if (ret) { try { config_file file(ret); temp_cfg = file; reload_required = true; } catch (...){} } } } if (tab == 3) { bool recommend_fw_updates = temp_cfg.get(configurations::update::recommend_updates); if (ImGui::Checkbox("Recommend Bundled Firmware", &recommend_fw_updates)) { temp_cfg.set(configurations::update::recommend_updates, recommend_fw_updates); refresh_updates = true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "When firmware of the device is below the version bundled with this software release\nsuggest firmware update"); } #ifdef CHECK_FOR_UPDATES ImGui::Separator(); ImGui::Text("%s", "SW/FW Updates From Server:"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Select the server URL of the SW/FW updates information"); } ImGui::SameLine(); static bool official_url(temp_cfg.get(configurations::update::sw_updates_official_server)); static char custom_url[256] = { 0 }; static std::string url_str = (temp_cfg.get(configurations::update::sw_updates_url)); memcpy(custom_url, url_str.c_str(), std::min(255, (int)url_str.size())); if (ImGui::RadioButton("Official Server", official_url)) { official_url = true; temp_cfg.set(configurations::update::sw_updates_url, server_versions_db_url); temp_cfg.set(configurations::update::sw_updates_official_server, true); } ImGui::SameLine(); if (ImGui::RadioButton("Custom Server", !official_url)) { official_url = false; temp_cfg.set(configurations::update::sw_updates_official_server, false); // Load last saved custom URL url_str = custom_url; temp_cfg.set(configurations::update::sw_updates_url, url_str); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Add file:// prefix to use a local DB file "); } if (!official_url) { if (ImGui::InputText("##custom_server_url", custom_url, 255)) { url_str = custom_url; temp_cfg.set(configurations::update::sw_updates_url, url_str); } } #endif } ImGui::Separator(); ImGui::GetWindowDrawList()->AddRectFilled({ (float)x0, (float)(y0 + h - 60) }, { (float)(x0 + w), (float)(y0 + h) }, ImColor(sensor_bg)); ImGui::SetCursorScreenPos({ (float)(x0 + 15), (float)(y0 + h - 60) }); if (reload_required) { ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::Text(u8"\uf071 The application will be restarted in order for new settings to take effect"); ImGui::PopStyleColor(); } auto apply = [&](){ config_file::instance() = temp_cfg; window.on_reload_complete = [this](){ _skybox.reset(); }; if (reload_required) window.reload(); else if (refresh_required) window.refresh(); update_configuration(); if (refresh_updates) for (auto&& dev : devices) dev->refresh_notifications(*this); }; ImGui::SetCursorScreenPos({ (float)(x0 + w / 2 - 190), (float)(y0 + h - 30) }); if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); apply(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Save settings and close"); } ImGui::SameLine(); auto configs_same = temp_cfg == config_file::instance(); ImGui::PushStyleColor(ImGuiCol_Text, configs_same ? light_grey : light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, configs_same ? light_grey : light_blue); if (ImGui::Button("Apply", ImVec2(120, 0))) { apply(); } ImGui::PopStyleColor(2); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Save settings"); } ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", "Close window without saving any changes to the settings"); } ImGui::EndPopup(); } ImGui::PopStyleColor(3); ImGui::PopStyleVar(2); } if (open_about_popup) { _measurements.disable(); ImGui::OpenPopup(menu_items[3]); } { float w = 590.f + window.get_font_size() * 6.f; float h = 300.f + window.get_font_size(); float x0 = (window.width() - w) / 2.f; float y0 = (window.height() - h) / 2.f; ImGui::SetNextWindowPos({ x0, y0 }); ImGui::SetNextWindowSize({ w, h }); flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings; ImGui_ScopePushFont(window.get_font()); ImGui::PushStyleColor(ImGuiCol_PopupBg, sensor_bg); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_Text, light_grey); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15, 15)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 1); if (ImGui::BeginPopupModal(menu_items[3], nullptr, flags)) { ImGui::Image((void*)(intptr_t)window.get_splash().get_gl_handle(), ImVec2(w - 30, 100), {0.20f, 0.38f}, {0.80f, 0.56f}); auto realsense_pos = ImGui::GetCursorPos(); ImGui::Text("Intel RealSense is a suite of depth-sensing and motion-tracking technologies."); ImGui::Text("librealsense is an open-source cross-platform SDK for working with RealSense devices."); ImGui::Text("Full source code is available at"); ImGui::SameLine(); auto github_pos = ImGui::GetCursorPos(); ImGui::Text("github.com/IntelRealSense/librealsense."); ImGui::Text("This software is distributed under the"); ImGui::SameLine(); auto license_pos = ImGui::GetCursorPos(); ImGui::Text("Apache License, Version 2.0."); ImGui::Text("RealSense is a registered trademark of Intel Corporation."); ImGui::Text("Copyright 2018 Intel Corporation."); if( RS2_API_BUILD_VERSION ) { ImGui::Text( "---" ); ImGui::Text( "Full version: " ); ImGui::SameLine(); ImGui::Text( RS2_API_FULL_VERSION_STR ); } ImGui::PushStyleColor(ImGuiCol_Button, sensor_bg); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, sensor_bg); ImGui::PushStyleColor(ImGuiCol_ButtonActive, sensor_bg); ImGui::PushStyleColor(ImGuiCol_Text, light_blue); ImGui::SetCursorPos({ realsense_pos.x - 4, realsense_pos.y - 3 }); hyperlink(window, "Intel RealSense", "https://realsense.intel.com/"); ImGui::SetCursorPos({ github_pos.x - 4, github_pos.y - 3 }); hyperlink(window, "github.com/IntelRealSense/librealsense", "https://github.com/IntelRealSense/librealsense/"); ImGui::SetCursorPos({ license_pos.x - 4, license_pos.y - 3 }); hyperlink(window, "Apache License, Version 2.0", "https://raw.githubusercontent.com/IntelRealSense/librealsense/master/LICENSE"); ImGui::PopStyleColor(4); ImGui::SetCursorScreenPos({ (float)(x0 + w / 2 - 60), (float)(y0 + h - 30) }); if (ImGui::Button("OK", ImVec2(120, 0))) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } ImGui::PopStyleColor(3); ImGui::PopStyleVar(2); } ImGui::PopStyleVar(); ImGui::PopStyleColor(7); ImGui::GetWindowDrawList()->AddLine({ pos1.x, pos1.y + 10 }, { pos1.x,pos1.y + panel_y - 10 }, ImColor(light_grey)); ImGui::SameLine(); ImGui::SetCursorPosX(window.width() - panel_width - panel_y); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleColor(ImGuiCol_PopupBg, almost_white_bg); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, light_blue); ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, white); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(5, 5)); ImGui::PopStyleVar(); ImGui::PopStyleColor(4); ImGui::PopFont(); ImGui::End(); ImGui::PopStyleColor(); ImGui::PopStyleVar(); } void viewer_model::update_3d_camera( ux_window& win, const rect& viewer_rect, bool force) { if (_measurements.manipulating()) return; if (win.get_hovered_over_input()) return; mouse_info& mouse = win.get_mouse(); auto now = std::chrono::high_resolution_clock::now(); static auto view_clock = std::chrono::high_resolution_clock::now(); auto sec_since_update = std::chrono::duration(now - view_clock).count() / 1000; view_clock = now; if (fixed_up) up = { 0.f, -1.f, 0.f }; auto dir = target - pos; auto x_axis = cross(dir, up); auto step = sec_since_update * 0.3f; if (ImGui::IsKeyPressed('w') || ImGui::IsKeyPressed('W')) { pos = pos + dir * step; target = target + dir * step; } if (ImGui::IsKeyPressed('s') || ImGui::IsKeyPressed('S')) { pos = pos - dir * step; target = target - dir * step; } if (ImGui::IsKeyPressed('d') || ImGui::IsKeyPressed('D')) { pos = pos + x_axis * step; target = target + x_axis * step; } if (ImGui::IsKeyPressed('a') || ImGui::IsKeyPressed('A')) { pos = pos - x_axis * step; target = target - x_axis * step; } if (viewer_rect.contains(mouse.cursor) || force) { // Whenever the mouse reaches the end of the window // and jump back to the start, it will add to the overflow // counter, so by adding the overflow value // we essentially emulate an infinite display auto cx = mouse.cursor.x + overflow.x; auto px = mouse.prev_cursor.x + overflow.x; auto cy = mouse.cursor.y + overflow.y; auto py = mouse.prev_cursor.y + overflow.y; auto dragging = ImGui::IsKeyDown(GLFW_KEY_LEFT_CONTROL); // Limit how much user mouse can jump between frames // This can work poorly when the app FPS is really terrible (< 10) // but overall works resonably well const auto MAX_MOUSE_JUMP = 200; const auto SCROLL_SLOW_MAX_TIME_MS = 50; const auto SCROLL_FAST_MIN_TIME_MS = 500; float zoom_per_tick = 0.2f; static auto prev_scroll_time = std::chrono::high_resolution_clock::now(); if (mouse.mouse_wheel != 0) { auto scroll_time = std::chrono::high_resolution_clock::now(); auto delta_scroll_time = std::chrono::duration_cast(scroll_time - prev_scroll_time).count(); prev_scroll_time = scroll_time; // scrolling impact is scaled up / down if the scrolling speed is fast / slow if (delta_scroll_time < SCROLL_SLOW_MAX_TIME_MS) zoom_per_tick *= 2.f; else if (delta_scroll_time > SCROLL_FAST_MIN_TIME_MS) zoom_per_tick *= 0.5f; } if (std::abs(cx - px) < MAX_MOUSE_JUMP && std::abs(cy - py) < MAX_MOUSE_JUMP ) arcball_camera_update( (float*)&pos, (float*)&target, (float*)&up, view, sec_since_update, zoom_per_tick, -0.1f, // pan speed 3.0f, // rotation multiplier static_cast(viewer_rect.w), static_cast(viewer_rect.h), // screen (window) size static_cast(px), static_cast(cx), static_cast(py), static_cast(cy), (ImGui::GetIO().MouseDown[2] || ImGui::GetIO().MouseDown[1] || (ImGui::GetIO().MouseDown[0] && dragging)) ? 1 : 0, (ImGui::GetIO().MouseDown[0] && !dragging) ? 1 : 0, mouse.mouse_wheel, 0); // If we are pressing mouse button // inside the 3D viewport // we should remember that we // are in the middle of manipulation // and adjust when mouse leaves the area if (ImGui::GetIO().MouseDown[0] || ImGui::GetIO().MouseDown[1] || ImGui::GetIO().MouseDown[2]) { manipulating = true; } } auto rect = viewer_rect; rect.w -= 10; // If we started manipulating the camera // and left the viewport if (manipulating && !rect.contains(mouse.cursor)) { // If mouse is no longer pressed, // abort the manipulation if (!ImGui::GetIO().MouseDown[0] && !ImGui::GetIO().MouseDown[1] && !ImGui::GetIO().MouseDown[2]) { manipulating = false; overflow = float2{ 0.f, 0.f }; } else { // Wrap-around the mouse in X direction auto startx = (mouse.cursor.x - rect.x); if (startx < 0) { overflow.x -= rect.w; startx += rect.w; } if (startx > rect.w) { overflow.x += rect.w; startx -= rect.w; } startx += rect.x; // Wrap-around the mouse in Y direction auto starty = (mouse.cursor.y - rect.y); if (starty < 0) { overflow.y -= rect.h; starty += rect.h; } if (starty > rect.h) { overflow.y += rect.h; starty -= rect.h; } starty += rect.y; // Set new cursor position glfwSetCursorPos(win, startx, starty); } } else overflow = float2{ 0.f, 0.f }; mouse.prev_cursor = mouse.cursor; } void viewer_model::begin_stream(std::shared_ptr d, rs2::stream_profile p) { // Starting post processing filter rendering thread ppf.start(); streams[p.unique_id()].begin_stream(d, p, *this); ppf.frames_queue.emplace(p.unique_id(), rs2::frame_queue(5)); } bool viewer_model::is_3d_texture_source(frame f) const { auto profile = f.get_profile().as(); auto index = profile.unique_id(); int mapped_index = -2; auto iter = streams_origin.find(index); if (iter != streams_origin.end()) mapped_index = iter->second; if (!is_rasterizeable(profile.format())) return false; if (index == selected_tex_source_uid || mapped_index == selected_tex_source_uid || selected_tex_source_uid == -1) return true; return false; } std::shared_ptr viewer_model::get_last_texture() { return last_texture; } std::vector rs2::viewer_model::get_frames(frame frame) { std::vector res; if (auto set = frame.as()) for (auto&& f : set) res.push_back(f); else res.push_back(frame); return res; } frame viewer_model::get_3d_depth_source(frame f) { auto frames = get_frames(f); for (auto&& f : frames) { if (is_3d_depth_source(f)) return f; } return nullptr; } frame rs2::viewer_model::get_3d_texture_source(frame f) { auto frames = get_frames(f); for (auto&& f : frames) { if (is_3d_texture_source(f)) return f; } return nullptr; } bool viewer_model::is_3d_depth_source(frame f) { auto index = f.get_profile().unique_id(); auto mapped_index = streams_origin[index]; if(index == selected_depth_source_uid || mapped_index == selected_depth_source_uid ||(selected_depth_source_uid == -1 && f.get_profile().stream_type() == RS2_STREAM_DEPTH)) return true; return false; } std::shared_ptr viewer_model::upload_frame(frame&& f) { if (f.get_profile().stream_type() == RS2_STREAM_DEPTH) ppf.depth_stream_active = true; auto index = f.get_profile().unique_id(); std::lock_guard lock(streams_mutex); auto stream_origin_iter = streams_origin.find(index); if ( stream_origin_iter != streams_origin.end() && streams.find( stream_origin_iter->second ) != streams.end()) return streams[stream_origin_iter->second].upload_frame(std::move(f)); else return nullptr; } void viewer_model::draw_viewport(const rect& viewer_rect, ux_window& window, int devices, std::string& error_message, std::shared_ptr texture, points points) { if (!modal_notification_on) updates->draw(not_model, window, error_message); static bool first = true; if (first) { update_3d_camera(window, viewer_rect, true); first = false; } if (!is_3d_view) { render_2d_view(viewer_rect, window, static_cast(get_output_height()), window.get_font(), window.get_large_font(), devices, window.get_mouse(), error_message); } else { if (paused) show_paused_icon(window.get_large_font(), static_cast(panel_width + 15), static_cast(panel_y + 15 + 60), 0); show_3dviewer_header(window, viewer_rect, paused, error_message); update_3d_camera(window, viewer_rect); _measurements.update_input(window, viewer_rect); rect window_size{ 0, 0, (float)window.width(), (float)window.height() }; rect fb_size{ 0, 0, (float)window.framebuf_width(), (float)window.framebuf_height() }; rect new_rect = viewer_rect.normalize(window_size).unnormalize(fb_size); render_3d_view(new_rect, window, texture, points); auto rect_copy = viewer_rect; rect_copy.y += 60; rect_copy.h -= 60; if (rect_copy.contains(window.get_mouse().cursor)) _measurements.show_tooltip(window); } if (ImGui::IsKeyPressed(' ')) { if (paused) { for (auto&& s : streams) { if (s.second.dev) { s.second.dev->resume(); if (auto p = s.second.dev->dev.as()) { p.resume(); } } } } else { for (auto&& s : streams) { if (s.second.dev) { s.second.dev->pause(); if (s.second.dev->dev.is()) { auto p = s.second.dev->dev.as(); p.pause(); } } } } paused = !paused; } } std::map viewer_model::get_interpolated_layout(const std::map& l) { using namespace std::chrono; auto now = high_resolution_clock::now(); if (l != _layout) // detect layout change { _transition_start_time = now; _old_layout = _layout; _layout = l; } //if (_old_layout.size() == 0 && l.size() == 1) return l; auto diff = now - _transition_start_time; auto ms = duration_cast(diff).count(); auto t = smoothstep(static_cast(ms), 0, 100); std::map results; for (auto&& kvp : l) { auto stream = kvp.first; if (_old_layout.find(stream) == _old_layout.end()) { _old_layout[stream] = _layout[stream].center(); } results[stream] = lerp( _old_layout[stream], t, _layout[stream] ); } return results; } void viewer_model::init_depth_uid(int& selected_depth_source, std::vector& depth_sources_str, std::vector& depth_sources) { int i = 0; for (auto&& s : streams) { if (s.second.is_stream_visible() && s.second.profile.stream_type() == RS2_STREAM_DEPTH) { auto stream_origin_iter = streams_origin.find(s.second.profile.unique_id()); if (selected_depth_source_uid == -1) { if (stream_origin_iter != streams_origin.end() && streams.find(stream_origin_iter->second) != streams.end()) { selected_depth_source_uid = stream_origin_iter->second; } } if (stream_origin_iter != streams_origin.end() && stream_origin_iter->second == selected_depth_source_uid) { selected_depth_source = i; } depth_sources.push_back(s.second.profile.unique_id()); auto dev_name = s.second.dev ? s.second.dev->dev.get_info(RS2_CAMERA_INFO_NAME) : "Unknown"; auto stream_name = rs2_stream_to_string(s.second.profile.stream_type()); depth_sources_str.push_back(rsutils::string::from() << dev_name << " " << stream_name); i++; } } } }