// ---------------------------------------------------------------------------- // - Open3D: www.open3d.org - // ---------------------------------------------------------------------------- // The MIT License (MIT) // // Copyright (c) 2018 www.open3d.org // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // ---------------------------------------------------------------------------- #include "Open3D/Visualization/Visualizer/GuiVisualizer.h" #include "Open3D/GUI/Application.h" #include "Open3D/GUI/Button.h" #include "Open3D/GUI/Checkbox.h" #include "Open3D/GUI/Color.h" #include "Open3D/GUI/ColorEdit.h" #include "Open3D/GUI/Combobox.h" #include "Open3D/GUI/Dialog.h" #include "Open3D/GUI/FileDialog.h" #include "Open3D/GUI/Label.h" #include "Open3D/GUI/Layout.h" #include "Open3D/GUI/SceneWidget.h" #include "Open3D/GUI/Slider.h" #include "Open3D/GUI/Theme.h" #include "Open3D/GUI/VectorEdit.h" #include "Open3D/Geometry/BoundingVolume.h" #include "Open3D/Geometry/Image.h" #include "Open3D/Geometry/PointCloud.h" #include "Open3D/Geometry/TriangleMesh.h" #include "Open3D/IO/ClassIO/FileFormatIO.h" #include "Open3D/IO/ClassIO/ImageIO.h" #include "Open3D/IO/ClassIO/PointCloudIO.h" #include "Open3D/IO/ClassIO/TriangleMeshIO.h" #include "Open3D/Open3DConfig.h" #include "Open3D/Utility/Console.h" #include "Open3D/Utility/FileSystem.h" #include "Open3D/Visualization/Rendering/Camera.h" #include "Open3D/Visualization/Rendering/Filament/FilamentResourceManager.h" #include "Open3D/Visualization/Rendering/RenderToBuffer.h" #include "Open3D/Visualization/Rendering/RendererStructs.h" #include "Open3D/Visualization/Rendering/Scene.h" #define LOAD_IN_NEW_WINDOW 0 namespace open3d { namespace visualization { namespace { std::shared_ptr CreateAboutDialog(gui::Window *window) { auto &theme = window->GetTheme(); auto dlg = std::make_shared("About"); auto title = std::make_shared( (std::string("Open3D ") + OPEN3D_VERSION).c_str()); auto text = std::make_shared( "The MIT License (MIT)\n" "Copyright (c) 2018 - 2020 www.open3d.org\n\n" "Permission is hereby granted, free of charge, to any person " "obtaining " "a copy of this software and associated documentation files (the " "\"Software\"), to deal in the Software without restriction, " "including " "without limitation the rights to use, copy, modify, merge, " "publish, " "distribute, sublicense, and/or sell copies of the Software, and " "to " "permit persons to whom the Software is furnished to do so, " "subject to " "the following conditions:\n\n" "The above copyright notice and this permission notice shall be " "included in all copies or substantial portions of the " "Software.\n\n" "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, " "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES " "OF " "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND " "NONINFRINGEMENT. " "IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR " "ANY " "CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF " "CONTRACT, " "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE " "SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."); auto ok = std::make_shared("OK"); ok->SetOnClicked([window]() { window->CloseDialog(); }); gui::Margins margins(theme.font_size); auto layout = std::make_shared(0, margins); layout->AddChild(gui::Horiz::MakeCentered(title)); layout->AddFixed(theme.font_size); layout->AddChild(text); layout->AddFixed(theme.font_size); layout->AddChild(gui::Horiz::MakeCentered(ok)); dlg->AddChild(layout); return dlg; } std::shared_ptr CreateHelpDisplay(gui::Window *window) { auto &theme = window->GetTheme(); gui::Margins margins(theme.font_size); auto layout = std::make_shared(2, 0, margins); layout->SetBackgroundColor(gui::Color(0, 0, 0, 0.5)); auto AddLabel = [layout](const char *text) { auto label = std::make_shared(text); label->SetTextColor(gui::Color(1, 1, 1)); layout->AddChild(label); }; auto AddRow = [layout, &AddLabel](const char *left, const char *right) { AddLabel(left); AddLabel(right); }; AddRow("Arcball mode", " "); AddRow("Left-drag", "Rotate camera"); AddRow("Shift + left-drag", "Forward/backward"); #if defined(__APPLE__) AddLabel("Cmd + left-drag"); #else AddLabel("Ctrl + left-drag"); #endif // __APPLE__ AddLabel("Pan camera"); #if defined(__APPLE__) AddLabel("Opt + left-drag (up/down) "); #else AddLabel("Win + left-drag (up/down) "); #endif // __APPLE__ AddLabel("Rotate around forward axis"); // GNOME3 uses Win/Meta as a shortcut to move windows around, so we // need another way to rotate around the forward axis. AddLabel("Ctrl + Shift + left-drag"); AddLabel("Rotate around forward axis"); #if defined(__APPLE__) AddLabel("Ctrl + left-drag"); #else AddLabel("Alt + left-drag"); #endif // __APPLE__ AddLabel("Rotate directional light"); AddRow("Right-drag", "Pan camera"); AddRow("Middle-drag", "Rotate directional light"); AddRow("Wheel", "Forward/backward"); AddRow("Shift + Wheel", "Change field of view"); AddRow("", ""); AddRow("Fly mode", " "); AddRow("Left-drag", "Rotate camera"); #if defined(__APPLE__) AddLabel("Opt + left-drag"); #else AddLabel("Win + left-drag"); #endif // __APPLE__ AddLabel("Rotate around forward axis"); AddRow("W", "Forward"); AddRow("S", "Backward"); AddRow("A", "Step left"); AddRow("D", "Step right"); AddRow("Q", "Step up"); AddRow("Z", "Step down"); AddRow("E", "Roll left"); AddRow("R", "Roll right"); AddRow("Up", "Look up"); AddRow("Down", "Look down"); AddRow("Left", "Look left"); AddRow("Right", "Look right"); return layout; } std::shared_ptr CreateCameraDisplay(gui::Window *window) { auto &theme = window->GetTheme(); gui::Margins margins(theme.font_size); auto layout = std::make_shared(2, 0, margins); layout->SetBackgroundColor(gui::Color(0, 0, 0, 0.5)); auto AddLabel = [layout](const char *text) { auto label = std::make_shared(text); label->SetTextColor(gui::Color(1, 1, 1)); layout->AddChild(label); }; auto AddRow = [layout, &AddLabel](const char *left, const char *right) { AddLabel(left); AddLabel(right); }; AddRow("Position:", "[0 0 0]"); AddRow("Forward:", "[0 0 0]"); AddRow("Left:", "[0 0 0]"); AddRow("Up:", "[0 0 0]"); return layout; } std::shared_ptr CreateContactDialog(gui::Window *window) { auto &theme = window->GetTheme(); auto em = theme.font_size; auto dlg = std::make_shared("Contact Us"); auto title = std::make_shared("Contact Us"); auto left_col = std::make_shared( "Web site:\n" "Code:\n" "Mailing list:\n" "Discord channel:"); auto right_col = std::make_shared( "http://www.open3d.org\n" "http://github.org/intel-isl/Open3D\n" "http://www.open3d.org/index.php/subscribe/\n" "https://discord.gg/D35BGvn"); auto ok = std::make_shared("OK"); ok->SetOnClicked([window]() { window->CloseDialog(); }); gui::Margins margins(em); auto layout = std::make_shared(0, margins); layout->AddChild(gui::Horiz::MakeCentered(title)); layout->AddFixed(em); auto columns = std::make_shared(em, gui::Margins()); columns->AddChild(left_col); columns->AddChild(right_col); layout->AddChild(columns); layout->AddFixed(em); layout->AddChild(gui::Horiz::MakeCentered(ok)); dlg->AddChild(layout); return dlg; } std::shared_ptr CreateAxes(double axis_length) { const double sphere_radius = 0.005 * axis_length; const double cyl_radius = 0.0025 * axis_length; const double cone_radius = 0.0075 * axis_length; const double cyl_height = 0.975 * axis_length; const double cone_height = 0.025 * axis_length; auto mesh_frame = geometry::TriangleMesh::CreateSphere(sphere_radius); mesh_frame->ComputeVertexNormals(); mesh_frame->PaintUniformColor(Eigen::Vector3d(0.5, 0.5, 0.5)); std::shared_ptr mesh_arrow; Eigen::Matrix4d transformation; mesh_arrow = geometry::TriangleMesh::CreateArrow(cyl_radius, cone_radius, cyl_height, cone_height); mesh_arrow->ComputeVertexNormals(); mesh_arrow->PaintUniformColor(Eigen::Vector3d(1.0, 0.0, 0.0)); transformation << 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1; mesh_arrow->Transform(transformation); *mesh_frame += *mesh_arrow; mesh_arrow = geometry::TriangleMesh::CreateArrow(cyl_radius, cone_radius, cyl_height, cone_height); mesh_arrow->ComputeVertexNormals(); mesh_arrow->PaintUniformColor(Eigen::Vector3d(0.0, 1.0, 0.0)); transformation << 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1; mesh_arrow->Transform(transformation); *mesh_frame += *mesh_arrow; mesh_arrow = geometry::TriangleMesh::CreateArrow(cyl_radius, cone_radius, cyl_height, cone_height); mesh_arrow->ComputeVertexNormals(); mesh_arrow->PaintUniformColor(Eigen::Vector3d(0.0, 0.0, 1.0)); transformation << 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; mesh_arrow->Transform(transformation); *mesh_frame += *mesh_arrow; // Add UVs because material shader for axes expects them mesh_frame->triangle_uvs_.resize(mesh_frame->triangles_.size() * 3, {0.0, 0.0}); return mesh_frame; } bool ColorArrayIsUniform(const std::vector &colors) { static const double e = 1.0 / 255.0; static const double SQ_EPSILON = Eigen::Vector3d(e, e, e).squaredNorm(); const auto &color = colors[0]; for (const auto &c : colors) { if ((color - c).squaredNorm() > SQ_EPSILON) { return false; } } return true; } bool PointCloudHasUniformColor(const geometry::PointCloud &pcd) { if (!pcd.HasColors()) { return true; } return ColorArrayIsUniform(pcd.colors_); }; bool MeshHasUniformColor(const geometry::MeshBase &mesh) { if (!mesh.HasVertexColors()) { return true; } return ColorArrayIsUniform(mesh.vertex_colors_); }; std::shared_ptr MakeSlider(const gui::Slider::Type type, const double min, const double max, const double value) { auto slider = std::make_shared(type); slider->SetLimits(min, max); slider->SetValue(value); return slider; } //---- class DrawTimeLabel : public gui::Label { using Super = Label; public: DrawTimeLabel(gui::Window *w) : Label("0.0 ms") { window_ = w; } gui::Size CalcPreferredSize(const gui::Theme &theme) const override { auto h = Super::CalcPreferredSize(theme).height; return gui::Size(theme.font_size * 5, h); } DrawResult Draw(const gui::DrawContext &context) override { char text[64]; // double ms = window_->GetLastFrameTimeSeconds() * 1000.0; double ms = 0.0; snprintf(text, sizeof(text) - 1, "%.1f ms", ms); SetText(text); return Super::Draw(context); } private: gui::Window *window_; }; //---- class SmallButton : public gui::Button { using Super = Button; public: explicit SmallButton(const char *title) : Button(title) {} gui::Size CalcPreferredSize(const gui::Theme &theme) const override { auto em = theme.font_size; auto size = Super::CalcPreferredSize(theme); return gui::Size(size.width - em, 1.2 * em); } }; //---- class SmallToggleButton : public SmallButton { using Super = SmallButton; public: explicit SmallToggleButton(const char *title) : SmallButton(title) { SetToggleable(true); } }; } // namespace struct LightingProfile { std::string name; double ibl_intensity; double sun_intensity; Eigen::Vector3f sun_dir; Eigen::Vector3f sun_color = {1.0f, 1.0f, 1.0f}; Scene::Transform ibl_rotation = Scene::Transform::Identity(); bool ibl_enabled = true; bool use_default_ibl = false; bool sun_enabled = true; }; static const std::string DEFAULT_IBL = "default"; static const std::string DEFAULT_MATERIAL_NAME = "Polished ceramic [default]"; static const std::string POINT_CLOUD_PROFILE_NAME = "Cloudy day (no direct sun)"; static const bool DEFAULT_SHOW_SKYBOX = false; static const bool DEFAULT_SHOW_AXES = false; static const std::vector g_lighting_profiles = { {.name = "Bright day with sun at +Y [default]", .ibl_intensity = 45000, .sun_intensity = 45000, .sun_dir = {0.577f, -0.577f, -0.577f}}, {.name = "Bright day with sun at -Y", .ibl_intensity = 45000, .sun_intensity = 45000, .sun_dir = {0.577f, 0.577f, 0.577f}, .sun_color = {1.0f, 1.0f, 1.0f}, .ibl_rotation = Scene::Transform( Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitX()))}, {.name = "Bright day with sun at +Z", .ibl_intensity = 45000, .sun_intensity = 45000, .sun_dir = {0.577f, 0.577f, -0.577f}}, {.name = "Less bright day with sun at +Y", .ibl_intensity = 35000, .sun_intensity = 50000, .sun_dir = {0.577f, -0.577f, -0.577f}}, {.name = "Less bright day with sun at -Y", .ibl_intensity = 35000, .sun_intensity = 50000, .sun_dir = {0.577f, 0.577f, 0.577f}, .sun_color = {1.0f, 1.0f, 1.0f}, .ibl_rotation = Scene::Transform( Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitX()))}, {.name = "Less bright day with sun at +Z", .ibl_intensity = 35000, .sun_intensity = 50000, .sun_dir = {0.577f, 0.577f, -0.577f}}, {.name = POINT_CLOUD_PROFILE_NAME, .ibl_intensity = 60000, .sun_intensity = 50000, .sun_dir = {0.577f, -0.577f, -0.577f}, .sun_color = {1.0f, 1.0f, 1.0f}, .ibl_rotation = Scene::Transform::Identity(), .ibl_enabled = true, .use_default_ibl = true, .sun_enabled = false}}; enum MenuId { FILE_OPEN, FILE_EXPORT_RGB, FILE_QUIT, SETTINGS_LIGHT_AND_MATERIALS, HELP_KEYS, HELP_CAMERA, HELP_ABOUT, HELP_CONTACT }; struct GuiVisualizer::Impl { std::vector geometry_handles_; std::shared_ptr scene_; std::shared_ptr help_keys_; std::shared_ptr help_camera_; struct LitMaterial { visualization::MaterialInstanceHandle handle; Eigen::Vector3f base_color = {0.9f, 0.9f, 0.9f}; float metallic = 0.f; float roughness = 0.7; float reflectance = 0.5f; float clear_coat = 0.2f; float clear_coat_roughness = 0.2f; float anisotropy = 0.f; float point_size = 5.f; }; struct UnlitMaterial { visualization::MaterialInstanceHandle handle; // The base color should NOT be {1, 1, 1}, because then the // model will be invisible against the default white background. Eigen::Vector3f base_color = {0.9f, 0.9f, 0.9f}; float point_size = 5.f; }; struct TextureMaps { TextureHandle albedo_map = FilamentResourceManager::kDefaultTexture; TextureHandle normal_map = FilamentResourceManager::kDefaultNormalMap; TextureHandle ambient_occlusion_map = FilamentResourceManager::kDefaultTexture; TextureHandle roughness_map = FilamentResourceManager::kDefaultTexture; TextureHandle metallic_map = FilamentResourceManager::kDefaultTexture; TextureHandle reflectance_map = FilamentResourceManager::kDefaultTexture; TextureHandle clear_coat_map = FilamentResourceManager::kDefaultTexture; TextureHandle clear_coat_roughness_map = FilamentResourceManager::kDefaultTexture; TextureHandle anisotropy_map = FilamentResourceManager::kDefaultTexture; }; std::map prefab_materials_ = { {DEFAULT_MATERIAL_NAME, {}}, {"Metal (rougher)", {visualization::MaterialInstanceHandle::kBad, {1.0f, 1.0f, 1.0f}, 1.0f, 0.5f, 0.9f, 0.0f, 0.0f, 0.0f, 3.0f}}, {"Metal (smoother)", {visualization::MaterialInstanceHandle::kBad, {1.0f, 1.0f, 1.0f}, 1.0f, 0.3f, 0.9f, 0.0f, 0.0f, 0.0f, 3.0f}}, {"Plastic", {visualization::MaterialInstanceHandle::kBad, {1.0f, 1.0f, 1.0f}, 0.0f, 0.5f, 0.5f, 0.5f, 0.2f, 0.0f, 3.0f}}, {"Glazed ceramic", {visualization::MaterialInstanceHandle::kBad, {1.0f, 1.0f, 1.0f}, 0.0f, 0.5f, 0.9f, 1.0f, 0.1f, 0.0f, 3.0f}}, {"Clay", {visualization::MaterialInstanceHandle::kBad, {0.7725f, 0.7725f, 0.7725f}, 0.0f, 1.0f, 0.5f, 0.1f, 0.287f, 0.0f, 3.0f}}, }; struct Materials { LitMaterial lit; UnlitMaterial unlit; TextureMaps maps; }; visualization::MaterialHandle lit_material_; visualization::MaterialHandle unlit_material_; struct Settings { visualization::IndirectLightHandle ibl; visualization::SkyboxHandle sky; visualization::LightHandle directional_light; visualization::GeometryHandle axes; std::shared_ptr wgt_base; std::shared_ptr wgt_show_axes; std::shared_ptr wgt_bg_color; std::shared_ptr wgt_mouse_arcball; std::shared_ptr wgt_mouse_fly; std::shared_ptr wgt_mouse_sun; std::shared_ptr wgt_mouse_ibl; std::shared_ptr wgt_mouse_model; std::shared_ptr wgt_lighting_profile; std::shared_ptr wgtAdvanced; std::shared_ptr wgt_ibl_enabled; std::shared_ptr wgt_sky_enabled; std::shared_ptr wgt_directional_enabled; std::shared_ptr wgt_ibls; std::shared_ptr wgt_ibl_intensity; std::shared_ptr wgt_sun_intensity; std::shared_ptr wgt_sun_dir; std::shared_ptr wgt_sun_color; enum MaterialType { LIT = 0, UNLIT, NORMAL_MAP, DEPTH, }; MaterialType selected_type = LIT; Materials current_materials; std::shared_ptr wgt_material_type; std::shared_ptr wgt_prefab_material; std::shared_ptr wgt_material_color; std::shared_ptr wgt_reset_material_color; std::shared_ptr wgt_point_size; bool user_has_changed_color = false; bool user_has_changed_lighting = false; void SetCustomProfile() { wgt_lighting_profile->SetSelectedIndex(g_lighting_profiles.size()); user_has_changed_lighting = true; } } settings_; void SetMaterialsToDefault(visualization::Renderer &renderer) { Materials defaults; if (settings_.user_has_changed_color) { defaults.unlit.base_color = settings_.current_materials.unlit.base_color; defaults.lit.base_color = settings_.current_materials.lit.base_color; } defaults.maps.albedo_map = FilamentResourceManager::kDefaultTexture; defaults.maps.normal_map = FilamentResourceManager::kDefaultNormalMap; defaults.maps.ambient_occlusion_map = FilamentResourceManager::kDefaultTexture; defaults.maps.roughness_map = FilamentResourceManager::kDefaultTexture; defaults.maps.metallic_map = FilamentResourceManager::kDefaultTexture; defaults.maps.reflectance_map = FilamentResourceManager::kDefaultTexture; defaults.maps.clear_coat_map = FilamentResourceManager::kDefaultTexture; defaults.maps.clear_coat_roughness_map = FilamentResourceManager::kDefaultTexture; defaults.maps.anisotropy_map = FilamentResourceManager::kDefaultTexture; settings_.current_materials = defaults; auto lit_handle = renderer.AddMaterialInstance(lit_material_); settings_.current_materials.lit.handle = renderer.ModifyMaterial(lit_handle) .SetColor("baseColor", defaults.lit.base_color) .SetParameter("baseRoughness", defaults.lit.roughness) .SetParameter("baseMetallic", defaults.lit.metallic) .SetParameter("reflectance", defaults.lit.reflectance) .SetParameter("clearCoat", defaults.lit.clear_coat) .SetParameter("clearCoatRoughness", defaults.lit.clear_coat_roughness) .SetParameter("anisotropy", defaults.lit.anisotropy) .SetParameter("pointSize", defaults.lit.point_size) .SetTexture("albedo", defaults.maps.albedo_map, TextureSamplerParameters::Pretty()) .SetTexture("normalMap", defaults.maps.normal_map, TextureSamplerParameters::Pretty()) .SetTexture("ambientOcclusionMap", defaults.maps.ambient_occlusion_map, TextureSamplerParameters::Pretty()) .SetTexture("roughnessMap", defaults.maps.roughness_map, TextureSamplerParameters::Pretty()) .SetTexture("metallicMap", defaults.maps.metallic_map, TextureSamplerParameters::Pretty()) .SetTexture("reflectanceMap", defaults.maps.reflectance_map, TextureSamplerParameters::Pretty()) .SetTexture("clearCoatMap", defaults.maps.clear_coat_map, TextureSamplerParameters::Pretty()) .SetTexture("clearCoatRoughnessMap", defaults.maps.clear_coat_roughness_map, TextureSamplerParameters::Pretty()) .SetTexture("anisotropyMap", defaults.maps.anisotropy_map, TextureSamplerParameters::Pretty()) .Finish(); auto unlit_handle = renderer.AddMaterialInstance(unlit_material_); settings_.current_materials.unlit.handle = renderer.ModifyMaterial(unlit_handle) .SetColor("baseColor", defaults.unlit.base_color) .SetParameter("pointSize", defaults.unlit.point_size) .SetTexture("albedo", defaults.maps.albedo_map, TextureSamplerParameters::Pretty()) .Finish(); if (settings_.wgt_prefab_material) { settings_.wgt_prefab_material->SetSelectedValue( DEFAULT_MATERIAL_NAME.c_str()); } if (settings_.wgt_material_color) { Eigen::Vector3f color = (settings_.selected_type == Settings::MaterialType::LIT ? defaults.lit.base_color : defaults.unlit.base_color); settings_.wgt_material_color->SetValue(color.x(), color.y(), color.z()); } } void SetMaterialsToCurrentSettings(visualization::Renderer &renderer, LitMaterial material, TextureMaps maps) { // Update the material settings settings_.current_materials.lit.base_color = material.base_color; settings_.current_materials.lit.roughness = material.roughness; settings_.current_materials.lit.metallic = material.metallic; settings_.current_materials.unlit.base_color = material.base_color; // Update maps settings_.current_materials.maps = maps; // update materials settings_.current_materials.lit.handle = renderer.ModifyMaterial(settings_.current_materials.lit.handle) .SetColor("baseColor", material.base_color) .SetParameter("baseRoughness", material.roughness) .SetParameter("baseMetallic", material.metallic) .SetTexture("albedo", maps.albedo_map, TextureSamplerParameters::Pretty()) .SetTexture("normalMap", maps.normal_map, TextureSamplerParameters::Pretty()) .SetTexture("ambientOcclusionMap", maps.ambient_occlusion_map, TextureSamplerParameters::Pretty()) .SetTexture("roughnessMap", maps.roughness_map, TextureSamplerParameters::Pretty()) .SetTexture("metallicMap", maps.metallic_map, TextureSamplerParameters::Pretty()) .SetTexture("reflectanceMap", maps.reflectance_map, TextureSamplerParameters::Pretty()) .SetTexture("clearCoatMap", maps.clear_coat_map, TextureSamplerParameters::Pretty()) .SetTexture("clearCoatRoughnessMap", maps.clear_coat_roughness_map, TextureSamplerParameters::Pretty()) .SetTexture("anisotropyMap", maps.anisotropy_map, TextureSamplerParameters::Pretty()) .Finish(); settings_.current_materials.unlit.handle = renderer.ModifyMaterial( settings_.current_materials.unlit.handle) .SetColor("baseColor", material.base_color) .SetTexture("albedo", maps.albedo_map, TextureSamplerParameters::Pretty()) .Finish(); } void SetMaterialType(Impl::Settings::MaterialType type) { using MaterialType = Impl::Settings::MaterialType; using ViewMode = visualization::View::Mode; auto render_scene = scene_->GetScene(); auto view = scene_->GetView(); settings_.selected_type = type; settings_.wgt_material_type->SetSelectedIndex(int(type)); bool is_lit = (type == MaterialType::LIT); settings_.wgt_prefab_material->SetEnabled(is_lit); switch (type) { case MaterialType::LIT: { view->SetMode(ViewMode::Color); for (const auto &handle : geometry_handles_) { auto mat = settings_.current_materials.lit.handle; render_scene->AssignMaterial(handle, mat); } settings_.wgt_material_color->SetEnabled(true); gui::Color color( settings_.current_materials.lit.base_color.x(), settings_.current_materials.lit.base_color.y(), settings_.current_materials.lit.base_color.z()); settings_.wgt_material_color->SetValue(color); settings_.wgt_reset_material_color->SetEnabled( settings_.user_has_changed_color); break; } case MaterialType::UNLIT: { view->SetMode(ViewMode::Color); for (const auto &handle : geometry_handles_) { auto mat = settings_.current_materials.unlit.handle; render_scene->AssignMaterial(handle, mat); } settings_.wgt_material_color->SetEnabled(true); gui::Color color( settings_.current_materials.unlit.base_color.x(), settings_.current_materials.unlit.base_color.y(), settings_.current_materials.unlit.base_color.z()); settings_.wgt_material_color->SetValue(color); settings_.wgt_reset_material_color->SetEnabled( settings_.user_has_changed_color); break; } case MaterialType::NORMAL_MAP: view->SetMode(ViewMode::Normals); settings_.wgt_material_color->SetEnabled(false); settings_.wgt_reset_material_color->SetEnabled(false); break; case MaterialType::DEPTH: view->SetMode(ViewMode::Depth); settings_.wgt_material_color->SetEnabled(false); settings_.wgt_reset_material_color->SetEnabled(false); break; } } visualization::MaterialInstanceHandle CreateUnlitMaterial( visualization::Renderer &renderer, visualization::MaterialInstanceHandle mat) { auto color = settings_.wgt_material_color->GetValue(); Eigen::Vector3f color3(color.GetRed(), color.GetGreen(), color.GetBlue()); float point_size = settings_.wgt_point_size->GetDoubleValue(); return renderer.ModifyMaterial(mat) .SetColor("baseColor", color3) .SetParameter("pointSize", point_size) .Finish(); } visualization::MaterialInstanceHandle CreateLitMaterial( visualization::Renderer &renderer, visualization::MaterialInstanceHandle mat, const LitMaterial &prefab) { Eigen::Vector3f color; if (settings_.user_has_changed_color) { auto c = settings_.wgt_material_color->GetValue(); color = Eigen::Vector3f(c.GetRed(), c.GetGreen(), c.GetBlue()); } else { color = prefab.base_color; } float point_size = settings_.wgt_point_size->GetDoubleValue(); return renderer.ModifyMaterial(mat) .SetColor("baseColor", color) .SetParameter("baseRoughness", prefab.roughness) .SetParameter("baseMetallic", prefab.metallic) .SetParameter("reflectance", prefab.reflectance) .SetParameter("clearCoat", prefab.clear_coat) .SetParameter("clearCoatRoughness", prefab.clear_coat_roughness) .SetParameter("anisotropy", prefab.anisotropy) .SetParameter("pointSize", point_size) .Finish(); } void SetMaterialByName(visualization::Renderer &renderer, const std::string &name) { auto prefab_it = prefab_materials_.find(name); if (prefab_it != prefab_materials_.end()) { auto &prefab = prefab_it->second; if (!settings_.user_has_changed_color) { settings_.current_materials.lit.base_color = prefab.base_color; settings_.wgt_material_color->SetValue(prefab.base_color.x(), prefab.base_color.y(), prefab.base_color.z()); } auto mat = settings_.current_materials.lit.handle; mat = this->CreateLitMaterial(renderer, mat, prefab); for (const auto &handle : geometry_handles_) { scene_->GetScene()->AssignMaterial(handle, mat); } settings_.current_materials.lit.handle = mat; } } void SetLightingProfile(visualization::Renderer &renderer, const std::string &name) { for (size_t i = 0; i < g_lighting_profiles.size(); ++i) { if (g_lighting_profiles[i].name == name) { SetLightingProfile(renderer, g_lighting_profiles[i]); settings_.wgt_lighting_profile->SetSelectedValue(name.c_str()); return; } } utility::LogWarning("Could not find lighting profile '{}'", name); } void SetLightingProfile(visualization::Renderer &renderer, const LightingProfile &profile) { auto *render_scene = scene_->GetScene(); if (profile.use_default_ibl) { this->SetIBL(renderer, nullptr); settings_.wgt_ibls->SetSelectedValue(DEFAULT_IBL.c_str()); } if (profile.ibl_enabled) { render_scene->SetIndirectLight(settings_.ibl); } else { render_scene->SetIndirectLight(IndirectLightHandle()); } render_scene->SetIndirectLightIntensity(profile.ibl_intensity); render_scene->SetIndirectLightRotation(profile.ibl_rotation); render_scene->SetSkybox(SkyboxHandle()); render_scene->SetEntityEnabled(settings_.directional_light, profile.sun_enabled); render_scene->SetLightIntensity(settings_.directional_light, profile.sun_intensity); render_scene->SetLightDirection(settings_.directional_light, profile.sun_dir); render_scene->SetLightColor(settings_.directional_light, profile.sun_color); settings_.wgt_ibl_enabled->SetChecked(profile.ibl_enabled); settings_.wgt_sky_enabled->SetChecked(false); settings_.wgt_directional_enabled->SetChecked(profile.sun_enabled); settings_.wgt_ibls->SetSelectedValue(DEFAULT_IBL.c_str()); settings_.wgt_ibl_intensity->SetValue(profile.ibl_intensity); settings_.wgt_sun_intensity->SetValue(profile.sun_intensity); settings_.wgt_sun_dir->SetValue(profile.sun_dir); settings_.wgt_sun_color->SetValue(gui::Color(profile.sun_color[0], profile.sun_color[1], profile.sun_color[2])); } bool SetIBL(visualization::Renderer &renderer, const char *path) { visualization::IndirectLightHandle new_ibl; std::string ibl_path; if (path) { new_ibl = renderer.AddIndirectLight(ResourceLoadRequest(path)); ibl_path = path; } else { ibl_path = std::string( gui::Application::GetInstance().GetResourcePath()) + "/" + DEFAULT_IBL + "_ibl.ktx"; new_ibl = renderer.AddIndirectLight( ResourceLoadRequest(ibl_path.c_str())); } if (new_ibl) { auto *render_scene = scene_->GetScene(); settings_.ibl = new_ibl; auto intensity = render_scene->GetIndirectLightIntensity(); render_scene->SetIndirectLight(new_ibl); render_scene->SetIndirectLightIntensity(intensity); auto skybox_path = std::string(ibl_path); if (skybox_path.find("_ibl.ktx") != std::string::npos) { skybox_path = skybox_path.substr(0, skybox_path.size() - 8); skybox_path += "_skybox.ktx"; settings_.sky = renderer.AddSkybox( ResourceLoadRequest(skybox_path.c_str())); if (!settings_.sky) { settings_.sky = renderer.AddSkybox( ResourceLoadRequest(ibl_path.c_str())); } bool is_on = settings_.wgt_sky_enabled->IsChecked(); if (is_on) { scene_->GetScene()->SetSkybox(settings_.sky); } scene_->SetSkyboxHandle(settings_.sky, is_on); } return true; } return false; } void SetMouseControls(gui::Window &window, gui::SceneWidget::Controls mode) { using Controls = gui::SceneWidget::Controls; scene_->SetViewControls(mode); window.SetFocusWidget(scene_.get()); settings_.wgt_mouse_arcball->SetOn(mode == Controls::ROTATE_OBJ); settings_.wgt_mouse_fly->SetOn(mode == Controls::FLY); settings_.wgt_mouse_model->SetOn(mode == Controls::ROTATE_MODEL); settings_.wgt_mouse_sun->SetOn(mode == Controls::ROTATE_SUN); settings_.wgt_mouse_ibl->SetOn(mode == Controls::ROTATE_IBL); } }; GuiVisualizer::GuiVisualizer( const std::vector> &geometries, const std::string &title, int width, int height, int left, int top) : gui::Window(title, left, top, width, height), impl_(new GuiVisualizer::Impl()) { auto &app = gui::Application::GetInstance(); auto &theme = GetTheme(); // Create menu if (!gui::Application::GetInstance().GetMenubar()) { auto file_menu = std::make_shared(); file_menu->AddItem("Open...", FILE_OPEN, gui::KEY_O); file_menu->AddItem("Export Current Image...", FILE_EXPORT_RGB); file_menu->AddSeparator(); #if WIN32 file_menu->AddItem("Exit", FILE_QUIT); #else file_menu->AddItem("Quit", FILE_QUIT, gui::KEY_Q); #endif auto help_menu = std::make_shared(); help_menu->AddItem("Show Controls", HELP_KEYS); help_menu->AddItem("Show Camera Info", HELP_CAMERA); help_menu->AddSeparator(); help_menu->AddItem("About", HELP_ABOUT); help_menu->AddItem("Contact", HELP_CONTACT); auto settings_menu = std::make_shared(); settings_menu->AddItem("Lighting & Materials", SETTINGS_LIGHT_AND_MATERIALS); settings_menu->SetChecked(SETTINGS_LIGHT_AND_MATERIALS, true); auto menu = std::make_shared(); menu->AddMenu("File", file_menu); menu->AddMenu("Settings", settings_menu); #if defined(__APPLE__) && GUI_USE_NATIVE_MENUS // macOS adds a special search item to menus named "Help", // so add a space to avoid that. menu->AddMenu("Help ", help_menu); #else menu->AddMenu("Help", help_menu); #endif gui::Application::GetInstance().SetMenubar(menu); } // Create scene auto scene_id = GetRenderer().CreateScene(); auto scene = std::make_shared( *GetRenderer().GetScene(scene_id)); auto render_scene = scene->GetScene(); impl_->scene_ = scene; scene->SetBackgroundColor(gui::Color(1.0, 1.0, 1.0)); // Create light const int default_lighting_profile_idx = 0; auto &lighting_profile = g_lighting_profiles[default_lighting_profile_idx]; visualization::LightDescription light_description; light_description.intensity = lighting_profile.sun_intensity; light_description.direction = lighting_profile.sun_dir; light_description.cast_shadows = true; light_description.custom_attributes["custom_type"] = "SUN"; impl_->settings_.directional_light = scene->GetScene()->AddLight(light_description); auto &settings = impl_->settings_; std::string resource_path = app.GetResourcePath(); auto ibl_path = resource_path + "/default_ibl.ktx"; settings.ibl = GetRenderer().AddIndirectLight( ResourceLoadRequest(ibl_path.data())); render_scene->SetIndirectLight(settings.ibl); render_scene->SetIndirectLightIntensity(lighting_profile.ibl_intensity); render_scene->SetIndirectLightRotation(lighting_profile.ibl_rotation); auto sky_path = resource_path + "/" + DEFAULT_IBL + "_skybox.ktx"; settings.sky = GetRenderer().AddSkybox(ResourceLoadRequest(sky_path.data())); scene->SetSkyboxHandle(settings.sky, DEFAULT_SHOW_SKYBOX); // Create materials auto lit_path = resource_path + "/defaultLit.filamat"; impl_->lit_material_ = GetRenderer().AddMaterial( visualization::ResourceLoadRequest(lit_path.data())); auto unlit_path = resource_path + "/defaultUnlit.filamat"; impl_->unlit_material_ = GetRenderer().AddMaterial( visualization::ResourceLoadRequest(unlit_path.data())); impl_->SetMaterialsToDefault(GetRenderer()); // Setup UI const auto em = theme.font_size; const int lm = std::ceil(0.5 * em); const int grid_spacing = std::ceil(0.25 * em); AddChild(scene); // Add settings widget const int separation_height = std::ceil(0.75 * em); // (we don't want as much left margin because the twisty arrow is the // only thing there, and visually it looks larger than the right.) const gui::Margins base_margins(0.5 * lm, lm, lm, lm); settings.wgt_base = std::make_shared(0, base_margins); gui::Margins indent(em, 0, 0, 0); auto view_ctrls = std::make_shared("View controls", 0, indent); // ... view manipulator buttons settings.wgt_mouse_arcball = std::make_shared("Arcball"); impl_->settings_.wgt_mouse_arcball->SetOn(true); settings.wgt_mouse_arcball->SetOnClicked([this]() { impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_OBJ); }); settings.wgt_mouse_fly = std::make_shared("Fly"); settings.wgt_mouse_fly->SetOnClicked([this]() { impl_->SetMouseControls(*this, gui::SceneWidget::Controls::FLY); }); settings.wgt_mouse_model = std::make_shared("Model"); settings.wgt_mouse_model->SetOnClicked([this]() { impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_MODEL); }); settings.wgt_mouse_sun = std::make_shared("Sun"); settings.wgt_mouse_sun->SetOnClicked([this]() { impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_SUN); }); settings.wgt_mouse_ibl = std::make_shared("Environment"); settings.wgt_mouse_ibl->SetOnClicked([this]() { impl_->SetMouseControls(*this, gui::SceneWidget::Controls::ROTATE_IBL); }); auto reset_camera = std::make_shared("Reset camera"); reset_camera->SetOnClicked([this]() { impl_->scene_->GoToCameraPreset(gui::SceneWidget::CameraPreset::PLUS_Z); }); auto camera_controls1 = std::make_shared(grid_spacing); camera_controls1->AddStretch(); camera_controls1->AddChild(settings.wgt_mouse_arcball); camera_controls1->AddChild(settings.wgt_mouse_fly); camera_controls1->AddChild(settings.wgt_mouse_model); camera_controls1->AddStretch(); auto camera_controls2 = std::make_shared(grid_spacing); camera_controls2->AddStretch(); camera_controls2->AddChild(settings.wgt_mouse_sun); camera_controls2->AddChild(settings.wgt_mouse_ibl); camera_controls2->AddStretch(); view_ctrls->AddChild(std::make_shared("Mouse Controls")); view_ctrls->AddChild(camera_controls1); view_ctrls->AddFixed(0.25 * em); view_ctrls->AddChild(camera_controls2); view_ctrls->AddFixed(separation_height); view_ctrls->AddChild(gui::Horiz::MakeCentered(reset_camera)); // ... background settings.wgt_sky_enabled = std::make_shared("Show skymap"); settings.wgt_sky_enabled->SetChecked(DEFAULT_SHOW_SKYBOX); settings.wgt_sky_enabled->SetOnChecked([this, render_scene](bool checked) { if (checked) { render_scene->SetSkybox(impl_->settings_.sky); } else { render_scene->SetSkybox(SkyboxHandle()); } impl_->scene_->SetSkyboxHandle(impl_->settings_.sky, checked); impl_->settings_.wgt_bg_color->SetEnabled(!checked); }); impl_->settings_.wgt_bg_color = std::make_shared(); impl_->settings_.wgt_bg_color->SetValue({1, 1, 1}); impl_->settings_.wgt_bg_color->SetOnValueChanged( [scene](const gui::Color &newColor) { scene->SetBackgroundColor(newColor); }); auto bg_layout = std::make_shared(2, grid_spacing); bg_layout->AddChild(std::make_shared("BG Color")); bg_layout->AddChild(impl_->settings_.wgt_bg_color); view_ctrls->AddFixed(separation_height); view_ctrls->AddChild(settings.wgt_sky_enabled); view_ctrls->AddFixed(0.25 * em); view_ctrls->AddChild(bg_layout); // ... show axes settings.wgt_show_axes = std::make_shared("Show axes"); settings.wgt_show_axes->SetChecked(DEFAULT_SHOW_AXES); settings.wgt_show_axes->SetOnChecked([this, render_scene](bool isChecked) { render_scene->SetEntityEnabled(this->impl_->settings_.axes, isChecked); }); view_ctrls->AddFixed(separation_height); view_ctrls->AddChild(settings.wgt_show_axes); // ... lighting profiles settings.wgt_lighting_profile = std::make_shared(); for (size_t i = 0; i < g_lighting_profiles.size(); ++i) { settings.wgt_lighting_profile->AddItem( g_lighting_profiles[i].name.c_str()); } settings.wgt_lighting_profile->AddItem("Custom"); settings.wgt_lighting_profile->SetSelectedIndex( default_lighting_profile_idx); settings.wgt_lighting_profile->SetOnValueChanged( [this](const char *, int index) { if (index < int(g_lighting_profiles.size())) { this->impl_->SetLightingProfile(this->GetRenderer(), g_lighting_profiles[index]); this->impl_->settings_.user_has_changed_lighting = true; } else { this->impl_->settings_.wgtAdvanced->SetIsOpen(true); this->SetNeedsLayout(); } }); auto profile_layout = std::make_shared(); profile_layout->AddChild(std::make_shared("Lighting profiles")); profile_layout->AddChild(settings.wgt_lighting_profile); view_ctrls->AddFixed(separation_height); view_ctrls->AddChild(profile_layout); settings.wgt_base->AddChild(view_ctrls); settings.wgt_base->AddFixed(separation_height); // ... advanced lighting settings.wgtAdvanced = std::make_shared( "Advanced lighting", 0, indent); settings.wgtAdvanced->SetIsOpen(false); settings.wgt_base->AddChild(settings.wgtAdvanced); // ....... lighting on/off settings.wgtAdvanced->AddChild( std::make_shared("Light sources")); auto checkboxes = std::make_shared(); settings.wgt_ibl_enabled = std::make_shared("HDR map"); settings.wgt_ibl_enabled->SetChecked(true); settings.wgt_ibl_enabled->SetOnChecked([this, render_scene](bool checked) { impl_->settings_.SetCustomProfile(); if (checked) { render_scene->SetIndirectLight(impl_->settings_.ibl); render_scene->SetIndirectLightIntensity( impl_->settings_.wgt_ibl_intensity->GetDoubleValue()); } else { render_scene->SetIndirectLight(IndirectLightHandle()); } this->impl_->settings_.user_has_changed_lighting = true; }); checkboxes->AddChild(settings.wgt_ibl_enabled); settings.wgt_directional_enabled = std::make_shared("Sun"); settings.wgt_directional_enabled->SetChecked(true); settings.wgt_directional_enabled->SetOnChecked( [this, render_scene](bool checked) { impl_->settings_.SetCustomProfile(); render_scene->SetEntityEnabled( impl_->settings_.directional_light, checked); }); checkboxes->AddChild(settings.wgt_directional_enabled); settings.wgtAdvanced->AddChild(checkboxes); settings.wgtAdvanced->AddFixed(separation_height); // ....... IBL settings.wgt_ibls = std::make_shared(); std::vector resource_files; utility::filesystem::ListFilesInDirectory(resource_path, resource_files); std::sort(resource_files.begin(), resource_files.end()); int n = 0; for (auto &f : resource_files) { if (f.find("_ibl.ktx") == f.size() - 8) { auto name = utility::filesystem::GetFileNameWithoutDirectory(f); name = name.substr(0, name.size() - 8); settings.wgt_ibls->AddItem(name.c_str()); if (name == DEFAULT_IBL) { settings.wgt_ibls->SetSelectedIndex(n); } n++; } } settings.wgt_ibls->AddItem("Custom KTX file..."); settings.wgt_ibls->SetOnValueChanged([this](const char *name, int) { std::string path = gui::Application::GetInstance().GetResourcePath(); path += std::string("/") + name + "_ibl.ktx"; if (!this->SetIBL(path.c_str())) { // must be the "Custom..." option auto dlg = std::make_shared( gui::FileDialog::Mode::OPEN, "Open HDR Map", GetTheme()); dlg->AddFilter(".ktx", "Khronos Texture (.ktx)"); dlg->SetOnCancel([this]() { this->CloseDialog(); }); dlg->SetOnDone([this](const char *path) { this->CloseDialog(); this->SetIBL(path); this->impl_->settings_.SetCustomProfile(); }); ShowDialog(dlg); } }); settings.wgt_ibl_intensity = MakeSlider(gui::Slider::INT, 0.0, 150000.0, lighting_profile.ibl_intensity); settings.wgt_ibl_intensity->SetOnValueChanged( [this, render_scene](double newValue) { render_scene->SetIndirectLightIntensity(newValue); this->impl_->settings_.SetCustomProfile(); }); auto ambient_layout = std::make_shared(2, grid_spacing); ambient_layout->AddChild(std::make_shared("HDR map")); ambient_layout->AddChild(settings.wgt_ibls); ambient_layout->AddChild(std::make_shared("Intensity")); ambient_layout->AddChild(settings.wgt_ibl_intensity); settings.wgtAdvanced->AddChild(std::make_shared("Environment")); settings.wgtAdvanced->AddChild(ambient_layout); settings.wgtAdvanced->AddFixed(separation_height); // ... directional light (sun) settings.wgt_sun_intensity = MakeSlider(gui::Slider::INT, 0.0, 500000.0, lighting_profile.sun_intensity); settings.wgt_sun_intensity->SetOnValueChanged( [this, render_scene](double new_value) { render_scene->SetLightIntensity( impl_->settings_.directional_light, new_value); this->impl_->settings_.SetCustomProfile(); }); auto SetSunDir = [this, render_scene](const Eigen::Vector3f &dir) { this->impl_->settings_.wgt_sun_dir->SetValue(dir); render_scene->SetLightDirection(impl_->settings_.directional_light, dir.normalized()); this->impl_->settings_.SetCustomProfile(); }; this->impl_->scene_->SelectDirectionalLight( settings.directional_light, [this](const Eigen::Vector3f &new_dir) { impl_->settings_.wgt_sun_dir->SetValue(new_dir); this->impl_->settings_.SetCustomProfile(); }); settings.wgt_sun_dir = std::make_shared(); settings.wgt_sun_dir->SetValue(light_description.direction); settings.wgt_sun_dir->SetOnValueChanged(SetSunDir); settings.wgt_sun_color = std::make_shared(); settings.wgt_sun_color->SetValue({1, 1, 1}); settings.wgt_sun_color->SetOnValueChanged( [this, render_scene](const gui::Color &new_color) { this->impl_->settings_.SetCustomProfile(); render_scene->SetLightColor( impl_->settings_.directional_light, {new_color.GetRed(), new_color.GetGreen(), new_color.GetBlue()}); }); auto sun_layout = std::make_shared(2, grid_spacing); sun_layout->AddChild(std::make_shared("Intensity")); sun_layout->AddChild(settings.wgt_sun_intensity); sun_layout->AddChild(std::make_shared("Direction")); sun_layout->AddChild(settings.wgt_sun_dir); sun_layout->AddChild(std::make_shared("Color")); sun_layout->AddChild(settings.wgt_sun_color); settings.wgtAdvanced->AddChild( std::make_shared("Sun (Directional light)")); settings.wgtAdvanced->AddChild(sun_layout); // materials settings auto materials = std::make_shared("Material settings", 0, indent); auto mat_grid = std::make_shared(2, grid_spacing); mat_grid->AddChild(std::make_shared("Type")); settings.wgt_material_type.reset( new gui::Combobox({"Lit", "Unlit", "Normal map", "Depth"})); settings.wgt_material_type->SetOnValueChanged([this](const char *, int selected_idx) { impl_->SetMaterialType(Impl::Settings::MaterialType(selected_idx)); }); mat_grid->AddChild(settings.wgt_material_type); settings.wgt_prefab_material = std::make_shared(); for (auto &prefab : impl_->prefab_materials_) { settings.wgt_prefab_material->AddItem(prefab.first.c_str()); } settings.wgt_prefab_material->SetSelectedValue( DEFAULT_MATERIAL_NAME.c_str()); settings.wgt_prefab_material->SetOnValueChanged( [this](const char *name, int) { auto &renderer = this->GetRenderer(); impl_->SetMaterialByName(renderer, name); }); mat_grid->AddChild(std::make_shared("Material")); mat_grid->AddChild(settings.wgt_prefab_material); settings.wgt_material_color = std::make_shared(); settings.wgt_material_color->SetOnValueChanged( [this, render_scene](const gui::Color &color) { auto &renderer = this->GetRenderer(); auto &settings = impl_->settings_; Eigen::Vector3f color3(color.GetRed(), color.GetGreen(), color.GetBlue()); if (settings.selected_type == Impl::Settings::LIT) { settings.current_materials.lit.base_color = color3; } else { settings.current_materials.unlit.base_color = color3; } settings.user_has_changed_color = true; settings.wgt_reset_material_color->SetEnabled(true); visualization::MaterialInstanceHandle mat; if (settings.selected_type == Impl::Settings::UNLIT) { mat = settings.current_materials.unlit.handle; } else { mat = settings.current_materials.lit.handle; } mat = renderer.ModifyMaterial(mat) .SetColor("baseColor", color3) .Finish(); for (const auto &handle : impl_->geometry_handles_) { render_scene->AssignMaterial(handle, mat); } }); settings.wgt_reset_material_color = std::make_shared("Reset"); settings.wgt_reset_material_color->SetEnabled( impl_->settings_.user_has_changed_color); settings.wgt_reset_material_color->SetOnClicked([this]() { auto &renderer = this->GetRenderer(); impl_->settings_.user_has_changed_color = false; impl_->settings_.wgt_reset_material_color->SetEnabled(false); impl_->SetMaterialByName( renderer, impl_->settings_.wgt_prefab_material->GetSelectedValue()); }); mat_grid->AddChild(std::make_shared("Color")); auto color_layout = std::make_shared(); color_layout->AddChild(settings.wgt_material_color); color_layout->AddFixed(0.25 * em); color_layout->AddChild(impl_->settings_.wgt_reset_material_color); mat_grid->AddChild(color_layout); mat_grid->AddChild(std::make_shared("Point size")); settings.wgt_point_size = MakeSlider(gui::Slider::INT, 1.0, 10.0, 3); settings.wgt_point_size->SetOnValueChanged([this](double value) { float size = float(value); impl_->settings_.current_materials.unlit.point_size = size; auto &renderer = GetRenderer(); renderer.ModifyMaterial(impl_->settings_.current_materials.lit.handle) .SetParameter("pointSize", size) .Finish(); renderer.ModifyMaterial(impl_->settings_.current_materials.unlit.handle) .SetParameter("pointSize", size) .Finish(); renderer.ModifyMaterial(FilamentResourceManager::kDepthMaterial) .SetParameter("pointSize", size) .Finish(); renderer.ModifyMaterial(FilamentResourceManager::kNormalsMaterial) .SetParameter("pointSize", size) .Finish(); }); mat_grid->AddChild(settings.wgt_point_size); materials->AddChild(mat_grid); settings.wgt_base->AddFixed(separation_height); settings.wgt_base->AddChild(materials); AddChild(settings.wgt_base); // Other items impl_->help_keys_ = CreateHelpDisplay(this); impl_->help_keys_->SetVisible(false); AddChild(impl_->help_keys_); impl_->help_camera_ = CreateCameraDisplay(this); impl_->help_camera_->SetVisible(false); AddChild(impl_->help_camera_); // Set the actual geometries SetGeometry(geometries); // also updates the camera } GuiVisualizer::~GuiVisualizer() {} void GuiVisualizer::SetTitle(const std::string &title) { Super::SetTitle(title.c_str()); } void GuiVisualizer::SetGeometry( const std::vector> &geometries) { const std::size_t MIN_POINT_CLOUD_POINTS_FOR_DECIMATION = 6000000; gui::SceneWidget::ModelDescription desc; auto *scene3d = impl_->scene_->GetScene(); if (impl_->settings_.axes) { scene3d->RemoveGeometry(impl_->settings_.axes); } for (auto &h : impl_->geometry_handles_) { scene3d->RemoveGeometry(h); } impl_->geometry_handles_.clear(); impl_->SetMaterialsToDefault(GetRenderer()); std::size_t num_point_clouds = 0; std::size_t num_point_cloud_points = 0; for (auto &g : geometries) { if (g->GetGeometryType() == geometry::Geometry::GeometryType::PointCloud) { num_point_clouds++; auto cloud = std::static_pointer_cast(g); num_point_cloud_points += cloud->points_.size(); } } geometry::AxisAlignedBoundingBox bounds; std::size_t num_unlit = 0; for (size_t i = 0; i < geometries.size(); ++i) { std::shared_ptr g = geometries[i]; Impl::Materials materials = impl_->settings_.current_materials; visualization::MaterialInstanceHandle selected_material; // If a point cloud or mesh has no vertex colors or a single uniform // color (usually white), then we want to display it normally, that is, // lit. But if the cloud/mesh has differing vertex colors, then we // assume that the vertex colors have the lighting value baked in // (for example, fountain.ply at http://qianyi.info/scenedata.html) switch (g->GetGeometryType()) { case geometry::Geometry::GeometryType::PointCloud: { auto pcd = std::static_pointer_cast(g); if (pcd->HasColors() && !PointCloudHasUniformColor(*pcd)) { selected_material = materials.unlit.handle; num_unlit += 1; } else { selected_material = materials.lit.handle; } } break; case geometry::Geometry::GeometryType::LineSet: { selected_material = materials.unlit.handle; num_unlit += 1; } break; case geometry::Geometry::GeometryType::TriangleMesh: { auto mesh = std::static_pointer_cast( g); bool albedo_only = true; if (mesh->HasMaterials()) { auto mesh_material = mesh->materials_.begin()->second; Impl::LitMaterial material; Impl::TextureMaps maps; material.base_color.x() = mesh_material.baseColor.r; material.base_color.y() = mesh_material.baseColor.g; material.base_color.z() = mesh_material.baseColor.b; material.roughness = mesh_material.baseRoughness; auto is_map_valid = [](std::shared_ptr map) -> bool { return map && map->HasData(); }; if (is_map_valid(mesh_material.albedo)) { maps.albedo_map = GetRenderer().AddTexture(mesh_material.albedo); } if (is_map_valid(mesh_material.normalMap)) { maps.normal_map = GetRenderer().AddTexture( mesh_material.normalMap); albedo_only = false; } if (is_map_valid(mesh_material.ambientOcclusion)) { maps.ambient_occlusion_map = GetRenderer().AddTexture( mesh_material.ambientOcclusion); albedo_only = false; } if (is_map_valid(mesh_material.roughness)) { maps.roughness_map = GetRenderer().AddTexture( mesh_material.roughness); albedo_only = false; } if (is_map_valid(mesh_material.metallic)) { material.metallic = 1.f; maps.metallic_map = GetRenderer().AddTexture( mesh_material.metallic); albedo_only = false; } else { material.metallic = 0.f; } if (is_map_valid(mesh_material.reflectance)) { maps.reflectance_map = GetRenderer().AddTexture( mesh_material.reflectance); albedo_only = false; } if (is_map_valid(mesh_material.clearCoat)) { maps.clear_coat_map = GetRenderer().AddTexture( mesh_material.clearCoat); albedo_only = false; } if (is_map_valid(mesh_material.clearCoatRoughness)) { maps.clear_coat_roughness_map = GetRenderer().AddTexture( mesh_material.clearCoatRoughness); albedo_only = false; } if (is_map_valid(mesh_material.anisotropy)) { maps.anisotropy_map = GetRenderer().AddTexture( mesh_material.anisotropy); albedo_only = false; } impl_->SetMaterialsToCurrentSettings(GetRenderer(), material, maps); } if ((mesh->HasVertexColors() && !MeshHasUniformColor(*mesh)) || (mesh->HasMaterials() && albedo_only)) { selected_material = materials.unlit.handle; num_unlit += 1; } else { selected_material = materials.lit.handle; } } break; default: utility::LogWarning("Geometry type {} not supported!", (int)g->GetGeometryType()); break; } auto g3 = std::static_pointer_cast(g); auto handle = scene3d->AddGeometry(*g3, selected_material); bounds += scene3d->GetEntityBoundingBox(handle); impl_->geometry_handles_.push_back(handle); if (g->GetGeometryType() == geometry::Geometry::GeometryType::PointCloud) { desc.point_clouds.push_back(handle); auto pcd = std::static_pointer_cast(g); if (num_point_cloud_points > MIN_POINT_CLOUD_POINTS_FOR_DECIMATION) { int sample_rate = num_point_cloud_points / (MIN_POINT_CLOUD_POINTS_FOR_DECIMATION / 2); auto small = pcd->UniformDownSample(sample_rate); handle = scene3d->AddGeometry(*small, selected_material); desc.fast_point_clouds.push_back(handle); impl_->geometry_handles_.push_back(handle); } } else { desc.meshes.push_back(handle); } } if (!geometries.empty()) { auto view_mode = impl_->scene_->GetView()->GetMode(); if (view_mode == visualization::View::Mode::Normals) { impl_->SetMaterialType(Impl::Settings::NORMAL_MAP); } else if (view_mode == visualization::View::Mode::Depth) { impl_->SetMaterialType(Impl::Settings::DEPTH); } else { if (num_unlit == geometries.size()) { impl_->SetMaterialType(Impl::Settings::UNLIT); } else { impl_->SetMaterialType(Impl::Settings::LIT); } } if (num_point_clouds == geometries.size() && !impl_->settings_.user_has_changed_lighting) { impl_->SetLightingProfile(GetRenderer(), POINT_CLOUD_PROFILE_NAME); } impl_->settings_.wgt_point_size->SetEnabled(num_point_clouds > 0); } // Add axes. Axes length should be the longer of the bounds extent // or 25% of the distance from the origin. The latter is necessary // so that the axis is big enough to be visible even if the object // is far from the origin. See caterpillar.ply from Tanks & Temples. auto axis_length = bounds.GetMaxExtent(); if (axis_length < 0.001) { // avoid div by zero errors in CreateAxes() axis_length = 1.0; } axis_length = std::max(axis_length, 0.25 * bounds.GetCenter().norm()); auto axes = CreateAxes(axis_length); impl_->settings_.axes = scene3d->AddGeometry(*axes); scene3d->SetGeometryShadows(impl_->settings_.axes, false, false); scene3d->SetEntityEnabled(impl_->settings_.axes, impl_->settings_.wgt_show_axes->IsChecked()); desc.axes = impl_->settings_.axes; impl_->scene_->SetModel(desc); impl_->scene_->SetupCamera(60.0, bounds, bounds.GetCenter().cast()); } void GuiVisualizer::Layout(const gui::Theme &theme) { auto r = GetContentRect(); const auto em = theme.font_size; impl_->scene_->SetFrame(r); // Draw help keys HUD in upper left const auto pref = impl_->help_keys_->CalcPreferredSize(theme); impl_->help_keys_->SetFrame(gui::Rect(0, r.y, pref.width, pref.height)); impl_->help_keys_->Layout(theme); // Draw camera HUD in lower left const auto prefcam = impl_->help_camera_->CalcPreferredSize(theme); impl_->help_camera_->SetFrame(gui::Rect(0, r.height + r.y - prefcam.height, prefcam.width, prefcam.height)); impl_->help_camera_->Layout(theme); // Settings in upper right const auto LIGHT_SETTINGS_WIDTH = 18 * em; auto light_settings_size = impl_->settings_.wgt_base->CalcPreferredSize(theme); gui::Rect lightSettingsRect(r.width - LIGHT_SETTINGS_WIDTH, r.y, LIGHT_SETTINGS_WIDTH, light_settings_size.height); impl_->settings_.wgt_base->SetFrame(lightSettingsRect); Super::Layout(theme); } bool GuiVisualizer::SetIBL(const char *path) { auto result = impl_->SetIBL(GetRenderer(), path); PostRedraw(); return result; } bool GuiVisualizer::LoadGeometry(const std::string &path) { auto geometry = std::shared_ptr(); auto geometry_type = io::ReadFileGeometryType(path); auto mesh = std::make_shared(); bool mesh_success = false; if (geometry_type & io::CONTAINS_TRIANGLES) { try { mesh_success = io::ReadTriangleMesh(path, *mesh); } catch (...) { mesh_success = false; } } if (mesh_success) { if (mesh->triangles_.size() == 0) { utility::LogWarning( "Contains 0 triangles, will read as point cloud"); mesh.reset(); } else { mesh->ComputeVertexNormals(); if (mesh->vertex_colors_.empty()) { mesh->PaintUniformColor({1, 1, 1}); } geometry = mesh; } // Make sure the mesh has texture coordinates if (!mesh->HasTriangleUvs()) { mesh->triangle_uvs_.resize(mesh->triangles_.size() * 3, {0.0, 0.0}); } } else { // LogError throws an exception, which we don't want, because this might // be a point cloud. utility::LogInfo("{} appears to be a point cloud", path.c_str()); mesh.reset(); } if (!geometry) { auto cloud = std::make_shared(); bool success = false; try { success = io::ReadPointCloud(path, *cloud); } catch (...) { success = false; } if (success) { utility::LogInfo("Successfully read {}", path.c_str()); if (!cloud->HasNormals()) { cloud->EstimateNormals(); } cloud->NormalizeNormals(); geometry = cloud; } else { utility::LogWarning("Failed to read points {}", path.c_str()); cloud.reset(); } } if (geometry) { SetGeometry({geometry}); } return (geometry != nullptr); } void GuiVisualizer::ExportCurrentImage(int width, int height, const std::string &path) { GetRenderer().RenderToImage( width, height, impl_->scene_->GetView(), impl_->scene_->GetScene(), [this, path](std::shared_ptr image) mutable { if (!io::WriteImage(path, *image)) { this->ShowMessageBox( "Error", (std::string("Could not write image to ") + path + ".") .c_str()); } }); } void GuiVisualizer::OnMenuItemSelected(gui::Menu::ItemId item_id) { auto menu_id = MenuId(item_id); switch (menu_id) { case FILE_OPEN: { auto dlg = std::make_shared( gui::FileDialog::Mode::OPEN, "Open Geometry", GetTheme()); dlg->AddFilter(".ply .stl .obj .off .gltf .glb", "Triangle mesh files (.ply, .stl, .obj, .off, " ".gltf, .glb)"); dlg->AddFilter(".xyz .xyzn .xyzrgb .ply .pcd .pts", "Point cloud files (.xyz, .xyzn, .xyzrgb, .ply, " ".pcd, .pts)"); dlg->AddFilter(".ply", "Polygon files (.ply)"); dlg->AddFilter(".stl", "Stereolithography files (.stl)"); dlg->AddFilter(".obj", "Wavefront OBJ files (.obj)"); dlg->AddFilter(".off", "Object file format (.off)"); dlg->AddFilter(".gltf", "OpenGL transfer files (.gltf)"); dlg->AddFilter(".glb", "OpenGL binary transfer files (.glb)"); dlg->AddFilter(".xyz", "ASCII point cloud files (.xyz)"); dlg->AddFilter(".xyzn", "ASCII point cloud with normals (.xyzn)"); dlg->AddFilter(".xyzrgb", "ASCII point cloud files with colors (.xyzrgb)"); dlg->AddFilter(".pcd", "Point Cloud Data files (.pcd)"); dlg->AddFilter(".pts", "3D Points files (.pts)"); dlg->AddFilter("", "All files"); dlg->SetOnCancel([this]() { this->CloseDialog(); }); dlg->SetOnDone([this](const char *path) { this->CloseDialog(); OnDragDropped(path); }); ShowDialog(dlg); break; } case FILE_EXPORT_RGB: { auto dlg = std::make_shared( gui::FileDialog::Mode::SAVE, "Save File", GetTheme()); dlg->AddFilter(".png", "PNG images (.png)"); dlg->AddFilter("", "All files"); dlg->SetOnCancel([this]() { this->CloseDialog(); }); dlg->SetOnDone([this](const char *path) { this->CloseDialog(); auto r = GetContentRect(); this->ExportCurrentImage(r.width, r.height, path); }); ShowDialog(dlg); break; } case FILE_QUIT: gui::Application::GetInstance().Quit(); break; case SETTINGS_LIGHT_AND_MATERIALS: { auto visibility = !impl_->settings_.wgt_base->IsVisible(); impl_->settings_.wgt_base->SetVisible(visibility); auto menubar = gui::Application::GetInstance().GetMenubar(); menubar->SetChecked(SETTINGS_LIGHT_AND_MATERIALS, visibility); // We need relayout because materials settings pos depends on light // settings visibility Layout(GetTheme()); break; } case HELP_KEYS: { bool is_visible = !impl_->help_keys_->IsVisible(); impl_->help_keys_->SetVisible(is_visible); auto menubar = gui::Application::GetInstance().GetMenubar(); menubar->SetChecked(HELP_KEYS, is_visible); break; } case HELP_CAMERA: { bool is_visible = !impl_->help_camera_->IsVisible(); impl_->help_camera_->SetVisible(is_visible); auto menubar = gui::Application::GetInstance().GetMenubar(); menubar->SetChecked(HELP_CAMERA, is_visible); if (is_visible) { impl_->scene_->SetCameraChangedCallback( [this](visualization::Camera *cam) { auto children = this->impl_->help_camera_->GetChildren(); auto set_text = [](const Eigen::Vector3f &v, std::shared_ptr label) { auto l = std::dynamic_pointer_cast( label); l->SetText(fmt::format("[{:.2f} {:.2f} " "{:.2f}]", v.x(), v.y(), v.z()) .c_str()); }; set_text(cam->GetPosition(), children[1]); set_text(cam->GetForwardVector(), children[3]); set_text(cam->GetLeftVector(), children[5]); set_text(cam->GetUpVector(), children[7]); this->SetNeedsLayout(); }); } else { impl_->scene_->SetCameraChangedCallback( std::function()); } break; } case HELP_ABOUT: { auto dlg = CreateAboutDialog(this); ShowDialog(dlg); break; } case HELP_CONTACT: { auto dlg = CreateContactDialog(this); ShowDialog(dlg); break; } } } void GuiVisualizer::OnDragDropped(const char *path) { auto title = std::string("Open3D - ") + path; #if LOAD_IN_NEW_WINDOW auto frame = this->GetFrame(); std::vector> nothing; auto vis = std::make_shared(nothing, title.c_str(), frame.width, frame.height, frame.x + 20, frame.y + 20); gui::Application::GetInstance().AddWindow(vis); #else this->SetTitle(title); auto vis = this; #endif // LOAD_IN_NEW_WINDOW if (!vis->LoadGeometry(path)) { auto err = std::string("Error reading geometry file '") + path + "'"; vis->ShowMessageBox("Error loading geometry", err.c_str()); } PostRedraw(); } } // namespace visualization } // namespace open3d