From 2af7a46e49f38eafb66d3cea2e78eb774ebb9a3d Mon Sep 17 00:00:00 2001 From: Robert Chisholm Date: Thu, 18 Aug 2022 15:00:02 +0100 Subject: [PATCH] ImGui support for user-defined interfaces to view/update environment properties. User's are provided the ability to add environment properties/environment array property elements to user interface panel(s) in the form of input boxes, sliders, drag things and checkboxes. Additionally, moved the debug menu to ImGui and added the initial random seed to it's items. --- cmake/dependencies/imgui-CMakeLists.txt.in | 57 ++++ cmake/dependencies/imgui.cmake | 39 +++ .../visualiser/FLAMEGPU_Visualisation.h | 9 + .../flamegpu/visualiser/config/ImGuiWidgets.h | 263 ++++++++++++++++++ .../flamegpu/visualiser/config/ModelConfig.h | 6 + .../flamegpu/visualiser/config/PanelConfig.h | 47 ++++ src/CMakeLists.txt | 11 + .../visualiser/FLAMEGPU_Visualisation.cpp | 6 + src/flamegpu/visualiser/Visualiser.cpp | 102 +++---- src/flamegpu/visualiser/Visualiser.h | 22 +- .../visualiser/config/ModelConfig.cpp | 1 + src/flamegpu/visualiser/ui/ImGuiPanel.cpp | 149 ++++++++++ src/flamegpu/visualiser/ui/ImGuiPanel.h | 94 +++++++ src/flamegpu/visualiser/ui/Overlay.h | 2 +- src/flamegpu/visualiser/ui/Sprite2D.h | 2 +- 15 files changed, 753 insertions(+), 57 deletions(-) create mode 100644 cmake/dependencies/imgui-CMakeLists.txt.in create mode 100644 cmake/dependencies/imgui.cmake create mode 100644 include/flamegpu/visualiser/config/ImGuiWidgets.h create mode 100644 include/flamegpu/visualiser/config/PanelConfig.h create mode 100644 src/flamegpu/visualiser/ui/ImGuiPanel.cpp create mode 100644 src/flamegpu/visualiser/ui/ImGuiPanel.h diff --git a/cmake/dependencies/imgui-CMakeLists.txt.in b/cmake/dependencies/imgui-CMakeLists.txt.in new file mode 100644 index 0000000..64f5a68 --- /dev/null +++ b/cmake/dependencies/imgui-CMakeLists.txt.in @@ -0,0 +1,57 @@ +project(imgui LANGUAGES CXX) +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + # If top level project + SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}/) +else() + # If called via add_subdirectory() + SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../lib/${CMAKE_BUILD_TYPE}/) +endif() + +# Glob for sources +file(GLOB imgui_sources LIST_DIRECTORIES false ${PROJECT_SOURCE_DIR}/imgui/*.cpp ${PROJECT_SOURCE_DIR}/imgui/*.h) + +# Include back-end specific sources +set(imgui_sources ${imgui_sources} + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_sdl.h + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_sdl.cpp + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3.cpp + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3.h + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_opengl3_loader.h +) +if(MSVC) +set(imgui_sources ${imgui_sources} + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_win32.h + ${PROJECT_SOURCE_DIR}/imgui/backends/imgui_impl_win32.cpp +) +endif() + +# Depends on the cpp and header files in case of changes +add_library(imgui STATIC ${imgui_sources}) + + +# Specify the include directory, to be forwared to targets which link against the imgui target. +# Mark this as SYSTEM INTERFACE, so that it does not result in compiler warnings being generated for dependent projects. +# For our use case, this is up a folder so we can use imgui/imgui.h as the include, by resolving the relative path to get an abs path +get_filename_component(imgui_inc_dir ${imgui_SOURCE_DIR} REALPATH) +target_include_directories("${PROJECT_NAME}" SYSTEM INTERFACE ${imgui_SOURCE_DIR}/imgui ${imgui_SOURCE_DIR}) + +# Because we're using a nested dir rather than root, specify include path +target_include_directories("${PROJECT_NAME}" PUBLIC ${imgui_SOURCE_DIR}/imgui) +# Also depends on SDL for backend +target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${SDL2_INCLUDE_DIRS}) + +# Add some compile time definitions +target_compile_definitions(imgui INTERFACE $<$:IMGUI_DEBUG>) +set_target_properties(imgui PROPERTIES + COMPILE_DEFINITIONS "IMGUI_EXPORT" +) + +# Pic is sensible for any library +set_property(TARGET imgui PROPERTY POSITION_INDEPENDENT_CODE ON) + +# Suppress warnigns from this target. (Not currently required) +#include(${CMAKE_CURRENT_LIST_DIR}/../warnings.cmake) +#DisableCompilerWarnings(TARGET imgui) + +# Create an alias target for imgui to namespace it / make it more like other modern cmake +add_library(ImGui::ImGui ALIAS imgui) \ No newline at end of file diff --git a/cmake/dependencies/imgui.cmake b/cmake/dependencies/imgui.cmake new file mode 100644 index 0000000..77352da --- /dev/null +++ b/cmake/dependencies/imgui.cmake @@ -0,0 +1,39 @@ +######### +# imgui # +######### + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/modules/ ${CMAKE_MODULE_PATH}) +include(FetchContent) + +cmake_policy(SET CMP0079 NEW) + +# Change the source_dir to allow inclusion via imgui/imgui.h rather than imgui.h +FetchContent_Declare( + imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG v1.88 + GIT_SHALLOW 1 + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/imgui-src/imgui + GIT_PROGRESS ON + # UPDATE_DISCONNECTED ON +) + +# imgui does not include anything + +# @todo - try finding the package first, assuming it sets system correctly when used. +FetchContent_GetProperties(imgui) +if(NOT imgui_POPULATED) + FetchContent_Populate(imgui) + + # The imgui repository does not include a CMakeLists.txt (or similar) + # Create our own cmake target for imgui. + + # If the target does not already exist, add it. + # @todo - make this far more robust. + if(NOT TARGET imgui) + # make a dynamically generated CMakeLists.txt which can be add_subdirectory'd instead, so that the .vcxproj goes in a folder. Just adding a project doesn't work. + configure_file(${CMAKE_CURRENT_LIST_DIR}/imgui-CMakeLists.txt.in ${FETCHCONTENT_BASE_DIR}/imgui-src/CMakeLists.txt @ONLY) + # We've now created CMakeLists.txt so we can use MakeAvailable + add_subdirectory(${imgui_SOURCE_DIR}/.. ${imgui_BINARY_DIR}) + endif() +endif() diff --git a/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h b/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h index 3d118fb..3b0f1da 100644 --- a/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h +++ b/include/flamegpu/visualiser/FLAMEGPU_Visualisation.h @@ -3,6 +3,7 @@ #include #include +#include #include "flamegpu/visualiser/config/TexBufferConfig.h" @@ -35,11 +36,19 @@ class FLAMEGPU_Visualisation { const std::map& core_tex_buffers, const std::multimap& tex_buffers) { updateAgentStateBuffer(agent_name.c_str(), state_name.c_str(), buffLen, core_tex_buffers, tex_buffers); } + /** + * Provide an environment property, so it can be displayed + */ + void registerEnvironmentProperty(const std::string& property_name, void* ptr, std::type_index type, unsigned int elements, bool is_const); /** * Update the UI step counter * @note When this value is first set non-0, the visualiser assumes sim has begun executing */ void setStepCount(const unsigned int stepCount); + /** + * Provide the random seed, so it can be displayed in the debug menu + */ + void setRandomSeed(uint64_t randomSeed); /* * Start visualiser in background thread */ diff --git a/include/flamegpu/visualiser/config/ImGuiWidgets.h b/include/flamegpu/visualiser/config/ImGuiWidgets.h new file mode 100644 index 0000000..55423c5 --- /dev/null +++ b/include/flamegpu/visualiser/config/ImGuiWidgets.h @@ -0,0 +1,263 @@ +#ifndef INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_ +#define INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_ + +#include + +#include +#include +#include +#include // Not actually required, linter just thinks initialiser for max is fn call + +namespace flamegpu { +namespace visualiser { + +/** + * Template for converting a type to the corresponding ImGui type enum + * Useful when summing unknown values + * e.g. sum_input_t::result_t == double + * e.g. sum_input_t::result_t == uint64_t + */ +template struct imgui_type; +/** + * @see imgui_type + */ +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_S8; static constexpr const char* fmt = "%c"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_S8; static constexpr const char* fmt = "%hhd"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_U8; static constexpr const char* fmt = "%hhu"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_S16; static constexpr const char* fmt = "%hd"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_U16; static constexpr const char* fmt = "%hu"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_S32; static constexpr const char* fmt = "%d"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_U32; static constexpr const char* fmt = "%u"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_S64; static constexpr const char* fmt = "%lld"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_U64; static constexpr const char* fmt = "%llu"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_Float; static constexpr const char* fmt = "%g"; }; +template <> struct imgui_type { static constexpr ImGuiDataType_ t = ImGuiDataType_Double; static constexpr const char* fmt = "%g"; }; + +/** + * The most generic abstract ImGui element superclass + * All other elements inherit from this + */ +struct PanelElement { + virtual ~PanelElement() = default; + /** + * Triggers the underlying call to ImGui + */ + virtual bool addToImGui() = 0; + /** + * Provides copy construction behaviour + */ + virtual std::unique_ptr clone() const = 0; +}; +/** + * Separator element, creates a horizontal line between elements + */ +struct SeparatorElement : PanelElement { + /** + * Constructor + */ + SeparatorElement() { } + bool addToImGui() override { ImGui::Separator(); return false; } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new SeparatorElement()); } +}; +/** + * Merely denotes that return value should be treated differently + */ +struct SectionElement : PanelElement { }; +/** + * Collapsible header element, creates a titled section which can be collapsed + */ +struct HeaderElement : SectionElement { + /** + * Constructor + * @param _text Text displayed in the section header + * @param _begin_open If true, the section will not begin in a collapsed state + */ + explicit HeaderElement(const std::string& _text, const bool _begin_open) + : text(_text) + , begin_open(_begin_open) { } + bool addToImGui() override { return ImGui::CollapsingHeader(text.c_str(), begin_open ? ImGuiTreeNodeFlags_DefaultOpen : 0); } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new HeaderElement(text, begin_open)); } + std::string text; + bool begin_open; +}; +/** + * Ends a section, unhiding following elements + */ +struct EndSectionElement : SectionElement { + /** + * Constructor + */ + EndSectionElement() { } + bool addToImGui() override { return true; } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new EndSectionElement()); } + std::string text; +}; +/** + * Label element, only contains a string used to hold it's text to be displayed + */ +struct LabelElement : PanelElement { + /** + * Constructor + * @param _text Text displayed on the label + */ + explicit LabelElement(const std::string &_text) + : text(_text) { } + bool addToImGui() override { ImGui::Text("%s", text.c_str()); return false; } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new LabelElement(text)); } + std::string text; +}; +/** + * Environment property modifier generic + * All environment properties inherit from this + */ +struct EnvPropertyElement : PanelElement { + /** + * Constructor + * @param _name Name of the environment property + * @param _index Index of the environment property element (0 if the property is not an array property) + * @param _ptr_offset Pointer offset from the environment property origin (as this depending on the index and type size, 0 if the property is not an array property) + * @note _ptr_offset should be calculated using the type information available to the subclass + */ + explicit EnvPropertyElement(const std::string& _name, unsigned int _index, unsigned int _ptr_offset) + : data_ptr(nullptr) + , name(_name) + , index(_index) + , ptr_offset(_ptr_offset) { } + void* data_ptr; + std::string name; + unsigned int index; + unsigned int ptr_offset; + bool is_const = false; + bool addToImGui() override = 0; + std::unique_ptr clone() const override = 0; + const std::string& getName() const { return name; } + void setPtr(void *ptr) { data_ptr = static_cast(ptr) + ptr_offset; } + void setConst(bool _is_const) { is_const = _is_const; } + void setArray() { name += "[" + std::to_string(index) +"]"; } +}; +/** + * ImGui slider element for an environment property + */ +template +struct EnvPropertySlider : EnvPropertyElement { + /** + * Constructor + * @param _name Name of the environment property + * @param _index Index of the environment property element (0 if the property is not an array property) + * @param _min Minimum value of the slider + * @param _max Maximum value of the slider + */ + EnvPropertySlider(const std::string& _name, unsigned int _index, T _min, T _max) + : EnvPropertyElement(_name, _index, _index * sizeof(T)) + , min(_min) + , max(_max) { } + T min, max; + bool addToImGui() override { + if (this->data_ptr) { + if (this->is_const) ImGui::BeginDisabled(); + const bool rtn = ImGui::SliderScalar(this->name.c_str(), imgui_type::t, this->data_ptr, &this->min, &this->max, imgui_type::fmt, ImGuiSliderFlags_AlwaysClamp); + if (this->is_const) ImGui::EndDisabled(); + return rtn; + } + ImGui::Text("Loading...%s", this->name.c_str()); + return false; + } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new EnvPropertySlider(this->name, this->index, this->min, this->max)); } +}; +/** + * ImGui drag element for an environment property + */ +template +struct EnvPropertyDrag : EnvPropertyElement { + /** + * Constructor + * @param _name Name of the environment property + * @param _index Index of the environment property element (0 if the property is not an array property) + * @param _min Minimum value that can be set + * @param _max Maximum value that can be set + * @param _speed Amount the value changes per pixel dragged + */ + EnvPropertyDrag(const std::string& _name, unsigned int _index, T _min, T _max, float _speed) + : EnvPropertyElement(_name, _index, _index * sizeof(T)) + , min(_min) + , max(_max) + , speed(_speed) { } + T min, max; + float speed; + bool addToImGui() override { + if (this->data_ptr) { + if (this->is_const) ImGui::BeginDisabled(); + const bool rtn = ImGui::DragScalar(this->name.c_str(), imgui_type::t, this->data_ptr, this->speed, &this->min, &this->max, imgui_type::fmt, ImGuiSliderFlags_AlwaysClamp); + if (this->is_const) ImGui::EndDisabled(); + return rtn; + } + ImGui::Text("Loading...%s", this->name.c_str()); + return false; + } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new EnvPropertyDrag(this->name, this->index, this->min, this->max, this->speed)); } +}; +/** + * ImGui input with +/- step buttons for an environment property + */ +template +struct EnvPropertyInput : EnvPropertyElement { + /** + * Constructor + * @param _name Name of the environment property + * @param _index Index of the environment property element (0 if the property is not an array property) + * @param _step Change per button click + * @param _step_fast Change per tick when holding button (?) + */ + EnvPropertyInput(const std::string& _name, unsigned int _index, T _step, T _step_fast) + : EnvPropertyElement(_name, _index, _index * sizeof(T)) + , step(_step) + , step_fast(_step_fast) { } + T step, step_fast; + bool addToImGui() override { + if (this->data_ptr) { + if (this->is_const) ImGui::BeginDisabled(); + const bool rtn = ImGui::InputScalar(this->name.c_str(), imgui_type::t, this->data_ptr, &this->step, &this->step_fast, imgui_type::fmt, ImGuiInputTextFlags_CallbackCompletion); + if (this->is_const) ImGui::EndDisabled(); + return rtn; + } + ImGui::Text("Loading...%s", this->name.c_str()); + return false; + } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new EnvPropertyInput(this->name, this->index, this->step, this->step_fast)); } +}; +/** + * ImGui checkbox for integer type environment properties + */ +template +struct EnvPropertyToggle : EnvPropertyElement { + /** + * Constructor + * @param _name Name of the environment property + * @param _index Index of the environment property element (0 if the property is not an array property) + */ + explicit EnvPropertyToggle(const std::string& _name, unsigned int _index) + : EnvPropertyElement(_name, _index, _index * sizeof(T)) + , data(false) { } + bool data; + bool addToImGui() override { + if (this->data_ptr) { + if (is_const) ImGui::BeginDisabled(); + // How to sync data + if (ImGui::Checkbox(this->name.c_str(), &data)) { + *static_cast(data_ptr) = static_cast(data ? 1 : 0); + return true; + } + // Really not sure that this sync will work as desired if the model updates the value independently + data = *static_cast(data_ptr); + if (is_const) ImGui::EndDisabled(); + return false; + } + ImGui::Text("Loading...%s", this->name.c_str()); + return false; + } + [[nodiscard]] std::unique_ptr clone() const override { return std::unique_ptr(new EnvPropertyToggle(this->name, this->index)); } +}; +} // namespace visualiser +} // namespace flamegpu + +#endif // INCLUDE_FLAMEGPU_VISUALISER_CONFIG_IMGUIWIDGETS_H_ diff --git a/include/flamegpu/visualiser/config/ModelConfig.h b/include/flamegpu/visualiser/config/ModelConfig.h index 351c1e3..a91bdcf 100644 --- a/include/flamegpu/visualiser/config/ModelConfig.h +++ b/include/flamegpu/visualiser/config/ModelConfig.h @@ -3,9 +3,11 @@ #include #include +#include #include #include "LineConfig.h" +#include "PanelConfig.h" namespace flamegpu { namespace visualiser { @@ -123,6 +125,10 @@ struct ModelConfig { * Store of user defined line renderings */ std::list> lines; + /** + * Store of user defined UI panels + */ + std::map> panels; /** * Notify visualisation that it's running under python * This mostly just allows the logos to be swapped diff --git a/include/flamegpu/visualiser/config/PanelConfig.h b/include/flamegpu/visualiser/config/PanelConfig.h new file mode 100644 index 0000000..abf704c --- /dev/null +++ b/include/flamegpu/visualiser/config/PanelConfig.h @@ -0,0 +1,47 @@ +#ifndef INCLUDE_FLAMEGPU_VISUALISER_CONFIG_PANELCONFIG_H_ +#define INCLUDE_FLAMEGPU_VISUALISER_CONFIG_PANELCONFIG_H_ + +#include +#include +#include + +#include "ImGuiWidgets.h" + +namespace flamegpu { +namespace visualiser { + +/** + * Represents the user-specified elements to be shown within an ImGui panel as part of the visualisation's UI + */ +struct PanelConfig { + /** + * Constructor, only a name is specified, elements can be added later + * @_title Name of the panel to be displayed in the title bar + */ + explicit PanelConfig(const std::string &_title) + : title(_title) { } + /** + * Copy constructor + * @other Other PanelConfig to be copied + */ + explicit PanelConfig(const PanelConfig& other) + : title(other.title) { + for (const auto &e : other.ui_elements) { + this->ui_elements.push_back(e->clone()); + } + } + /** + * Name of the panel + * This will normally be shown in the title_bar at the top of the panel + */ + std::string title; + /** + * Elements will be applied in order + */ + std::list> ui_elements; +}; + +} // namespace visualiser +} // namespace flamegpu + +#endif // INCLUDE_FLAMEGPU_VISUALISER_CONFIG_PANELCONFIG_H_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee42dd3..9e3da5c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,8 +53,10 @@ SET(VISUALISER_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/AgentStateConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/ModelConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/LineConfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/PanelConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/Stock.h ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/TexBufferConfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/../include/flamegpu/visualiser/config/ImGuiWidgets.h ) # Prepare list of source files SET(VISUALISER_SRC @@ -107,6 +109,7 @@ SET(VISUALISER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/Texture2D_Multisample.h ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/TextureBuffer.h ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/TextureCubeMap.h + ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/ImGuiPanel.h ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/Overlay.h ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/SplashScreen.h ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/Sprite2D.h @@ -140,6 +143,7 @@ SET(VISUALISER_SRC ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/Texture2D_Multisample.cpp ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/TextureBuffer.cu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/texture/TextureCubeMap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/ImGuiPanel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/Overlay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/SplashScreen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/flamegpu/visualiser/ui/Sprite2D.cpp @@ -200,6 +204,8 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/dependencies/fontconfig.cmake) include(${CMAKE_CURRENT_LIST_DIR}/../cmake/dependencies/freetype.cmake) # DevIL include(${CMAKE_CURRENT_LIST_DIR}/../cmake/dependencies/devil.cmake) +# imgui +include(${CMAKE_CURRENT_LIST_DIR}/../cmake/dependencies/imgui.cmake) # GCC requires -lpthreads for std::thread set(CMAKE_THREAD_PREFER_PTHREAD TRUE) @@ -242,6 +248,7 @@ endif() target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${glm_INCLUDE_DIRS}) target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${SDL2_INCLUDE_DIRS}) target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${GLEW_INCLUDE_DIRS}) +target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${IMGUI_INCLUDE_DIRS}) if (FREETYPE_FOUND) # Only use this if we aren't building it ourselves target_include_directories("${PROJECT_NAME}" SYSTEM PRIVATE ${FREETYPE_INCLUDE_DIRS}) endif () @@ -281,6 +288,7 @@ else() endif() target_link_libraries("${PROJECT_NAME}" freetype) target_link_libraries("${PROJECT_NAME}" "${IL_LIBRARIES}") +target_link_libraries("${PROJECT_NAME}" ImGui::ImGui) target_link_libraries("${PROJECT_NAME}" OpenGL::GL) target_link_libraries("${PROJECT_NAME}" Threads::Threads) if(TARGET Fontconfig::Fontconfig) @@ -362,4 +370,7 @@ if (CMAKE_USE_FOLDERS) if (TARGET resources) set_property(TARGET "resources" PROPERTY FOLDER "FLAMEGPU/Dependencies") endif() + if (TARGET imgui) + set_property(TARGET "imgui" PROPERTY FOLDER "FLAMEGPU/Dependencies") + endif() endif() diff --git a/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp b/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp index 530d979..877c74b 100644 --- a/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp +++ b/src/flamegpu/visualiser/FLAMEGPU_Visualisation.cpp @@ -37,9 +37,15 @@ void FLAMEGPU_Visualisation::updateAgentStateBuffer(const char *agent_name, cons const std::map& core_tex_buffers, const std::multimap& tex_buffers) { vis->updateAgentStateBuffer(agent_name, state_name, buffLen, core_tex_buffers, tex_buffers); } +void FLAMEGPU_Visualisation::registerEnvironmentProperty(const std::string& property_name, void* ptr, const std::type_index type, const unsigned int elements, const bool is_const) { + vis->registerEnvironmentProperty(property_name, ptr, type, elements, is_const); +} void FLAMEGPU_Visualisation::setStepCount(const unsigned int stepCount) { vis->setStepCount(stepCount); } +void FLAMEGPU_Visualisation::setRandomSeed(const uint64_t randomSeed) { + vis->setRandomSeed(randomSeed); +} void FLAMEGPU_Visualisation::start() { vis->start(); } diff --git a/src/flamegpu/visualiser/Visualiser.cpp b/src/flamegpu/visualiser/Visualiser.cpp index a27f0d9..3fdaeef 100644 --- a/src/flamegpu/visualiser/Visualiser.cpp +++ b/src/flamegpu/visualiser/Visualiser.cpp @@ -1,5 +1,9 @@ #include "flamegpu/visualiser/Visualiser.h" +#include +#include +#include + #include #include #include @@ -116,7 +120,6 @@ Visualiser::Visualiser(const ModelConfig& modelcfg) , fpsDisplay(nullptr) , stepDisplay(nullptr) , spsDisplay(nullptr) - , debugMenu(nullptr) , modelConfig(modelcfg) , lighting(nullptr) , gamepad(nullptr) @@ -143,11 +146,9 @@ Visualiser::Visualiser(const ModelConfig& modelcfg) stepDisplay->setUseAA(false); hud->add(stepDisplay, HUD::AnchorV::South, HUD::AnchorH::East, glm::ivec2(0, 0), INT_MAX - 1); } - { // Debug menu - debugMenu = std::make_shared("", 16, glm::vec3(1.0f), fonts::findFont({ "Consolas", "Arial" }, fonts::GenericFontFamily::SANS).c_str()); - debugMenu->setBackgroundColor(glm::vec4(*reinterpret_cast(&modelcfg.clearColor[0]), 0.65f)); - debugMenu->setVisible(false); - hud->add(debugMenu, HUD::AnchorV::North, HUD::AnchorH::West, glm::ivec2(0, 0), INT_MAX); + { // ImGui Panels + imguiPanel = std::make_shared(modelcfg.panels, *this); + hud->add(imguiPanel, HUD::AnchorV::North, HUD::AnchorH::West, glm::ivec2(0, 0), INT_MAX-2); } lines = std::make_shared(); lines->setViewMatPtr(camera->getViewMatPtr()); @@ -363,6 +364,10 @@ void Visualiser::render() { this->lighting->getPointLight(0).Position(this->camera->getEye()); // handle each event on the queue while (SDL_PollEvent(&e) != 0) { + if (!SDL_GetRelativeMouseMode()) { + // Assume ImGUI is top level, allow it first chance on IO if mouse isn't locked for movement + ImGui_ImplSDL2_ProcessEvent(&e); + } switch (e.type) { case SDL_QUIT: continueRender = false; @@ -372,7 +377,7 @@ void Visualiser::render() { resizeWindow(); break; case SDL_KEYDOWN: - { + if (!ImGui::GetIO().WantCaptureKeyboard) { int x = 0; int y = 0; SDL_GetMouseState(&x, &y); @@ -385,7 +390,8 @@ void Visualiser::render() { this->handleMouseMove(e.motion.xrel, e.motion.yrel); break; case SDL_MOUSEBUTTONDOWN: - this->toggleMouseMode(); + if (!ImGui::GetIO().WantCaptureMouse) + this->toggleMouseMode(); break; case SDL_CONTROLLERDEVICEADDED: this->addController(e.cdevice); @@ -408,7 +414,6 @@ void Visualiser::render() { this->queryControllerAxis(frameTime); // Update lighting lighting->update(); - updateDebugMenu(); // Render render_buffer->use(); for (auto &sm : staticModels) @@ -608,7 +613,10 @@ void Visualiser::updateAgentStateBuffer(const std::string &agent_name, const std } } } - +void Visualiser::registerEnvironmentProperty(const std::string& property_name, void* ptr, std::type_index /*type*/, unsigned int elements, bool is_const) { + // Construct an (ordered) map of the registered properties + imguiPanel->registerProperty(property_name, ptr, is_const, elements > 1); +} // Items taken from sdl_exp bool Visualiser::init() { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) { @@ -673,6 +681,21 @@ bool Visualiser::init() { screenshot_buffer = std::make_shared(FBAFactory::ManagedColorRenderBufferRGBA(), FBAFactory::ManagedDepthRenderBuffer(), FBAFactory::Disabled(), 1, 1.0f, true, *reinterpret_cast(modelConfig.clearColor)); setMSAA(this->msaaState); + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.IniFilename = nullptr; // Don't automatically save ImGui panel position/state to file + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + // ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, this->context); + ImGui_ImplOpenGL3_Init(); + // Setup the projection matrix this->resizeWindow(); GL_CHECK(); @@ -693,6 +716,9 @@ void Visualiser::resizeWindow() { glm::ivec2 tDims; SDL_GL_GetDrawableSize(this->window, &tDims.x, &tDims.y); this->windowDims = tDims; + // Setup display size (every frame to accommodate for window resizing) + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(static_cast(tDims.x), static_cast(tDims.y)); } // Get the view frustum using GLM. Alternatively glm::perspective could be used. this->projMat = glm::perspectiveFov( @@ -712,7 +738,7 @@ void Visualiser::deallocateGLObjects() { fpsDisplay.reset(); stepDisplay.reset(); spsDisplay.reset(); - debugMenu.reset(); + imguiPanel.reset(); this->hud->clear(); // Don't clear the map, as update buffer methods might still be called for (auto &as : agentStates) { @@ -721,6 +747,11 @@ void Visualiser::deallocateGLObjects() { this->lines.reset(); render_buffer.reset(); screenshot_buffer.reset(); + + // Release imgui state + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); } void Visualiser::close() { @@ -809,8 +840,12 @@ void Visualiser::handleKeypress(SDL_Keycode keycode, int /*x*/, int /*y*/) { this->hud->reload(); break; case SDLK_F1: - if (this->debugMenu) - this->debugMenu->setVisible(!this->debugMenu->getVisible()); + if (this->imguiPanel) + this->imguiPanel->toggleDebugMenuVisible(); + break; + case SDLK_F2: + if (this->imguiPanel) + this->imguiPanel->toggleUIVisible(); break; case SDLK_p: if (this->pause_guard) { @@ -894,6 +929,9 @@ void Visualiser::setStepCount(const unsigned int &_stepCount) { this->lastStepCount = _stepCount; } } +void Visualiser::setRandomSeed(const uint64_t _randomSeed) { + randomSeed = _randomSeed; +} // Overrides unsigned Visualiser::getWindowWidth() const { return windowDims.x; @@ -1100,44 +1138,6 @@ void Visualiser::setWindowIcon() { if (surface) SDL_SetWindowIcon(window, surface.get()); } -void Visualiser::updateDebugMenu() { - if (debugMenu && debugMenu->getVisible()) { - std::stringstream ss; - ss << std::setprecision(3); - ss << std::fixed; // To stop camera floats from changing box width - ss << "===Debug Menu===" << "\n"; - const glm::vec3 eye = camera->getEye(); - const glm::vec3 look = camera->getLook(); - const glm::vec3 up = camera->getUp(); - ss << "Camera Location: (" << eye.x << ", " << eye.y << ", " << eye.z << ")" "\n"; - ss << "Camera Direction: (" << look.x << ", " << look.y << ", " << look.z << ")" "\n"; - ss << "Camera Up: (" << up.x << ", " << up.y << ", " << up.z << ")" "\n"; - ss << "MSAA: " << (this->msaaState ? "On" : "Off") << "\n"; - switch (this->fpsStatus) { - case 2: - ss << "Display FPS: Show All" << "\n"; - break; - case 1: - ss << "Display FPS: Show Step Count" << "\n"; - break; - default: - ss << "Display FPS: Off" << "\n"; - } - ss << "Display Lines: " << (this->renderLines ? "On" : "Off") << "\n"; - ss << "Agent Populations:" << "\n"; - for (const auto &as : agentStates) { - if (debugMenu_showStateNames) { - ss << " " << as.first.first << "(" << as.first.second << "): " << as.second.requiredSize << "\n"; - } else { - ss << " " << as.first.first << ": " << as.second.requiredSize << "\n"; - } - } - - // Last item (don't end \n) - ss << "Pause Simulation: " << (this->pause_guard ? "On" : "Off"); - debugMenu->setString(ss.str().c_str()); - } -} } // namespace visualiser } // namespace flamegpu diff --git a/src/flamegpu/visualiser/Visualiser.h b/src/flamegpu/visualiser/Visualiser.h index 1bf29ee..924020f 100644 --- a/src/flamegpu/visualiser/Visualiser.h +++ b/src/flamegpu/visualiser/Visualiser.h @@ -2,6 +2,7 @@ #define SRC_FLAMEGPU_VISUALISER_VISUALISER_H_ #include +#undef main // SDL breaks the regular main entry point, this fixes #include #include @@ -12,7 +13,7 @@ #include #include #include -#undef main // SDL breaks the regular main entry point, this fixes +#include #define GLM_FORCE_NO_CTOR_INIT #include @@ -20,6 +21,7 @@ #include "flamegpu/visualiser/Entity.h" #include "flamegpu/visualiser/HUD.h" #include "flamegpu/visualiser/ui/Text.h" +#include "flamegpu/visualiser/ui/ImGuiPanel.h" #include "flamegpu/visualiser/camera/NoClipCamera.h" #include "flamegpu/visualiser/config/AgentStateConfig.h" #include "flamegpu/visualiser/config/TexBufferConfig.h" @@ -40,6 +42,7 @@ class LightsBuffer; * This is the main class of the visualisation, hosting the window and render loop */ class Visualiser : public ViewportExt { + friend class ImGuiPanel; typedef std::pair NamePair; struct NamePairHash { size_t operator()(const NamePair &k) const { return std::hash()(k.first) ^ (std::hash()(k.second) << 1); } @@ -117,6 +120,10 @@ class Visualiser : public ViewportExt { */ void updateAgentStateBuffer(const std::string &agent_name, const std::string &state_name, const unsigned int buffLen, const std::map& _core_tex_buffers, const std::multimap& _tex_buffers); + /** + * Provide the env_cache ptr for the specified environment property, for visualisation + */ + void registerEnvironmentProperty(const std::string& property_name, void* ptr, std::type_index type, unsigned int elements, bool is_const); private: void run(); @@ -232,9 +239,12 @@ class Visualiser : public ViewportExt { * @param stepCount The step value to be displayed */ void setStepCount(const unsigned int &stepCount); + /** + * Sets the value to be rendered for random seed in the debug menu + */ + void setRandomSeed(uint64_t randomSeed); private: - void updateDebugMenu(); SDL_Window *window; SDL_Rect windowedBounds; SDL_GLContext context; @@ -297,13 +307,17 @@ class Visualiser : public ViewportExt { */ unsigned int previousStepTime = 0, currentStepTime, stepCount = 0, lastStepCount = 0; /** - * Displays internal status info to screen + * Handles the ImGui supported rendering of the debug menu and user-specified UI panels */ - std::shared_ptr debugMenu; + std::shared_ptr imguiPanel; /** * If set true, we don't display agent states in debug menu because agents only have one state. */ bool debugMenu_showStateNames = false; + /** + * Random seed, displayed in debugMenu + */ + uint64_t randomSeed = 0; /** * Steps equivalent of FPS. * Calculated in the wrong thread, so we update it whenever FPS updates diff --git a/src/flamegpu/visualiser/config/ModelConfig.cpp b/src/flamegpu/visualiser/config/ModelConfig.cpp index 1e182fc..c483134 100644 --- a/src/flamegpu/visualiser/config/ModelConfig.cpp +++ b/src/flamegpu/visualiser/config/ModelConfig.cpp @@ -46,6 +46,7 @@ ModelConfig &ModelConfig::operator=(const ModelConfig &other) { isPython = other.isPython; // staticModels // lines + // panels return *this; } diff --git a/src/flamegpu/visualiser/ui/ImGuiPanel.cpp b/src/flamegpu/visualiser/ui/ImGuiPanel.cpp new file mode 100644 index 0000000..b04416c --- /dev/null +++ b/src/flamegpu/visualiser/ui/ImGuiPanel.cpp @@ -0,0 +1,149 @@ +#include +#include +#include + +#include +#include +#include +#include // for PRIu64 (cross-platform uint64_t format specifier) + +#include "flamegpu/visualiser/Visualiser.h" +#include "flamegpu/visualiser/ui/ImGuiPanel.h" +#include "flamegpu/visualiser/shader/Shaders.h" + +namespace flamegpu { +namespace visualiser { + +ImGuiPanel::ImGuiPanel(const std::map>& cfgs, const Visualiser& _vis) + : Overlay(std::make_shared(Stock::Shaders::SPRITE2D)) + , vis(_vis) { + for (const auto &cfg : cfgs) + configs.push_back(*cfg.second); + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); // This can't be called in the render thread, as it tries to grab the window dimensions via SDL +} +void ImGuiPanel::reload() { + first_render = 0; +} +void ImGuiPanel::drawPanel() { + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImVec2 prev_window_size(0, 0), prev_window_pos(main_viewport->WorkPos.x, main_viewport->WorkPos.y); + for (const auto &config : configs) { + // Translucent background + ImGui::SetNextWindowBgAlpha(150.0f / 255.0f); + // Size panel to fit it's content + ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_Always); + if (first_render < 2) { + // Manual set once condition, as I can't get ImGuiCond to play nicely here for multiple panels + // Position multiple panels so that they don't overlap + ImGui::SetNextWindowPos(ImVec2(prev_window_pos.x + prev_window_size.x + 5, main_viewport->WorkPos.y + 5), ImGuiCond_Always); + } + + // Actually start creating the panel + if (!ImGui::Begin(config.title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + prev_window_size = ImGui::GetWindowSize(); + prev_window_pos = ImGui::GetWindowPos(); + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + // Give items a fixed width (I think this i supposed to be text, but possibly conflicts with auto window resize) + ImGui::PushItemWidth(ImGui::GetFontSize() * 15); + + // Add the user defined items in order + bool open = true; + for (auto& e : config.ui_elements) { + if (auto a = dynamic_cast(e.get())) { + open = a->addToImGui(); + } else if (open) { + e->addToImGui(); + } + } + + // Finalise the panel + ImGui::PopItemWidth(); + prev_window_size = ImGui::GetWindowSize(); + prev_window_pos = ImGui::GetWindowPos(); + ImGui::End(); + } + first_render += first_render < 2 ? 1: 0; // Auto size is not calculated until it's been drawn, so wait a frame to fix it +} +void ImGuiPanel::drawDebugPanel() const { + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 5, main_viewport->WorkPos.y + 5), ImGuiCond_Once); + // Translucent background + ImGui::SetNextWindowBgAlpha(150.0f / 255.0f); + // Size panel to fit it's content (might fix this width so it doesn't jump about with camera mvmt) + ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_Always); + + // Actually start creating the panel + ImGui::Begin("Debug Menu", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + // Give items a fixed width (I think this i supposed to be text, but possibly conflicts with auto window resize) + ImGui::PushItemWidth(ImGui::GetFontSize() * 15); + + ImGui::Text("Initial Random Seed: %" PRIu64, vis.randomSeed); + const glm::vec3 eye = vis.camera->getEye(); + const glm::vec3 look = vis.camera->getLook(); + const glm::vec3 up = vis.camera->getUp(); + ImGui::Text("Camera Location : (% .3f, % .3f, % .3f)", eye.x, eye.y, eye.z); + ImGui::Text("Camera Direction : (% .3f, % .3f, % .3f)", look.x, look.y, look.z); + ImGui::Text("Camera Up : (% .3f, % .3f, % .3f)", up.x, up.y, up.z); + ImGui::Text("MSAA: %s", (vis.msaaState ? "On" : "Off")); + switch (vis.fpsStatus) { + case 2: + ImGui::Text("Display FPS: Show All"); + break; + case 1: + ImGui::Text("Display FPS: Show Step Count"); + break; + default: + ImGui::Text("Display FPS: Off"); + } + ImGui::Text("Pause Simulation: %s", (vis.pause_guard ? "On" : "Off")); + ImGui::Separator(); + ImGui::Text("Agent Populations:"); + for (const auto& as : vis.agentStates) { + if (vis.debugMenu_showStateNames) { + ImGui::BulletText("%s (%s): %u", as.first.first.c_str(), as.first.second.c_str(), as.second.requiredSize); + } else { + ImGui::BulletText("%s: %u", as.first.first.c_str(), as.second.requiredSize); + } + } + + // Finalise the panel + ImGui::PopItemWidth(); + ImGui::End(); +} +void ImGuiPanel::render(const glm::mat4*, const glm::mat4*, GLuint fbo) { + ImGui::NewFrame(); + // bool is_true = true; + // ImGui::ShowDemoWindow(&is_true); + if (ui_visible) drawPanel(); + if (debug_menu_visible) drawDebugPanel(); + // This renders the interface to a host texture + ImGui::Render(); + // This presumably copies the host texture to a GPU texture and renders it + // imgui wants to be it's own HUD, so we kind of need to give it a full screen overlay to draw into + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + // SDL_GL_SwapWindow(window); +} +void ImGuiPanel::registerProperty(const std::string& name, void* ptr, bool is_const, bool is_array) { + for (const auto& config : configs) { + for (auto& e : config.ui_elements) { + if (auto a = dynamic_cast(e.get())) { + if (a->getName() == name) { + if (is_const) a->setConst(true); + if (is_array) a->setArray(); + a->setPtr(ptr); + } + } + } + } + first_render = 0; // As these can change window width +} + +} // namespace visualiser +} // namespace flamegpu diff --git a/src/flamegpu/visualiser/ui/ImGuiPanel.h b/src/flamegpu/visualiser/ui/ImGuiPanel.h new file mode 100644 index 0000000..4b03aea --- /dev/null +++ b/src/flamegpu/visualiser/ui/ImGuiPanel.h @@ -0,0 +1,94 @@ +#ifndef SRC_FLAMEGPU_VISUALISER_UI_IMGUIPANEL_H_ +#define SRC_FLAMEGPU_VISUALISER_UI_IMGUIPANEL_H_ + +#include +#include +#include +#include + +#include "flamegpu/visualiser/config/PanelConfig.h" +#include "flamegpu/visualiser/ui/Overlay.h" + +namespace flamegpu { +namespace visualiser { + +class Visualiser; + +/** + * Class for rendering all ImGui user interfaces to the HUD + * + * ImGui draws to the whole screen space, so we handle them all at the same time for ease of input handling + */ +class ImGuiPanel : public Overlay { + public: + /** + * Default constructor, currently creates a debugging panel thing + * @param cfgs Map of configs specifying how they should be constructed + * @param _vis Handle to the visualiser so that data can be probed by the debug panel + */ + explicit ImGuiPanel(const std::map> &cfgs, const Visualiser& _vis); + /** + * Only resets first_render flag, as ImGui runs in immediate mode + */ + void reload() override; + /** + * Renders the desired panels using ImGui + * Arguments are ignored, ImGui detects and sets these internally + */ + void render(const glm::mat4*, const glm::mat4*, GLuint) override; + /** + * This must be called for every environment property stored within configs + * It provides the host cache pointer and constant status of each property + * @param name Name of the environment property + * @param ptr Pointer to the environment properties data within the host cache + * @param is_const True if the environment property should not be changed + * @param is_array True if the environment property's name should reflect that it's an array within the UI + */ + void registerProperty(const std::string &name, void *ptr, bool is_const, bool is_array); + /** + * Toggle visibility of the debug menu + * @note If debug menu is made visible, the user specified panels are hidden + */ + void toggleDebugMenuVisible() { debug_menu_visible = !debug_menu_visible; if (debug_menu_visible) { ui_visible = false; }} + /** + * Toggle visibility of the user specified panels + * @note If the user specified panels are made visible, the debug menu is hidden + */ + void toggleUIVisible() { ui_visible = !ui_visible; if (ui_visible) { debug_menu_visible = false; }} + + private: + /** + * If true render() triggers drawDebugPanel() + */ + bool debug_menu_visible = false; + /** + * If true render() triggers drawPanel() + */ + bool ui_visible = true; + /** + * Calls ImGui to draw the user specified panels from configs + */ + void drawPanel(); + /** + * Calls ImGui to draw the debug panel + */ + void drawDebugPanel() const; + /** + * Causes panels to be automatically positioned + * Takes 2 frames to process, due to auto sizing + */ + unsigned char first_render; + /** + * A copy of the panel configurations passed to the constructor + * These are maintaned as they must be passed to ImGui prior to each frame being rendered + */ + std::list configs; + /** + * Used by drawDebugPanel() to access visualisation properties + */ + const Visualiser &vis; +}; + +} // namespace visualiser +} // namespace flamegpu +#endif // SRC_FLAMEGPU_VISUALISER_UI_IMGUIPANEL_H_ diff --git a/src/flamegpu/visualiser/ui/Overlay.h b/src/flamegpu/visualiser/ui/Overlay.h index 56d8068..17d923f 100644 --- a/src/flamegpu/visualiser/ui/Overlay.h +++ b/src/flamegpu/visualiser/ui/Overlay.h @@ -43,7 +43,7 @@ class Overlay { * @param proj The projection matrix * @param fbo The buffer object holding the face indices */ - void render(const glm::mat4 *mv, const glm::mat4 *proj, GLuint fbo); + virtual void render(const glm::mat4 *mv, const glm::mat4 *proj, GLuint fbo); unsigned int getWidth() const { return dimensions.x; } unsigned int getHeight() const { return dimensions.y;} std::shared_ptr getShaders() const { return shaders; } diff --git a/src/flamegpu/visualiser/ui/Sprite2D.h b/src/flamegpu/visualiser/ui/Sprite2D.h index bc8acf8..10a97ad 100644 --- a/src/flamegpu/visualiser/ui/Sprite2D.h +++ b/src/flamegpu/visualiser/ui/Sprite2D.h @@ -3,7 +3,7 @@ #include #include "flamegpu/visualiser/texture/Texture2D.h" -#include "Overlay.h" +#include "flamegpu/visualiser/ui/Overlay.h" namespace flamegpu { namespace visualiser {