diff --git a/CMakeLists.txt b/CMakeLists.txt index 00fb980e..7ee5c2cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ add_library(iridescence src/guik/imgui_application.cpp src/guik/screen_capture.cpp src/guik/recent_files.cpp + src/guik/drawable_tree.cpp src/guik/camera/camera_control.cpp src/guik/camera/orbit_camera_control_xy.cpp src/guik/camera/orbit_camera_control_xz.cpp @@ -251,6 +252,7 @@ if(BUILD_EXAMPLES) ${example_src} ) target_link_libraries(${example_name} + fmt iridescence ) endforeach() diff --git a/include/guik/drawable_tree.hpp b/include/guik/drawable_tree.hpp new file mode 100644 index 00000000..064b411c --- /dev/null +++ b/include/guik/drawable_tree.hpp @@ -0,0 +1,110 @@ +#ifndef GUIK_DRAWABLE_TREE_HPP +#define GUIK_DRAWABLE_TREE_HPP + +#include +#include + +namespace guik { + +/** +@brief Hierarchical drawable tree. + Drawables are managed with slash-separated path names (e.g., "tree/child/grandchild/obj1"). + The model matrix of a node is hierarchically applied to its children. + For example, for a tree "A/B/C", the model matrix of "C" becomes "A * B * C". +@example + auto tree = std::make_shared(); + // Register drawables to the tree. + tree->update_drawable("parent/child/node1", drawable1, guik::VertexColor(node1_matrix)); + tree->update_drawable("parent/child/node2", drawable2, guik::VertexColor(node2_matrix)); + // Update shader settings. This matrix is applied to both "node1" and "node2". + tree->update_setting("parent/child", guik::ShaderSetting(child_matrix)); + + // Overwrite the color settings of all children of "parent/child". + tree->update_tree_color_mode("parent/child", guik::ColorMode::FLAT_COLOR); + tree->update_tree_color("parent/child", Eigen::Vector4f(0.0f, 0.0f, 1.0f, 1.0f)); + + // Overwrite the point scale of all children of "parent/child". + tree->update_tree_setting("parent/child", "point_scale", 2.0f); + + // Output the tree structure to stdout for debugging. + tree->print_tree(); + + viewer->update_drawable("tree", tree); +*/ +class DrawableTree : public glk::Drawable { +public: + using Ptr = std::shared_ptr; + using ConstPtr = std::shared_ptr; + + DrawableTree(); + + /// @brief Update drawable at a tree node. + /// @param name Slash-separated path name (e.g., "parent/child/node1") + /// @param drawable Drawable object + /// @param setting Shader setting + void update_drawable(const std::string& name, glk::Drawable::ConstPtr drawable, const guik::ShaderSetting& setting); + + /// @brief Update shader setting of a node. + void update_setting(const std::string& name, const guik::ShaderSetting& setting); + + /// @brief Update shader setting of all children of a node. + template + void update_tree_setting(const std::string& name, const std::string& param_name, const T& val) { + const auto names = split_path(name); + update_tree_setting(names.data(), names.data() + names.size(), param_name, val); + } + + /// @brief Update color mode of all children of a node. + void update_tree_color_mode(const std::string& name, guik::ColorMode::MODE color_mode); + + /// @brief Update color of all children of a node. + void update_tree_color(const std::string& name, const Eigen::Vector4f& color); + + /// @brief Remove a subtree. + bool remove(const std::string& name); + + /// @brief Print the tree structure to stdout. + void print_tree(int depth = 0, const Eigen::Matrix4f& parent_model_matrix = Eigen::Matrix4f::Identity()) const; + + /// @brief Draw the tree. + virtual void draw(glk::GLSLShader& shader) const override; + +private: + DrawableTree(const std::string& name); + + void update_drawable(const std::string* first, const std::string* last, glk::Drawable::ConstPtr drawable, const guik::ShaderSetting& setting); + + bool remove(const std::string* first, const std::string* last); + + template + void update_tree_setting(const std::string* first, const std::string* last, const std::string& param_name, const T& val) { + if (first == last) { + setting.add(param_name, val); + for (auto child : children) { + child.second->update_tree_setting(first, last, param_name, val); + } + } else { + auto found = children.find(*first); + if (found == children.end()) { + return; + } + + found->second->update_tree_setting(first + 1, last, param_name, val); + } + } + + void draw(glk::GLSLShader& shader, const Eigen::Matrix4f& parent_model_matrix) const; + +private: + std::vector split_path(const std::string& path); + +private: + const std::string name; // Node name + guik::ShaderSetting setting; // Shader setting associated with the node + glk::Drawable::ConstPtr drawable; // Drawable object associated with the node + std::unordered_map children; // Child nodes +}; + +} // namespace guik + +#endif \ No newline at end of file diff --git a/include/guik/viewer/shader_setting.hpp b/include/guik/viewer/shader_setting.hpp index 03beebf5..828c2ce3 100644 --- a/include/guik/viewer/shader_setting.hpp +++ b/include/guik/viewer/shader_setting.hpp @@ -118,7 +118,7 @@ struct ShaderSetting { /// @param name Parameter name /// @return Parameter value if found, otherwise std::nullopt template - std::optional get(const std::string& name) { + std::optional get(const std::string& name) const { for (const auto& param : params) { if (param->name != name) { continue; @@ -139,7 +139,7 @@ struct ShaderSetting { /// @param name Parameter name /// @return Parameter value template - const T& cast(const std::string& name) { + const T& cast(const std::string& name) const { for (const auto& param : params) { if (param->name != name) { continue; @@ -170,6 +170,11 @@ struct ShaderSetting { ShaderSetting& dymamic_object() { return add("dynamic_object", 1); } // Color + int color_mode() const { + auto p = static_cast*>(params[0].get()); + return p->value; + } + ShaderSetting& set_color(float r, float g, float b, float a) { if (a < 0.999f) { transparent = true; @@ -233,6 +238,8 @@ struct ShaderSetting { return *this; } + bool has_model_matrix() const { return params[2] != nullptr; } + Eigen::Matrix4f model_matrix() const { auto p = static_cast*>(params[2].get()); return p->value; diff --git a/src/guik/drawable_tree.cpp b/src/guik/drawable_tree.cpp new file mode 100644 index 00000000..937f3f46 --- /dev/null +++ b/src/guik/drawable_tree.cpp @@ -0,0 +1,137 @@ +#include + +namespace guik { + +DrawableTree::DrawableTree() : name("root") {} + +DrawableTree::DrawableTree(const std::string& name) : name(name) {} + +void DrawableTree::update_drawable(const std::string& name, glk::Drawable::ConstPtr drawable, const guik::ShaderSetting& setting) { + const auto names = split_path(name); + update_drawable(names.data(), names.data() + names.size(), drawable, setting); +} + +void DrawableTree::update_setting(const std::string& name, const guik::ShaderSetting& setting) { + const auto names = split_path(name); + update_drawable(names.data(), names.data() + names.size(), nullptr, setting); +} + +void DrawableTree::update_tree_color_mode(const std::string& name, guik::ColorMode::MODE color_mode) { + update_tree_setting(name, "color_mode", color_mode); +} +void DrawableTree::update_tree_color(const std::string& name, const Eigen::Vector4f& color) { + update_tree_setting(name, "material_color", color); +} + +bool DrawableTree::remove(const std::string& name) { + const auto names = split_path(name); + if (!remove(names.data(), names.data() + names.size())) { + std::cerr << "warning: failed to remove subtree : " << name << std::endl; + return false; + } + return true; +} + +void DrawableTree::draw(glk::GLSLShader& shader) const { + draw(shader, Eigen::Matrix4f::Identity()); +} + +void DrawableTree::print_tree(int depth, const Eigen::Matrix4f& parent_model_matrix) const { + Eigen::Matrix4f model_matrix = setting.has_model_matrix() ? parent_model_matrix * setting.model_matrix() : parent_model_matrix; + + for (int i = 0; i < depth; i++) { + std::cout << " "; + } + + Eigen::Quaternionf quat(model_matrix.block<3, 3>(0, 0)); + Eigen::Vector3f trans(model_matrix.block<3, 1>(0, 3)); + + std::cout << name << " : trans=(" << trans.x() << "," << trans.y() << "," << trans.z() << ") quat=(" << quat.x() << "," << quat.y() << "," << quat.z() << "," << quat.w() << ")" + << (drawable ? " D" : "") << std::endl; + + for (const auto& child : children) { + child.second->print_tree(depth + 1, model_matrix); + } +} + +void DrawableTree::update_drawable(const std::string* first, const std::string* last, glk::Drawable::ConstPtr drawable, const guik::ShaderSetting& setting) { + if (first == last) { + this->setting = setting; + if (drawable) { + this->drawable = drawable; + } + return; + } + + auto found = children.find(*first); + if (found == children.end()) { + auto child = DrawableTree::Ptr(new DrawableTree(*first)); + found = children.emplace_hint(found, *first, child); + } + + found->second->update_drawable(first + 1, last, drawable, setting); +} + +bool DrawableTree::remove(const std::string* first, const std::string* last) { + if (first == last) { + return false; + } + + auto found = children.find(*first); + if (found == children.end()) { + return false; + } + + if (first + 1 == last) { + children.erase(found); + return true; + } + + return found->second->remove(first + 1, last); +} + +void DrawableTree::draw(glk::GLSLShader& shader, const Eigen::Matrix4f& parent_model_matrix) const { + Eigen::Matrix4f model_matrix = parent_model_matrix; + if (setting.has_model_matrix()) { + model_matrix = model_matrix * setting.model_matrix(); + } + + if (drawable) { + setting.set(shader); + shader.set_uniform("model_matrix", model_matrix); + drawable->draw(shader); + } + + for (const auto& child : children) { + setting.set(shader); + child.second->draw(shader, model_matrix); + } +} + +std::vector DrawableTree::split_path(const std::string& path) { + if (path.empty()) { + std::cerr << "warning: empty path" << std::endl; + return {}; + } + + std::vector names; + int cursor = path.find_first_not_of('/'); + while (cursor < path.size()) { + int next = path.find('/', cursor); + if (next == cursor) { + std::cout << "warning: skip empty name : " << path.substr(0, cursor) << "()" << path.substr(cursor) << std::endl; + cursor++; + continue; + } + + if (next == std::string::npos) { + names.push_back(path.substr(cursor)); + break; + } + names.push_back(path.substr(cursor, next - cursor)); + cursor = next + 1; + } + + return names; +} +} // namespace guik