You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3405 lines
134 KiB
3405 lines
134 KiB
// 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 <opengl3.h>
|
|
|
|
#include <imgui_internal.h>
|
|
|
|
#define ARCBALL_CAMERA_IMPLEMENTATION
|
|
#include <third-party/arcball_camera.h>
|
|
|
|
#include <rsutils/os/special-folder.h>
|
|
#include <rsutils/string/trim-newlines.h>
|
|
#include <common/utilities/imgui/wrap.h>
|
|
|
|
#include <regex>
|
|
|
|
namespace rs2
|
|
{
|
|
template <typename T>
|
|
T non_negative(const T& input)
|
|
{
|
|
return std::max(static_cast<T>(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<rs2::frame> 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<rs2::filter> exporter;
|
|
if (tab == export_type::ply)
|
|
exporter = std::unique_ptr<rs2::filter>(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<float>(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<std::string> depth_sources_str;
|
|
std::vector<int> depth_sources;
|
|
init_depth_uid(selected_depth_source, depth_sources_str, depth_sources);
|
|
|
|
int selected_tex_source = 0;
|
|
std::vector<std::string> tex_sources_str;
|
|
std::vector<int> tex_sources;
|
|
std::vector<rs2::stream_profile> 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<char>(f)),
|
|
std::istreambuf_iterator<char>());
|
|
|
|
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_upgrade_model>(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<syncer_model>();
|
|
updates = std::make_shared<updates_model>();
|
|
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, export_model>(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<std::mutex> lock(streams_mutex);
|
|
std::vector<int> 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<std::string> 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<char*>(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<uint8_t> read_fw_file(std::string file_path)
|
|
{
|
|
std::vector<uint8_t> 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<uint8_t>(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<int, rect> generate_layout(const rect& r,
|
|
int top_bar_height, size_t factor,
|
|
const std::set<stream_model*>& active_streams,
|
|
std::map<stream_model*, int>& stream_index
|
|
)
|
|
{
|
|
std::map<int, rect> results;
|
|
if (factor == 0) return results;
|
|
|
|
// Calc the number of rows
|
|
auto complement = ceil((float)active_streams.size() / factor);
|
|
|
|
auto cell_width = static_cast<float>(r.w / factor);
|
|
auto cell_height = static_cast<float>(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<stream_model*> 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<int, rect>& l)
|
|
{
|
|
float res = 0.f;
|
|
for (auto&& kvp : l) res += kvp.second.area();
|
|
return res;
|
|
}
|
|
|
|
std::map<int, rect> viewer_model::calc_layout(const rect& r)
|
|
{
|
|
const int top_bar_height = 32;
|
|
|
|
std::set<stream_model*> active_streams;
|
|
std::map<stream_model*, int> 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<int, rect> 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_buffer> texture_frame = nullptr;
|
|
points p;
|
|
frame f{};
|
|
gc_streams();
|
|
|
|
std::map<int, frame> 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<rs2::frameset>())
|
|
{
|
|
for (auto frame : f.as<rs2::frameset>())
|
|
{
|
|
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> 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<int>(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<int>(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<float>& 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<float>(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<GLsizei>(win.framebuf_width()), static_cast<GLsizei>(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<int>(view_rect.x), static_cast<int>(view_rect.y), static_cast<int>(win.width()), static_cast<int>(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<playback>();
|
|
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<int>(stream_rect.center().x - 100),
|
|
static_cast<int>(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<int>(stream_rect.x), static_cast<int>(stream_rect.y), static_cast<int>(stream_rect.w),
|
|
static_cast<int>(stream_rect.h), stream_mv.profile.format());
|
|
}
|
|
|
|
if (stream_mv.dev->_is_being_recorded)
|
|
{
|
|
show_recording_icon(font2, static_cast<int>(posX), static_cast<int>(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<int>(posX), static_cast<int>(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<motion_frame>();
|
|
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<pose_frame>();
|
|
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<video_frame>();
|
|
auto textured_frame = streams[stream].texture->get_last_frame(true).as<video_frame>();
|
|
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<video_stream_profile>();
|
|
auto depth_width = depth_vid_profile.width();
|
|
auto depth_height = depth_vid_profile.height();
|
|
auto depth_data = static_cast<const uint16_t*>(frame.get_data());
|
|
auto textured_depth_data = static_cast<const uint8_t*>(textured_frame.get_data());
|
|
static const auto skip_pixels_factor = 30;
|
|
std::vector<rgb_per_distance> rgb_per_distance_vec;
|
|
std::vector<float> 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_buffer> 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<GLint>(non_negative(viewer_rect.x)), static_cast<GLint>(non_negative(bottom_y)),
|
|
static_cast<GLsizei>(non_negative(viewer_rect.w)), static_cast<GLsizei>(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<float>(-M_PI / 2);
|
|
float _rx[4][4] = {
|
|
{ 1 , 0, 0, 0 },
|
|
{ 0, static_cast<float>(cos(x)), static_cast<float>(-sin(x)), 0 },
|
|
{ 0, static_cast<float>(sin(x)), static_cast<float>(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<video_stream_profile>().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<float>(intrin.width), 0);
|
|
auto bottom_right = get_point(static_cast<float>(intrin.width), static_cast<float>(intrin.height));
|
|
auto bottom_left = get_point(0, static_cast<float>(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<video_stream_profile>();
|
|
// 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<std::string> 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<std::string> 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<float, std::milli>(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<std::chrono::milliseconds>(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<int>(viewer_rect.w), static_cast<int>(viewer_rect.h), // screen (window) size
|
|
static_cast<int>(px), static_cast<int>(cx),
|
|
static_cast<int>(py), static_cast<int>(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<subdevice_model> 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<video_stream_profile>();
|
|
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<texture_buffer> viewer_model::get_last_texture()
|
|
{
|
|
return last_texture;
|
|
}
|
|
|
|
std::vector<frame> rs2::viewer_model::get_frames(frame frame)
|
|
{
|
|
std::vector<rs2::frame> res;
|
|
|
|
if (auto set = frame.as<frameset>())
|
|
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<texture_buffer> 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<std::mutex> 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_buffer> 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<int>(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<int>(panel_width + 15), static_cast<int>(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<playback>())
|
|
{
|
|
p.resume();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto&& s : streams)
|
|
{
|
|
if (s.second.dev)
|
|
{
|
|
s.second.dev->pause();
|
|
if (s.second.dev->dev.is<playback>())
|
|
{
|
|
auto p = s.second.dev->dev.as<playback>();
|
|
p.pause();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
paused = !paused;
|
|
}
|
|
}
|
|
|
|
std::map<int, rect> viewer_model::get_interpolated_layout(const std::map<int, rect>& 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<milliseconds>(diff).count();
|
|
auto t = smoothstep(static_cast<float>(ms), 0, 100);
|
|
|
|
std::map<int, rect> 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<std::string>& depth_sources_str, std::vector<int>& 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++;
|
|
}
|
|
}
|
|
}
|
|
}
|