diff --git a/lib/third_party/imgui/CMakeLists.txt b/lib/third_party/imgui/CMakeLists.txt
index 871971743..2bef955fc 100644
--- a/lib/third_party/imgui/CMakeLists.txt
+++ b/lib/third_party/imgui/CMakeLists.txt
@@ -8,8 +8,9 @@ add_library(imgui_all_includes INTERFACE)
add_subdirectory(imgui)
add_subdirectory(cimgui)
add_subdirectory(implot)
+add_subdirectory(implot3d)
add_subdirectory(imnodes)
add_subdirectory(custom)
add_subdirectory(ColorTextEditor)
-set(IMGUI_LIBRARIES imgui_imgui imgui_cimgui imgui_implot imgui_imnodes imgui_custom imgui_color_text_editor PARENT_SCOPE)
\ No newline at end of file
+set(IMGUI_LIBRARIES imgui_imgui imgui_cimgui imgui_implot imgui_implot3d imgui_imnodes imgui_custom imgui_color_text_editor PARENT_SCOPE)
\ No newline at end of file
diff --git a/lib/third_party/imgui/implot3d/CMakeLists.txt b/lib/third_party/imgui/implot3d/CMakeLists.txt
new file mode 100644
index 000000000..bbdd45318
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.16)
+# https://github.com/brenocq/implot3d
+project(imgui_implot3d)
+
+set(CMAKE_CXX_STANDARD 17)
+
+if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD)
+ add_library(imgui_implot3d OBJECT
+ source/implot3d.cpp
+ source/implot3d_items.cpp
+ source/implot3d_demo.cpp
+ source/implot3d_meshes.cpp
+ )
+
+ target_include_directories(imgui_implot3d PUBLIC
+ include
+ )
+
+ target_link_libraries(imgui_implot3d PRIVATE imgui_includes)
+endif()
+
+target_include_directories(imgui_all_includes INTERFACE include)
diff --git a/lib/third_party/imgui/implot3d/LICENSE b/lib/third_party/imgui/implot3d/LICENSE
new file mode 100644
index 000000000..6da710d51
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Breno Cunha Queiroz
+
+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.
diff --git a/lib/third_party/imgui/implot3d/README.md b/lib/third_party/imgui/implot3d/README.md
new file mode 100644
index 000000000..a67ca9c4f
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/README.md
@@ -0,0 +1,103 @@
+# ImPlot3D
+
+
+
+
+
+
+
+
+
+ImPlot3D is an extension of [Dear ImGui](https://github.com/ocornut/imgui) that provides easy-to-use, high-performance 3D plotting functionality. Inspired by [ImPlot](https://github.com/epezent/implot), it brings a familiar and intuitive API for developers already acquainted with ImPlot. ImPlot3D is designed for rendering 3D plots with customizable markers, lines, surfaces, and meshes, providing an ideal solution for applications requiring visual representation of 3D data.
+
+## 🚀 Features
+- GPU-accelerated rendering
+- Multiple plot types:
+ - Line plots
+ - Scatter plots
+ - Surface plots
+ - Quad plots
+ - Triangle plots
+ - Mesh plots
+ - Text plots
+- Rotate, pan, and zoom 3D plots interactively
+- Several plot styling options: 10 marker types, adjustable marker sizes, line weights, outline colors, fill colors, etc.
+- 16 built-in colormaps and support for and user-added colormaps
+- Optional plot titles, axis labels, and grid labels
+- Optional and configurable legends with toggle buttons to quickly show/hide plot items
+- Default styling based on the current ImGui theme, or completely custom plot styles
+
+## 🛠️ Usage
+The ImPlot3D API is designed to feel very similar to Dear ImGui and ImPlot. You start by calling `ImPlot3D::BeginPlot()` to initialize a 3D plot, followed by plotting various data using the `PlotX` functions (e.g., `PlotLine()` , `PlotScatter()` , `PlotSurface()` ). Finally, you end the plot with ` ImPlot3D::EndPlot()` .
+
+```cpp
+float x_data[1000] = ...;
+float y_data[1000] = ...;
+float z_data[1000] = ...;
+
+ImGui::Begin("My Window");
+if (ImPlot3D::BeginPlot("My Plot")) {
+ ImPlot3D::PlotLine("My Line Plot", x_data, y_data, z_data, 1000);
+ ImPlot3D::PlotScatter("My Scatter Plot", x_data, y_data, z_data, 1000);
+ ...
+ ImPlot3D::EndPlot();
+}
+ImGui::End();
+```
+
+## 🎨 Demos
+A comprehensive example showcasing ImPlot3D features can be found in `implot3d_demo.cpp`. Add this file to your project and call `ImPlot3D::ShowDemoWindow()` in your update loop. This demo provides a wide variety of 3D plotting examples, serving as a reference for creating different types of 3D plots. The demo is regularly updated to reflect new features and plot types, so be sure to revisit it with each release!
+
+## ⚙️ Integration
+To integrate ImPlot3D into your application, follow these steps:
+
+1. Ensure you have a working Dear ImGui environment. ImPlot3D requires only Dear ImGui to function and does not depend on ImPlot.
+2. Add the following source files to your project: `implot3d.h`, `implot3d.cpp`, `implot3d_internal.h`, `implot3d_items.cpp`. Optionally, include `implot3d_demo.cpp` for examples and `implot3d_meshes.cpp` to support pre-loaded meshes.
+3. Create and destroy an ImPlot3DContext alongside your ImGuiContext:
+ ```cpp
+ ImGui::CreateContext();
+ ImPlot3D::CreateContext();
+ ...
+ ImPlot3D::DestroyContext();
+ ImGui::DestroyContext();
+ ```
+
+You're now ready to start plotting in 3D!
+
+## ⚠️ Extremely Important Note
+Dear ImGui, by default, uses 16-bit indexing, which might cause issues with high-density 3D visualizations such as complex surfaces or meshes. This can lead to assertion failures, data truncation, or visual glitches. To avoid these problems, it's recommended to:
+
+- Option 1: Enable 32-bit indices by uncommenting `#define ImDrawIdx unsigned int` in your ImGui imconfig.h file.
+- Option 2: Ensure your renderer supports the `ImGuiBackendFlags_RendererHasVtxOffset` flag. Many official ImGui backends already support this functionality.
+
+## 💬 FAQ
+#### Why ImPlot3D?
+While ImGui excels at building UI, it lacks tools for 3D data visualization. ImPlot3D fills this gap, offering a lightweight, real-time library for 3D plotting, designed with interactivity and ease of use in mind.
+
+Inspired by ImPlot, ImPlot3D provides a similar API, making it easy for existing ImPlot users to adopt. It focuses on real-time, application-level 3D visualizations for debugging, simulations, and data analysis, with performance as a priority.
+
+ImPlot is great for 2D visualizations; ImPlot3D extends this power to 3D, offering the same simplicity and speed.
+
+#### Where can I find documentation?
+The API for ImPlot3D is thoroughly commented in `implot3d.h`, and a comprehensive demo file, `implot3d_demo.cpp`, showcases all the features. You are encouraged to explore the demo file as it is regularly updated to reflect new functionality. Additionally, if you're familiar with ImPlot, you'll notice many similarities in usage patterns.
+
+#### How is ImPlot3D different from ImPlot?
+ImPlot3D is highly inspired by ImPlot, so if you're already familiar with ImPlot, you'll feel right at home. However, ImPlot3D is specifically built for 3D visualizations, offering interactive 3D rotations, panning, and scaling.
+
+### Do I need ImPlot to use ImPlot3D?
+No. ImPlot3D is a standalone library and does not depend on ImPlot. You only need Dear ImGui to get started.
+
+#### Does ImPlot3D support 2D plots?
+While you can rotate the 3D view to align with a 2D plane, ImPlot is far better suited for visualizing 2D data. ImPlot3D is specifically designed for 3D plotting and interaction, so we recommend using ImPlot for all your 2D visualization needs.
+
+#### Can I customize the appearance of plots?
+Absolutely. ImPlot3D allows you to modify plot styles, including line colors, thickness, fill opacity, and marker sizes. You can also use colormaps for surfaces and customize axis labels, grid styles, and background colors.
+
+#### Can I export 3D plots to an image?
+Not currently. You can use your OS's screen capturing tools to save a plot. ImPlot3D is designed for real-time visualization and interaction, not for creating publication-quality renders. For publication-quality output, consider exporting your data to a dedicated 3D rendering tool.
+
+#### Is ImPlot3D suitable for publication-quality visuals?
+ImPlot3D prioritizes interactivity and real-time performance. If you need high-quality visualizations, use ImPlot3D for initial exploration and then switch to tools like [MATLAB](https://www.mathworks.com/products/matlab.html), [matplotlib](https://matplotlib.org/), or [ParaView](https://www.paraview.org/) for the final output.
+
+## License
+This project is licensed under the MIT License - check [LICENSE](LICENSE) for details.
diff --git a/lib/third_party/imgui/implot3d/include/implot3d.h b/lib/third_party/imgui/implot3d/include/implot3d.h
new file mode 100644
index 000000000..11c3ac6e8
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/include/implot3d.h
@@ -0,0 +1,748 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d.h
+// Date: 2024-11-16
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] Macros and Defines
+// [SECTION] Forward declarations and basic types
+// [SECTION] Flags & Enumerations
+// [SECTION] Context
+// [SECTION] Begin/End Plot
+// [SECTION] Setup
+// [SECTION] Plot Items
+// [SECTION] Plot Utils
+// [SECTION] Miscellaneous
+// [SECTION] Styles
+// [SECTION] Demo
+// [SECTION] Debugging
+// [SECTION] ImPlot3DPoint
+// [SECTION] ImPlot3DRay
+// [SECTION] ImPlot3DPlane
+// [SECTION] ImPlot3DBox
+// [SECTION] ImPlot3DQuat
+// [SECTION] ImPlot3DStyle
+// [SECTION] Callbacks
+// [SECTION] Meshes
+
+#pragma once
+#include "imgui.h"
+#ifndef IMGUI_DISABLE
+
+//-----------------------------------------------------------------------------
+// [SECTION] Macros and Defines
+//-----------------------------------------------------------------------------
+
+#ifndef IMPLOT3D_API
+#define IMPLOT3D_API
+#endif
+
+#define IMPLOT3D_VERSION "0.1" // ImPlot3D version
+#define IMPLOT3D_AUTO -1 // Deduce variable automatically
+#define IMPLOT3D_AUTO_COL ImVec4(0, 0, 0, -1) // Deduce color automatically
+#define IMPLOT3D_TMP template IMPLOT3D_API
+
+//-----------------------------------------------------------------------------
+// [SECTION] Forward declarations and basic types
+//-----------------------------------------------------------------------------
+
+// Forward declarations
+struct ImPlot3DContext;
+struct ImPlot3DStyle;
+struct ImPlot3DPoint;
+struct ImPlot3DRay;
+struct ImPlot3DPlane;
+struct ImPlot3DBox;
+struct ImPlot3DRange;
+struct ImPlot3DQuat;
+
+// Enums
+typedef int ImPlot3DCond; // -> ImPlot3DCond_ // Enum: Condition for flags
+typedef int ImPlot3DCol; // -> ImPlot3DCol_ // Enum: Styling colors
+typedef int ImPlot3DStyleVar; // -> ImPlot3DStyleVar_ // Enum: Style variables
+typedef int ImPlot3DMarker; // -> ImPlot3DMarker_ // Enum: Marker styles
+typedef int ImPlot3DLocation; // -> ImPlot3DLocation_ // Enum: Locations
+typedef int ImAxis3D; // -> ImAxis3D_ // Enum: Axis indices
+typedef int ImPlane3D; // -> ImPlane3D_ // Enum: Plane indices
+typedef int ImPlot3DColormap; // -> ImPlot3DColormap_ // Enum: Colormaps
+
+// Flags
+typedef int ImPlot3DFlags; // -> ImPlot3DFlags_ // Flags: for BeginPlot()
+typedef int ImPlot3DItemFlags; // -> ImPlot3DItemFlags_ // Flags: Item flags
+typedef int ImPlot3DScatterFlags; // -> ImPlot3DScatterFlags_ // Flags: Scatter plot flags
+typedef int ImPlot3DLineFlags; // -> ImPlot3DLineFlags_ // Flags: Line plot flags
+typedef int ImPlot3DTriangleFlags; // -> ImPlot3DTriangleFlags_ // Flags: Triangle plot flags
+typedef int ImPlot3DQuadFlags; // -> ImPlot3DQuadFlags_ // Flags: QuadFplot flags
+typedef int ImPlot3DSurfaceFlags; // -> ImPlot3DSurfaceFlags_ // Flags: Surface plot flags
+typedef int ImPlot3DMeshFlags; // -> ImPlot3DMeshFlags_ // Flags: Mesh plot flags
+typedef int ImPlot3DLegendFlags; // -> ImPlot3DLegendFlags_ // Flags: Legend flags
+typedef int ImPlot3DAxisFlags; // -> ImPlot3DAxisFlags_ // Flags: Axis flags
+
+//-----------------------------------------------------------------------------
+// [SECTION] Flags & Enumerations
+//-----------------------------------------------------------------------------
+
+// Flags for ImPlot3D::BeginPlot()
+enum ImPlot3DFlags_ {
+ ImPlot3DFlags_None = 0, // Default
+ ImPlot3DFlags_NoTitle = 1 << 0, // Hide plot title
+ ImPlot3DFlags_NoLegend = 1 << 1, // Hide plot legend
+ ImPlot3DFlags_NoMouseText = 1 << 2, // Hide mouse position in plot coordinates
+ ImPlot3DFlags_NoClip = 1 << 3, // Disable 3D box clipping
+ ImPlot3DFlags_CanvasOnly = ImPlot3DFlags_NoTitle | ImPlot3DFlags_NoLegend | ImPlot3DFlags_NoMouseText,
+};
+
+// Represents a condition for SetupAxisLimits etc. (same as ImGuiCond, but we only support a subset of those enums)
+enum ImPlot3DCond_ {
+ ImPlot3DCond_None = ImGuiCond_None, // No condition (always set the variable), same as _Always
+ ImPlot3DCond_Always = ImGuiCond_Always, // No condition (always set the variable)
+ ImPlot3DCond_Once = ImGuiCond_Once, // Set the variable once per runtime session (only the first call will succeed)
+};
+
+enum ImPlot3DCol_ {
+ // Item colors
+ ImPlot3DCol_Line = 0, // Line color
+ ImPlot3DCol_Fill, // Fill color
+ ImPlot3DCol_MarkerOutline, // Marker outline color
+ ImPlot3DCol_MarkerFill, // Marker fill color
+ // Plot colors
+ ImPlot3DCol_TitleText, // Title color
+ ImPlot3DCol_InlayText, // Color for texts appearing inside of plots
+ ImPlot3DCol_FrameBg, // Frame background color
+ ImPlot3DCol_PlotBg, // Plot area background color
+ ImPlot3DCol_PlotBorder, // Plot area border color
+ // Legend colors
+ ImPlot3DCol_LegendBg, // Legend background color
+ ImPlot3DCol_LegendBorder, // Legend border color
+ ImPlot3DCol_LegendText, // Legend text color
+ // Axis colors
+ ImPlot3DCol_AxisText, // Axis label and tick lables color
+ ImPlot3DCol_AxisGrid, // Axis grid color
+ ImPlot3DCol_AxisTick, // Axis tick color (defaults to AxisGrid)
+ ImPlot3DCol_COUNT,
+};
+
+// Plot styling variables
+enum ImPlot3DStyleVar_ {
+ // Item style
+ ImPlot3DStyleVar_LineWeight, // float, plot item line weight in pixels
+ ImPlot3DStyleVar_Marker, // int, marker specification
+ ImPlot3DStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius")
+ ImPlot3DStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels
+ ImPlot3DStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills
+ // Plot style
+ ImPlot3DStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot
+ ImPlot3DStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk
+ ImPlot3DStyleVar_PlotPadding, // ImVec2, padding between widget frame and plot area, labels, or outside legends (i.e. main padding)
+ ImPlot3DStyleVar_LabelPadding, // ImVec2, padding between axes labels, tick labels, and plot edge
+ // Legend style
+ ImPlot3DStyleVar_LegendPadding, // ImVec2, legend padding from plot edges
+ ImPlot3DStyleVar_LegendInnerPadding, // ImVec2, legend inner padding from legend edges
+ ImPlot3DStyleVar_LegendSpacing, // ImVec2, spacing between legend entries
+ ImPlot3DStyleVar_COUNT
+};
+
+enum ImPlot3DMarker_ {
+ ImPlot3DMarker_None = -1, // No marker
+ ImPlot3DMarker_Circle, // Circle marker (default)
+ ImPlot3DMarker_Square, // Square maker
+ ImPlot3DMarker_Diamond, // Diamond marker
+ ImPlot3DMarker_Up, // Upward-pointing triangle marker
+ ImPlot3DMarker_Down, // Downward-pointing triangle marker
+ ImPlot3DMarker_Left, // Leftward-pointing triangle marker
+ ImPlot3DMarker_Right, // Rightward-pointing triangle marker
+ ImPlot3DMarker_Cross, // Cross marker (not fillable)
+ ImPlot3DMarker_Plus, // Plus marker (not fillable)
+ ImPlot3DMarker_Asterisk, // Asterisk marker (not fillable)
+ ImPlot3DMarker_COUNT
+};
+
+// Flags for items
+enum ImPlot3DItemFlags_ {
+ ImPlot3DItemFlags_None = 0, // Default
+ ImPlot3DItemFlags_NoLegend = 1 << 0, // The item won't have a legend entry displayed
+ ImPlot3DItemFlags_NoFit = 1 << 1, // The item won't be considered for plot fits
+};
+
+// Flags for PlotScatter
+enum ImPlot3DScatterFlags_ {
+ ImPlot3DScatterFlags_None = 0, // Default
+ ImPlot3DScatterFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DScatterFlags_NoFit = ImPlot3DItemFlags_NoFit,
+};
+
+// Flags for PlotLine
+enum ImPlot3DLineFlags_ {
+ ImPlot3DLineFlags_None = 0, // Default
+ ImPlot3DLineFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DLineFlags_NoFit = ImPlot3DItemFlags_NoFit,
+ ImPlot3DLineFlags_Segments = 1 << 10, // A line segment will be rendered from every two consecutive points
+ ImPlot3DLineFlags_Loop = 1 << 11, // The last and first point will be connected to form a closed loop
+ ImPlot3DLineFlags_SkipNaN = 1 << 12, // NaNs values will be skipped instead of rendered as missing data
+};
+
+// Flags for PlotTriangle
+enum ImPlot3DTriangleFlags_ {
+ ImPlot3DTriangleFlags_None = 0, // Default
+ ImPlot3DTriangleFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DTriangleFlags_NoFit = ImPlot3DItemFlags_NoFit,
+};
+
+// Flags for PlotQuad
+enum ImPlot3DQuadFlags_ {
+ ImPlot3DQuadFlags_None = 0, // Default
+ ImPlot3DQuadFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DQuadFlags_NoFit = ImPlot3DItemFlags_NoFit,
+};
+
+// Flags for PlotSurface
+enum ImPlot3DSurfaceFlags_ {
+ ImPlot3DSurfaceFlags_None = 0, // Default
+ ImPlot3DSurfaceFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DSurfaceFlags_NoFit = ImPlot3DItemFlags_NoFit,
+};
+
+// Flags for PlotMesh
+enum ImPlot3DMeshFlags_ {
+ ImPlot3DMeshFlags_None = 0, // Default
+ ImPlot3DMeshFlags_NoLegend = ImPlot3DItemFlags_NoLegend,
+ ImPlot3DMeshFlags_NoFit = ImPlot3DItemFlags_NoFit,
+};
+
+// Flags for legends
+enum ImPlot3DLegendFlags_ {
+ ImPlot3DLegendFlags_None = 0, // Default
+ ImPlot3DLegendFlags_NoButtons = 1 << 0, // Legend icons will not function as hide/show buttons
+ ImPlot3DLegendFlags_NoHighlightItem = 1 << 1, // Plot items will not be highlighted when their legend entry is hovered
+ ImPlot3DLegendFlags_Horizontal = 1 << 2, // Legend entries will be displayed horizontally
+};
+
+// Used to position legend on a plot
+enum ImPlot3DLocation_ {
+ ImPlot3DLocation_Center = 0, // Center-center
+ ImPlot3DLocation_North = 1 << 0, // Top-center
+ ImPlot3DLocation_South = 1 << 1, // Bottom-center
+ ImPlot3DLocation_West = 1 << 2, // Center-left
+ ImPlot3DLocation_East = 1 << 3, // Center-right
+ ImPlot3DLocation_NorthWest = ImPlot3DLocation_North | ImPlot3DLocation_West, // Top-left
+ ImPlot3DLocation_NorthEast = ImPlot3DLocation_North | ImPlot3DLocation_East, // Top-right
+ ImPlot3DLocation_SouthWest = ImPlot3DLocation_South | ImPlot3DLocation_West, // Bottom-left
+ ImPlot3DLocation_SouthEast = ImPlot3DLocation_South | ImPlot3DLocation_East // Bottom-right
+};
+
+// Flags for axis
+enum ImPlot3DAxisFlags_ {
+ ImPlot3DAxisFlags_None = 0, // Default
+ ImPlot3DAxisFlags_NoLabel = 1 << 0, // No axis label will be displayed
+ ImPlot3DAxisFlags_NoGridLines = 1 << 1, // No grid lines will be displayed
+ ImPlot3DAxisFlags_NoTickMarks = 1 << 2, // No tick marks will be displayed
+ ImPlot3DAxisFlags_NoTickLabels = 1 << 3, // No tick labels will be displayed
+ ImPlot3DAxisFlags_LockMin = 1 << 4, // The axis minimum value will be locked when panning/zooming
+ ImPlot3DAxisFlags_LockMax = 1 << 5, // The axis maximum value will be locked when panning/zooming
+ ImPlot3DAxisFlags_AutoFit = 1 << 6, // Axis will be auto-fitting to data extents
+ ImPlot3DAxisFlags_Lock = ImPlot3DAxisFlags_LockMin | ImPlot3DAxisFlags_LockMax,
+ ImPlot3DAxisFlags_NoDecorations = ImPlot3DAxisFlags_NoLabel | ImPlot3DAxisFlags_NoGridLines | ImPlot3DAxisFlags_NoTickLabels,
+};
+
+// Axis indices
+enum ImAxis3D_ {
+ ImAxis3D_X = 0,
+ ImAxis3D_Y,
+ ImAxis3D_Z,
+ ImAxis3D_COUNT,
+};
+
+// Plane indices
+enum ImPlane3D_ {
+ ImPlane3D_YZ = 0,
+ ImPlane3D_XZ,
+ ImPlane3D_XY,
+ ImPlane3D_COUNT,
+};
+
+// Colormaps
+enum ImPlot3DColormap_ {
+ ImPlot3DColormap_Deep = 0, // Same as seaborn "deep"
+ ImPlot3DColormap_Dark = 1, // Same as matplotlib "Set1"
+ ImPlot3DColormap_Pastel = 2, // Same as matplotlib "Pastel1"
+ ImPlot3DColormap_Paired = 3, // Same as matplotlib "Paired"
+ ImPlot3DColormap_Viridis = 4, // Same as matplotlib "viridis"
+ ImPlot3DColormap_Plasma = 5, // Same as matplotlib "plasma"
+ ImPlot3DColormap_Hot = 6, // Same as matplotlib/MATLAB "hot"
+ ImPlot3DColormap_Cool = 7, // Same as matplotlib/MATLAB "cool"
+ ImPlot3DColormap_Pink = 8, // Same as matplotlib/MATLAB "pink"
+ ImPlot3DColormap_Jet = 9, // Same as matplotlib/MATLAB "jet"
+ ImPlot3DColormap_Twilight = 10, // Same as matplotlib "twilight"
+ ImPlot3DColormap_RdBu = 11, // Same as matplotlib "RdBu"
+ ImPlot3DColormap_BrBG = 12, // Same as matplotlib "BrGB"
+ ImPlot3DColormap_PiYG = 13, // Same as matplotlib "PiYG"
+ ImPlot3DColormap_Spectral = 14, // Same as matplotlib "Spectral"
+ ImPlot3DColormap_Greys = 15, // White/black
+};
+
+namespace ImPlot3D {
+
+//-----------------------------------------------------------------------------
+// [SECTION] Context
+//-----------------------------------------------------------------------------
+IMPLOT3D_API ImPlot3DContext* CreateContext();
+IMPLOT3D_API void DestroyContext(ImPlot3DContext* ctx = nullptr);
+IMPLOT3D_API ImPlot3DContext* GetCurrentContext();
+IMPLOT3D_API void SetCurrentContext(ImPlot3DContext* ctx);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Begin/End Plot
+//-----------------------------------------------------------------------------
+
+// Starts a 3D plotting context. If this function returns true, EndPlot() MUST
+// be called! You are encouraged to use the following convention:
+//
+// if (ImPlot3D::BeginPlot(...)) {
+// ImPlot3D::PlotLine(...);
+// ...
+// ImPlot3D::EndPlot();
+// }
+//
+// Important notes:
+// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID
+// collisions or don't want to display a title in the plot, use double hashes
+// (e.g. "MyPlot##HiddenIdText" or "##NoTitle").
+// - #size is the **frame** size of the plot widget, not the plot area.
+IMPLOT3D_API bool BeginPlot(const char* title_id, const ImVec2& size = ImVec2(-1, 0), ImPlot3DFlags flags = 0);
+IMPLOT3D_API void EndPlot(); // Only call if BeginPlot() returns true!
+
+//-----------------------------------------------------------------------------
+// [SECTION] Setup
+//-----------------------------------------------------------------------------
+
+// The following API allows you to setup and customize various aspects of the
+// current plot. The functions should be called immediately after BeginPlot()
+// and before any other API calls. Typical usage is as follows:
+
+// if (ImPlot3D::BeginPlot(...)) { 1) Begin a new plot
+// ImPlot3D::SetupAxis(ImAxis3D_X, "My X-Axis"); 2) Make Setup calls
+// ImPlot3D::SetupAxis(ImAxis3D_Y, "My Y-Axis");
+// ImPlot3D::SetupLegend(ImPlotLocation_North);
+// ...
+// ImPlot3D::SetupFinish(); 3) [Optional] Explicitly finish setup
+// ImPlot3D::PlotLine(...); 4) Plot items
+// ...
+// ImPlot3D::EndPlot(); 5) End the plot
+// }
+//
+// Important notes:
+//
+// - Always call Setup code at the top of your BeginPlot conditional statement.
+// - Setup is locked once you start plotting or explicitly call SetupFinish.
+// Do NOT call Setup code after you begin plotting or after you make
+// any non-Setup API calls (e.g. utils like PlotToPixels also lock Setup).
+// - Calling SetupFinish is OPTIONAL, but probably good practice. If you do not
+// call it yourself, then the first subsequent plotting or utility function will
+// call it for you.
+
+// Enables an axis or sets the label and/or flags for an existing axis. Leave #label = nullptr for no label
+IMPLOT3D_API void SetupAxis(ImAxis3D axis, const char* label = nullptr, ImPlot3DAxisFlags flags = 0);
+
+IMPLOT3D_API void SetupAxisLimits(ImAxis3D axis, double v_min, double v_max, ImPlot3DCond cond = ImPlot3DCond_Once);
+
+// Sets the label and/or flags for primary X/Y/Z axes (shorthand for three calls to SetupAxis)
+IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags = 0, ImPlot3DAxisFlags y_flags = 0, ImPlot3DAxisFlags z_flags = 0);
+
+// Sets the X/Y/Z axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits)
+IMPLOT3D_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, ImPlot3DCond cond = ImPlot3DCond_Once);
+
+IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plot Items
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags = 0, int offset = 0, int stride = sizeof(T));
+
+IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags = 0, int offset = 0, int stride = sizeof(T));
+
+IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags = 0, int offset = 0, int stride = sizeof(T));
+
+IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags = 0, int offset = 0, int stride = sizeof(T));
+
+IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, ImPlot3DSurfaceFlags flags = 0, int offset = 0, int stride = sizeof(T));
+
+IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0);
+
+// Plots a centered text label at point x,y,z. It is possible to set the text angle in radians and offset in pixels
+IMPLOT3D_API void PlotText(const char* text, float x, float y, float z, float angle = 0.0f, const ImVec2& pix_offset = ImVec2(0, 0));
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plot Utils
+//-----------------------------------------------------------------------------
+
+// Convert a position in the current plot's coordinate system to pixels
+IMPLOT3D_API ImVec2 PlotToPixels(const ImPlot3DPoint& point);
+IMPLOT3D_API ImVec2 PlotToPixels(double x, double y, double z);
+// Convert a pixel coordinate to a ray in the current plot's coordinate system
+IMPLOT3D_API ImPlot3DRay PixelsToPlotRay(const ImVec2& pix);
+IMPLOT3D_API ImPlot3DRay PixelsToPlotRay(double x, double y);
+// Convert a pixel coordinate to a point in an axis plane in the current plot's coordinate system
+IMPLOT3D_API ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask = true);
+IMPLOT3D_API ImPlot3DPoint PixelsToPlotPlane(double x, double y, ImPlane3D plane, bool mask = true);
+
+IMPLOT3D_API ImVec2 GetPlotPos(); // Get the current plot position (top-left) in pixels
+IMPLOT3D_API ImVec2 GetPlotSize(); // Get the current plot size in pixels
+
+//-----------------------------------------------------------------------------
+// [SECTION] Miscellaneous
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_API ImDrawList* GetPlotDrawList();
+
+//-----------------------------------------------------------------------------
+// [SECTION] Styles
+//-----------------------------------------------------------------------------
+
+// Get current style
+IMPLOT3D_API ImPlot3DStyle& GetStyle();
+
+// Set color styles
+IMPLOT3D_API void StyleColorsAuto(ImPlot3DStyle* dst = nullptr); // Set colors with ImGui style
+IMPLOT3D_API void StyleColorsDark(ImPlot3DStyle* dst = nullptr); // Set colors with dark style
+IMPLOT3D_API void StyleColorsLight(ImPlot3DStyle* dst = nullptr); // Set colors with light style
+IMPLOT3D_API void StyleColorsClassic(ImPlot3DStyle* dst = nullptr); // Set colors with classic style
+
+// Temporarily modify a style color. Don't forget to call PopStyleColor!
+IMPLOT3D_API void PushStyleColor(ImPlot3DCol idx, ImU32 col);
+IMPLOT3D_API void PushStyleColor(ImPlot3DCol idx, const ImVec4& col);
+// Undo temporary style color modification(s). Undo multiple pushes at once by increasing count
+IMPLOT3D_API void PopStyleColor(int count = 1);
+
+// Temporarily modify a style variable of float type. Don't forget to call PopStyleVar!
+IMPLOT3D_API void PushStyleVar(ImPlot3DStyleVar idx, float val);
+// Temporarily modify a style variable of int type. Don't forget to call PopStyleVar!
+IMPLOT3D_API void PushStyleVar(ImPlot3DStyleVar idx, int val);
+// Temporarily modify a style variable of ImVec2 type. Don't forget to call PopStyleVar!
+IMPLOT3D_API void PushStyleVar(ImPlot3DStyleVar idx, const ImVec2& val);
+// Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count
+IMPLOT3D_API void PopStyleVar(int count = 1);
+
+// Set the line color and weight for the next item only
+IMPLOT3D_API void SetNextLineStyle(const ImVec4& col = IMPLOT3D_AUTO_COL, float weight = IMPLOT3D_AUTO);
+// Set the fill color for the next item only
+IMPLOT3D_API void SetNextFillStyle(const ImVec4& col = IMPLOT3D_AUTO_COL, float alpha_mod = IMPLOT3D_AUTO);
+// Set the marker style for the next item only
+IMPLOT3D_API void SetNextMarkerStyle(ImPlot3DMarker marker = IMPLOT3D_AUTO, float size = IMPLOT3D_AUTO, const ImVec4& fill = IMPLOT3D_AUTO_COL, float weight = IMPLOT3D_AUTO, const ImVec4& outline = IMPLOT3D_AUTO_COL);
+
+// Get color
+IMPLOT3D_API ImVec4 GetStyleColorVec4(ImPlot3DCol idx);
+IMPLOT3D_API ImU32 GetStyleColorU32(ImPlot3DCol idx);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Colormaps
+//-----------------------------------------------------------------------------
+
+// Item styling is based on colormaps when the relevant ImPlot3DCol_XXX is set to
+// IMPLOT3D_AUTO_COL (default). Several built-in colormaps are available. You can
+// add and then push/pop your own colormaps as well. To permanently set a colormap,
+// modify the Colormap index member of your ImPlot3DStyle.
+
+// Colormap data will be ignored and a custom color will be used if you have done one of the following:
+// 1) Modified an item style color in your ImPlot3DStyle to anything other than IMPLOT3D_AUTO_COL.
+// 3) Set the next item style with a SetNextXXXStyle function.
+
+// Add a new colormap. The color data will be copied. The colormap can be used by pushing either the returned index or the
+// string name with PushColormap. The colormap name must be unique and the size must be greater than 1. You will receive
+// an assert otherwise! By default colormaps are considered to be qualitative (i.e. discrete). If you want to create a
+// continuous colormap, set #qual=false. This will treat the colors you provide as keys, and ImPlot3D will build a linearly
+// interpolated lookup table. The memory footprint of this table will be exactly ((size-1)*255+1)*4 bytes.
+
+IMPLOT3D_API ImPlot3DColormap AddColormap(const char* name, const ImVec4* cols, int size, bool qual = true);
+IMPLOT3D_API ImPlot3DColormap AddColormap(const char* name, const ImU32* cols, int size, bool qual = true);
+
+// Returns the number of available colormaps (i.e. the built-in + user-added count)
+IMPLOT3D_API int GetColormapCount();
+// Returns a null terminated string name for a colormap given an index. Returns nullptr if index is invalid
+IMPLOT3D_API const char* GetColormapName(ImPlot3DColormap cmap);
+// Returns an index number for a colormap given a valid string name. Returns -1 if name is invalid
+IMPLOT3D_API ImPlot3DColormap GetColormapIndex(const char* name);
+
+// Temporarily switch to one of the built-in (i.e. ImPlot3DColormap_XXX) or user-added colormaps (i.e. a return value of AddColormap). Don't forget to call PopColormap!
+IMPLOT3D_API void PushColormap(ImPlot3DColormap cmap);
+// Push a colormap by string name. Use built-in names such as "Default", "Deep", "Jet", etc. or a string you provided to AddColormap. Don't forget to call PopColormap!
+IMPLOT3D_API void PushColormap(const char* name);
+// Undo temporary colormap modification(s). Undo multiple pushes at once by increasing count
+IMPLOT3D_API void PopColormap(int count = 1);
+
+// Returns the next color from the current colormap and advances the colormap for the current plot
+// Can also be used with no return value to skip colors if desired. You need to call it between Begin/EndPlot!
+IMPLOT3D_API ImVec4 NextColormapColor();
+
+// Returns the size of a colormap
+IMPLOT3D_API int GetColormapSize(ImPlot3DColormap cmap = IMPLOT3D_AUTO);
+// Returns a color from a colormap given an index >= 0 (modulo will be performed)
+IMPLOT3D_API ImVec4 GetColormapColor(int idx, ImPlot3DColormap cmap = IMPLOT3D_AUTO);
+// Sample a color from the current colormap given t between 0 and 1
+IMPLOT3D_API ImVec4 SampleColormap(float t, ImPlot3DColormap cmap = IMPLOT3D_AUTO);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Demo
+//-----------------------------------------------------------------------------
+// Add implot3d_demo.cpp to your sources to use methods in this section
+
+// Shows the ImPlot3D demo window
+IMPLOT3D_API void ShowDemoWindow(bool* p_open = nullptr);
+
+// Shows ImPlot3D style editor block (not a window)
+IMPLOT3D_API void ShowStyleEditor(ImPlot3DStyle* ref = nullptr);
+
+} // namespace ImPlot3D
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DPoint
+//-----------------------------------------------------------------------------
+
+// ImPlot3DPoint: 3D vector to store points in 3D
+struct ImPlot3DPoint {
+ float x, y, z;
+ constexpr ImPlot3DPoint() : x(0.0f), y(0.0f), z(0.0f) {}
+ constexpr ImPlot3DPoint(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
+
+ // Accessors
+ float& operator[](size_t idx) {
+ IM_ASSERT(idx == 0 || idx == 1 || idx == 2);
+ return ((float*)(void*)(char*)this)[idx];
+ }
+ float operator[](size_t idx) const {
+ IM_ASSERT(idx == 0 || idx == 1 || idx == 2);
+ return ((const float*)(const void*)(const char*)this)[idx];
+ }
+
+ // Binary operators
+ ImPlot3DPoint operator*(float rhs) const;
+ ImPlot3DPoint operator/(float rhs) const;
+ ImPlot3DPoint operator+(const ImPlot3DPoint& rhs) const;
+ ImPlot3DPoint operator-(const ImPlot3DPoint& rhs) const;
+ ImPlot3DPoint operator*(const ImPlot3DPoint& rhs) const;
+ ImPlot3DPoint operator/(const ImPlot3DPoint& rhs) const;
+
+ // Unary operator
+ ImPlot3DPoint operator-() const;
+
+ // Compound assignment operators
+ ImPlot3DPoint& operator*=(float rhs);
+ ImPlot3DPoint& operator/=(float rhs);
+ ImPlot3DPoint& operator+=(const ImPlot3DPoint& rhs);
+ ImPlot3DPoint& operator-=(const ImPlot3DPoint& rhs);
+ ImPlot3DPoint& operator*=(const ImPlot3DPoint& rhs);
+ ImPlot3DPoint& operator/=(const ImPlot3DPoint& rhs);
+
+ // Comparison operators
+ bool operator==(const ImPlot3DPoint& rhs) const;
+ bool operator!=(const ImPlot3DPoint& rhs) const;
+
+ // Dot product
+ float Dot(const ImPlot3DPoint& rhs) const;
+
+ // Cross product
+ ImPlot3DPoint Cross(const ImPlot3DPoint& rhs) const;
+
+ // Get vector length
+ float Length() const;
+
+ // Get vector squared length
+ float LengthSquared() const;
+
+ // Normalize to unit length
+ void Normalize();
+
+ // Return vector normalized to unit length
+ ImPlot3DPoint Normalized() const;
+
+ // Friend binary operators to allow commutative behavior
+ friend ImPlot3DPoint operator*(float lhs, const ImPlot3DPoint& rhs);
+
+ // Check if the point is NaN
+ bool IsNaN() const;
+
+#ifdef IMPLOT3D_POINT_CLASS_EXTRA
+ IMPLOT3D_POINT_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImPlot3DPoint
+#endif
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DRay
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DRay {
+ ImPlot3DPoint Origin;
+ ImPlot3DPoint Direction;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DPlane
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DPlane {
+ ImPlot3DPoint Point;
+ ImPlot3DPoint Normal;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DBox
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DBox {
+ ImPlot3DPoint Min;
+ ImPlot3DPoint Max;
+
+ // Default constructor
+ constexpr ImPlot3DBox() : Min(ImPlot3DPoint()), Max(ImPlot3DPoint()) {}
+
+ // Constructor with two points
+ constexpr ImPlot3DBox(const ImPlot3DPoint& min, const ImPlot3DPoint& max) : Min(min), Max(max) {}
+
+ // Method to expand the box to include a point
+ void Expand(const ImPlot3DPoint& point);
+
+ // Method to check if a point is inside the box
+ bool Contains(const ImPlot3DPoint& point) const;
+
+ // Method to clip a line segment against the box
+ bool ClipLineSegment(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, ImPlot3DPoint& p0_clipped, ImPlot3DPoint& p1_clipped) const;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DRange
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DRange {
+ float Min;
+ float Max;
+
+ constexpr ImPlot3DRange() : Min(0.0f), Max(0.0f) {}
+ constexpr ImPlot3DRange(float min, float max) : Min(min), Max(max) {}
+
+ void Expand(float value);
+ bool Contains(float value) const;
+ float Size() const { return Max - Min; }
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DQuat
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DQuat {
+ float x, y, z, w;
+
+ // Constructors
+ constexpr ImPlot3DQuat() : x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}
+ constexpr ImPlot3DQuat(float _x, float _y, float _z, float _w) : x(_x), y(_y), z(_z), w(_w) {}
+
+ ImPlot3DQuat(float _angle, const ImPlot3DPoint& _axis);
+
+ // Set quaternion from two vectors
+ static ImPlot3DQuat FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3DPoint& v1);
+
+ // Get quaternion length
+ float Length() const;
+
+ // Get normalized quaternion
+ ImPlot3DQuat Normalized() const;
+
+ // Conjugate of the quaternion
+ ImPlot3DQuat Conjugate() const;
+
+ // Inverse of the quaternion
+ ImPlot3DQuat Inverse() const;
+
+ // Binary operators
+ ImPlot3DQuat operator*(const ImPlot3DQuat& rhs) const;
+
+ // Normalize the quaternion in place
+ ImPlot3DQuat& Normalize();
+
+ // Rotate a 3D point using the quaternion
+ ImPlot3DPoint operator*(const ImPlot3DPoint& point) const;
+
+ // Comparison operators
+ bool operator==(const ImPlot3DQuat& rhs) const;
+ bool operator!=(const ImPlot3DQuat& rhs) const;
+
+ // Interpolate between two quaternions
+ static ImPlot3DQuat Slerp(const ImPlot3DQuat& q1, const ImPlot3DQuat& q2, float t);
+
+ // Get quaternion dot product
+ float Dot(const ImPlot3DQuat& rhs) const;
+
+#ifdef IMPLOT3D_QUAT_CLASS_EXTRA
+ IMPLOT3D_QUAT_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImPlot3DQuat
+#endif
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DStyle
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DStyle {
+ // Item style
+ float LineWeight; // Line weight in pixels
+ int Marker; // Default marker type (ImPlot3DMarker_None)
+ float MarkerSize; // Marker size in pixels (roughly the marker's "radius")
+ float MarkerWeight; // Marker outline weight in pixels
+ float FillAlpha; // Alpha modifier applied to plot fills
+ // Plot style
+ ImVec2 PlotDefaultSize;
+ ImVec2 PlotMinSize;
+ ImVec2 PlotPadding;
+ ImVec2 LabelPadding;
+ // Legend style
+ ImVec2 LegendPadding; // Legend padding from plot edges
+ ImVec2 LegendInnerPadding; // Legend inner padding from legend edges
+ ImVec2 LegendSpacing; // Spacing between legend entries
+ // Colors
+ ImVec4 Colors[ImPlot3DCol_COUNT];
+ // Colormap
+ ImPlot3DColormap Colormap; // The current colormap. Set this to either an ImPlot3DColormap_ enum or an index returned by AddColormap
+ // Constructor
+ IMPLOT3D_API ImPlot3DStyle();
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Callbacks
+//-----------------------------------------------------------------------------
+
+// Callback signature for axis tick label formatter
+typedef int (*ImPlot3DFormatter)(float value, char* buff, int size, void* user_data);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Meshes
+//-----------------------------------------------------------------------------
+
+namespace ImPlot3D {
+
+// Cube
+constexpr int CUBE_VTX_COUNT = 8; // Number of cube vertices
+constexpr int CUBE_IDX_COUNT = 36; // Number of cube indices (12 triangles)
+extern ImPlot3DPoint cube_vtx[CUBE_VTX_COUNT]; // Cube vertices
+extern unsigned int cube_idx[CUBE_IDX_COUNT]; // Cube indices
+
+// Sphere
+constexpr int SPHERE_VTX_COUNT = 162; // Number of sphere vertices for 128 triangles
+constexpr int SPHERE_IDX_COUNT = 960; // Number of sphere indices (128 triangles)
+extern ImPlot3DPoint sphere_vtx[SPHERE_VTX_COUNT]; // Sphere vertices
+extern unsigned int sphere_idx[SPHERE_IDX_COUNT]; // Sphere indices
+
+// Duck (Rubber Duck by Poly by Google [CC-BY] via Poly Pizza)
+constexpr int DUCK_VTX_COUNT = 254; // Number of duck vertices
+constexpr int DUCK_IDX_COUNT = 1428; // Number of duck indices
+extern ImPlot3DPoint duck_vtx[DUCK_VTX_COUNT]; // Duck vertices
+extern unsigned int duck_idx[DUCK_IDX_COUNT]; // Duck indices
+
+} // namespace ImPlot3D
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/lib/third_party/imgui/implot3d/include/implot3d_internal.h b/lib/third_party/imgui/implot3d/include/implot3d_internal.h
new file mode 100644
index 000000000..b0ce4f0d0
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/include/implot3d_internal.h
@@ -0,0 +1,687 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d_internal.h
+// Date: 2024-11-17
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] Constants
+// [SECTION] Generic Helpers
+// [SECTION] Forward Declarations
+// [SECTION] Callbacks
+// [SECTION] Structs
+// [SECTION] Context Pointer
+// [SECTION] Context Utils
+// [SECTION] Style Utils
+// [SECTION] Item Utils
+// [SECTION] Plot Utils
+// [SECTION] Setup Utils
+// [SECTION] Formatter
+// [SECTION] Locator
+
+#pragma once
+
+#ifndef IMPLOT3D_VERSION
+#include "implot3d.h"
+#endif
+
+#ifndef IMGUI_DISABLE
+#include "imgui_internal.h"
+
+//-----------------------------------------------------------------------------
+// [SECTION] Constants
+//-----------------------------------------------------------------------------
+
+// Default label format for axis labels
+#define IMPLOT3D_LABEL_FORMAT "%g"
+// Max character size for tick labels
+#define IMPLOT3D_LABEL_MAX_SIZE 32
+
+//-----------------------------------------------------------------------------
+// [SECTION] Generic Helpers
+//-----------------------------------------------------------------------------
+
+namespace ImPlot3D {
+
+// Computes the common (base-10) logarithm
+static inline float ImLog10(float x) { return log10f(x); }
+// Returns true if flag is set
+template
+static inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; }
+// Flips a flag in a flagset
+template
+static inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; }
+template
+static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); }
+// Returns true if val is NAN
+static inline bool ImNan(float val) { return isnan(val); }
+// Returns true if val is NAN or INFINITY
+static inline bool ImNanOrInf(float val) { return !(val >= -FLT_MAX && val <= FLT_MAX) || ImNan(val); }
+// Turns NANs to 0s
+static inline double ImConstrainNan(float val) { return ImNan(val) ? 0 : val; }
+// Turns infinity to floating point maximums
+static inline double ImConstrainInf(double val) { return val >= FLT_MAX ? FLT_MAX : val <= -FLT_MAX ? -FLT_MAX
+ : val; }
+// True if two numbers are approximately equal using units in the last place.
+static inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1 - v2) < FLT_EPSILON * ImAbs(v1 + v2) * ulp || ImAbs(v1 - v2) < FLT_MIN; }
+// Set alpha channel of 32-bit color from float in range [0.0 1.0]
+static inline ImU32 ImAlphaU32(ImU32 col, float alpha) {
+ return col & ~((ImU32)((1.0f - alpha) * 255) << IM_COL32_A_SHIFT);
+}
+// Mix color a and b by factor s in [0 256]
+static inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) {
+#ifdef IMPLOT3D_MIX64
+ const ImU32 af = 256 - s;
+ const ImU32 bf = s;
+ const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24);
+ const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24);
+ const ImU64 mix = (al * af + bl * bf);
+ return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8);
+#else
+ const ImU32 af = 256 - s;
+ const ImU32 bf = s;
+ const ImU32 al = (a & 0x00ff00ff);
+ const ImU32 ah = (a & 0xff00ff00) >> 8;
+ const ImU32 bl = (b & 0x00ff00ff);
+ const ImU32 bh = (b & 0xff00ff00) >> 8;
+ const ImU32 ml = (al * af + bl * bf);
+ const ImU32 mh = (ah * af + bh * bf);
+ return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8);
+#endif
+}
+
+} // namespace ImPlot3D
+
+//-----------------------------------------------------------------------------
+// [SECTION] Forward Declarations
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DTicker;
+
+//------------------------------------------------------------------------------
+// [SECTION] Callbacks
+//------------------------------------------------------------------------------
+
+typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Structs
+//-----------------------------------------------------------------------------
+
+struct ImDrawList3D {
+ ImVector IdxBuffer = {}; // Index buffer
+ ImVector VtxBuffer = {}; // Vertex buffer
+ ImVector ZBuffer = {}; // Z buffer. Depth value for each triangle
+ unsigned int _VtxCurrentIdx = 0; // [Internal] current vertex index
+ ImDrawVert* _VtxWritePtr = nullptr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)
+ ImDrawIdx* _IdxWritePtr = nullptr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)
+ float* _ZWritePtr = nullptr; // [Internal] point within ZBuffer.Data after each add command (to avoid using the ImVector<> operators too much)
+ ImDrawListFlags _Flags = ImDrawListFlags_None; // [Internal] draw list flags
+ ImDrawListSharedData* _SharedData = nullptr; // [Internal] shared draw list data
+
+ ImDrawList3D() {
+ }
+
+ void PrimReserve(int idx_count, int vtx_count);
+ void PrimUnreserve(int idx_count, int vtx_count);
+
+ void SortedMoveToImGuiDrawList();
+
+ constexpr static unsigned int MaxIdx() { return sizeof(ImDrawIdx) == 2 ? 65535 : 4294967295; }
+};
+
+struct ImPlot3DNextItemData {
+ ImVec4 Colors[4]; // ImPlot3DCol_Line, ImPlot3DCol_Fill, ImPlot3DCol_MarkerOutline, ImPlot3DCol_MarkerFill,
+ float LineWeight;
+ ImPlot3DMarker Marker;
+ float MarkerSize;
+ float MarkerWeight;
+ float FillAlpha;
+ bool RenderLine;
+ bool RenderFill;
+ bool RenderMarkerLine;
+ bool RenderMarkerFill;
+ bool IsAutoFill;
+ bool IsAutoLine;
+ bool Hidden;
+
+ ImPlot3DNextItemData() { Reset(); }
+
+ void Reset() {
+ for (int i = 0; i < 4; i++)
+ Colors[i] = IMPLOT3D_AUTO_COL;
+ LineWeight = IMPLOT3D_AUTO;
+ Marker = IMPLOT3D_AUTO;
+ MarkerSize = IMPLOT3D_AUTO;
+ MarkerWeight = IMPLOT3D_AUTO;
+ FillAlpha = IMPLOT3D_AUTO;
+ RenderLine = false;
+ RenderFill = false;
+ RenderMarkerLine = true;
+ RenderMarkerFill = true;
+ IsAutoFill = true;
+ IsAutoLine = true;
+ Hidden = false;
+ }
+};
+
+// Colormap data storage
+struct ImPlot3DColormapData {
+ ImVector Keys;
+ ImVector KeyCounts;
+ ImVector KeyOffsets;
+ ImVector Tables;
+ ImVector TableSizes;
+ ImVector TableOffsets;
+ ImGuiTextBuffer Text;
+ ImVector TextOffsets;
+ ImVector Quals;
+ ImGuiStorage Map;
+ int Count;
+
+ ImPlot3DColormapData() { Count = 0; }
+
+ int Append(const char* name, const ImU32* keys, int count, bool qual) {
+ if (GetIndex(name) != -1)
+ return -1;
+ KeyOffsets.push_back(Keys.size());
+ KeyCounts.push_back(count);
+ Keys.reserve(Keys.size() + count);
+ for (int i = 0; i < count; ++i)
+ Keys.push_back(keys[i]);
+ TextOffsets.push_back(Text.size());
+ Text.append(name, name + strlen(name) + 1);
+ Quals.push_back(qual);
+ ImGuiID id = ImHashStr(name);
+ int idx = Count++;
+ Map.SetInt(id, idx);
+ _AppendTable(idx);
+ return idx;
+ }
+
+ void _AppendTable(ImPlot3DColormap cmap) {
+ int key_count = GetKeyCount(cmap);
+ const ImU32* keys = GetKeys(cmap);
+ int off = Tables.size();
+ TableOffsets.push_back(off);
+ if (IsQual(cmap)) {
+ Tables.reserve(key_count);
+ for (int i = 0; i < key_count; ++i)
+ Tables.push_back(keys[i]);
+ TableSizes.push_back(key_count);
+ } else {
+ int max_size = 255 * (key_count - 1) + 1;
+ Tables.reserve(off + max_size);
+ // ImU32 last = keys[0];
+ // Tables.push_back(last);
+ // int n = 1;
+ for (int i = 0; i < key_count - 1; ++i) {
+ for (int s = 0; s < 255; ++s) {
+ ImU32 a = keys[i];
+ ImU32 b = keys[i + 1];
+ ImU32 c = ImPlot3D::ImMixU32(a, b, s);
+ // if (c != last) {
+ Tables.push_back(c);
+ // last = c;
+ // n++;
+ // }
+ }
+ }
+ ImU32 c = keys[key_count - 1];
+ // if (c != last) {
+ Tables.push_back(c);
+ // n++;
+ // }
+ // TableSizes.push_back(n);
+ TableSizes.push_back(max_size);
+ }
+ }
+
+ void RebuildTables() {
+ Tables.resize(0);
+ TableSizes.resize(0);
+ TableOffsets.resize(0);
+ for (int i = 0; i < Count; ++i)
+ _AppendTable(i);
+ }
+
+ inline bool IsQual(ImPlot3DColormap cmap) const { return Quals[cmap]; }
+ inline const char* GetName(ImPlot3DColormap cmap) const { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : nullptr; }
+ inline ImPlot3DColormap GetIndex(const char* name) const {
+ ImGuiID key = ImHashStr(name);
+ return Map.GetInt(key, -1);
+ }
+
+ inline const ImU32* GetKeys(ImPlot3DColormap cmap) const { return &Keys[KeyOffsets[cmap]]; }
+ inline int GetKeyCount(ImPlot3DColormap cmap) const { return KeyCounts[cmap]; }
+ inline ImU32 GetKeyColor(ImPlot3DColormap cmap, int idx) const { return Keys[KeyOffsets[cmap] + idx]; }
+ inline void SetKeyColor(ImPlot3DColormap cmap, int idx, ImU32 value) {
+ Keys[KeyOffsets[cmap] + idx] = value;
+ RebuildTables();
+ }
+
+ inline const ImU32* GetTable(ImPlot3DColormap cmap) const { return &Tables[TableOffsets[cmap]]; }
+ inline int GetTableSize(ImPlot3DColormap cmap) const { return TableSizes[cmap]; }
+ inline ImU32 GetTableColor(ImPlot3DColormap cmap, int idx) const { return Tables[TableOffsets[cmap] + idx]; }
+
+ inline ImU32 LerpTable(ImPlot3DColormap cmap, float t) const {
+ int off = TableOffsets[cmap];
+ int siz = TableSizes[cmap];
+ int idx = Quals[cmap] ? ImClamp((int)(siz * t), 0, siz - 1) : (int)((siz - 1) * t + 0.5f);
+ return Tables[off + idx];
+ }
+};
+
+// State information for plot items
+struct ImPlot3DItem {
+ ImGuiID ID;
+ ImU32 Color;
+ int NameOffset;
+ bool Show;
+ bool LegendHovered;
+ bool SeenThisFrame;
+
+ ImPlot3DItem() {
+ ID = 0;
+ Color = IM_COL32_WHITE;
+ NameOffset = -1;
+ Show = true;
+ LegendHovered = false;
+ SeenThisFrame = false;
+ }
+ ~ImPlot3DItem() { ID = 0; }
+};
+
+// Holds legend state
+struct ImPlot3DLegend {
+ ImPlot3DLegendFlags Flags;
+ ImPlot3DLegendFlags PreviousFlags;
+ ImPlot3DLocation Location;
+ ImPlot3DLocation PreviousLocation;
+ ImVector Indices;
+ ImGuiTextBuffer Labels;
+ ImRect Rect;
+ bool Hovered;
+ bool Held;
+
+ ImPlot3DLegend() {
+ PreviousFlags = Flags = ImPlot3DLegendFlags_None;
+ Hovered = Held = false;
+ PreviousLocation = Location = ImPlot3DLocation_NorthWest;
+ }
+
+ void Reset() {
+ Indices.shrink(0);
+ Labels.Buf.shrink(0);
+ }
+};
+
+// Holds items
+struct ImPlot3DItemGroup {
+ ImPool ItemPool;
+ ImPlot3DLegend Legend;
+ int ColormapIdx;
+
+ ImPlot3DItemGroup() {
+ ColormapIdx = 0;
+ }
+
+ int GetItemCount() const { return ItemPool.GetBufSize(); }
+ ImGuiID GetItemID(const char* label_id) { return ImGui::GetID(label_id); }
+ ImPlot3DItem* GetItem(ImGuiID id) { return ItemPool.GetByKey(id); }
+ ImPlot3DItem* GetItem(const char* label_id) { return GetItem(GetItemID(label_id)); }
+ ImPlot3DItem* GetOrAddItem(ImGuiID id) { return ItemPool.GetOrAddByKey(id); }
+ ImPlot3DItem* GetItemByIndex(int i) { return ItemPool.GetByIndex(i); }
+ int GetItemIndex(ImPlot3DItem* item) { return ItemPool.GetIndex(item); }
+ int GetLegendCount() const { return Legend.Indices.size(); }
+ ImPlot3DItem* GetLegendItem(int i) { return ItemPool.GetByIndex(Legend.Indices[i]); }
+ const char* GetLegendLabel(int i) { return Legend.Labels.Buf.Data + GetLegendItem(i)->NameOffset; }
+ void Reset() {
+ ItemPool.Clear();
+ Legend.Reset();
+ ColormapIdx = 0;
+ }
+};
+
+// Tick mark info
+struct ImPlot3DTick {
+ float PlotPos;
+ bool Major;
+ bool ShowLabel;
+ ImVec2 LabelSize;
+ int TextOffset;
+ int Idx;
+
+ ImPlot3DTick(double value, bool major, bool show_label) {
+ PlotPos = value;
+ Major = major;
+ ShowLabel = show_label;
+ TextOffset = -1;
+ }
+};
+
+// Collection of ticks
+struct ImPlot3DTicker {
+ ImVector Ticks;
+ ImGuiTextBuffer TextBuffer;
+
+ ImPlot3DTicker() {
+ Reset();
+ }
+
+ ImPlot3DTick& AddTick(double value, bool major, bool show_label, const char* label) {
+ ImPlot3DTick tick(value, major, show_label);
+ if (show_label && label != nullptr) {
+ tick.TextOffset = TextBuffer.size();
+ TextBuffer.append(label, label + strlen(label) + 1);
+ tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset);
+ }
+ return AddTick(tick);
+ }
+
+ ImPlot3DTick& AddTick(double value, bool major, bool show_label, ImPlot3DFormatter formatter, void* data) {
+ ImPlot3DTick tick(value, major, show_label);
+ if (show_label && formatter != nullptr) {
+ char buff[IMPLOT3D_LABEL_MAX_SIZE];
+ tick.TextOffset = TextBuffer.size();
+ formatter(tick.PlotPos, buff, sizeof(buff), data);
+ TextBuffer.append(buff, buff + strlen(buff) + 1);
+ tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset);
+ }
+ return AddTick(tick);
+ }
+
+ inline ImPlot3DTick& AddTick(ImPlot3DTick tick) {
+ tick.Idx = Ticks.size();
+ Ticks.push_back(tick);
+ return Ticks.back();
+ }
+
+ const char* GetText(int idx) const {
+ return TextBuffer.Buf.Data + Ticks[idx].TextOffset;
+ }
+
+ const char* GetText(const ImPlot3DTick& tick) const {
+ return GetText(tick.Idx);
+ }
+
+ void Reset() {
+ Ticks.shrink(0);
+ TextBuffer.Buf.shrink(0);
+ }
+
+ int TickCount() const {
+ return Ticks.Size;
+ }
+};
+
+// Holds axis information
+struct ImPlot3DAxis {
+ ImPlot3DAxisFlags Flags;
+ ImPlot3DAxisFlags PreviousFlags;
+ ImPlot3DRange Range;
+ ImPlot3DCond RangeCond;
+ ImGuiTextBuffer Label;
+ // Ticks
+ ImPlot3DTicker Ticker;
+ ImPlot3DFormatter Formatter;
+ void* FormatterData;
+ ImPlot3DLocator Locator;
+ // Fit data
+ bool FitThisFrame;
+ ImPlot3DRange FitExtents;
+ // User input
+ bool Held;
+
+ // Constructor
+ ImPlot3DAxis() {
+ PreviousFlags = Flags = ImPlot3DAxisFlags_None;
+ // Range
+ Range.Min = 0.0f;
+ Range.Max = 1.0f;
+ RangeCond = ImPlot3DCond_None;
+ // Ticks
+ Formatter = nullptr;
+ FormatterData = nullptr;
+ Locator = nullptr;
+ // Fit data
+ FitThisFrame = true;
+ FitExtents.Min = HUGE_VAL;
+ FitExtents.Max = -HUGE_VAL;
+ // User input
+ Held = false;
+ }
+
+ inline void SetRange(double v1, double v2) {
+ Range.Min = ImMin(v1, v2);
+ Range.Max = ImMax(v1, v2);
+ }
+
+ inline bool SetMin(double _min, bool force = false) {
+ if (!force && IsLockedMin())
+ return false;
+ _min = ImPlot3D::ImConstrainNan(ImPlot3D::ImConstrainInf(_min));
+ if (_min >= Range.Max)
+ return false;
+ Range.Min = _min;
+ return true;
+ }
+
+ inline bool SetMax(double _max, bool force = false) {
+ if (!force && IsLockedMax())
+ return false;
+ _max = ImPlot3D::ImConstrainNan(ImPlot3D::ImConstrainInf(_max));
+ if (_max <= Range.Min)
+ return false;
+ Range.Max = _max;
+ return true;
+ }
+
+ inline bool IsRangeLocked() const { return RangeCond == ImPlot3DCond_Always; }
+ inline bool IsLockedMin() const { return IsRangeLocked() || ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_LockMin); }
+ inline bool IsLockedMax() const { return IsRangeLocked() || ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_LockMax); }
+ inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); }
+
+ inline void SetLabel(const char* label) {
+ Label.Buf.shrink(0);
+ if (label && ImGui::FindRenderedTextEnd(label, nullptr) != label)
+ Label.append(label, label + strlen(label) + 1);
+ }
+
+ inline const char* GetLabel() const { return Label.Buf.Data; }
+
+ bool HasLabel() const;
+ bool HasGridLines() const;
+ bool HasTickLabels() const;
+ bool HasTickMarks() const;
+ bool IsAutoFitting() const;
+ void ExtendFit(float value);
+ void ApplyFit();
+ float PlotToNDC(float value) const;
+ float NDCToPlot(float value) const;
+};
+
+// Holds plot state information that must persist after EndPlot
+struct ImPlot3DPlot {
+ ImGuiID ID;
+ ImPlot3DFlags Flags;
+ ImPlot3DFlags PreviousFlags;
+ ImGuiTextBuffer Title;
+ bool JustCreated;
+ bool Initialized;
+ // Bounding rectangles
+ ImRect FrameRect; // Outermost bounding rectangle that encapsulates whole the plot/title/padding/etc
+ ImRect CanvasRect; // Frame rectangle reduced by padding
+ ImRect PlotRect; // Bounding rectangle for the actual plot area
+ // Rotation & Axes
+ ImPlot3DQuat Rotation;
+ ImPlot3DAxis Axes[3];
+ // Animation
+ float AnimationTime; // Remaining animation time
+ ImPlot3DQuat RotationAnimationEnd; // End rotation for animation
+ // User input
+ bool SetupLocked;
+ bool Hovered;
+ bool Held;
+ int HeldEdgeIdx; // Index of the edge being held
+ int HeldPlaneIdx; // Index of the plane being held
+ // Fit data
+ bool FitThisFrame;
+ // Items
+ ImPlot3DItemGroup Items;
+ ImPlot3DItem* CurrentItem;
+ // 3D draw list
+ ImDrawList3D DrawList;
+ // Misc
+ bool ContextClick; // True if context button was clicked (to distinguish from double click)
+ bool OpenContextThisFrame;
+
+ ImPlot3DPlot() {
+ PreviousFlags = Flags = ImPlot3DFlags_None;
+ JustCreated = true;
+ Initialized = false;
+ Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f);
+ for (int i = 0; i < 3; i++)
+ Axes[i] = ImPlot3DAxis();
+ AnimationTime = 0.0f;
+ RotationAnimationEnd = Rotation;
+ SetupLocked = false;
+ Hovered = Held = false;
+ HeldEdgeIdx = -1;
+ HeldPlaneIdx = -1;
+ FitThisFrame = true;
+ CurrentItem = nullptr;
+ ContextClick = false;
+ OpenContextThisFrame = false;
+ }
+
+ inline void SetTitle(const char* title) {
+ Title.Buf.shrink(0);
+ if (title && ImGui::FindRenderedTextEnd(title, nullptr) != title)
+ Title.append(title, title + strlen(title) + 1);
+ }
+ inline bool HasTitle() const { return !Title.empty() && !ImPlot3D::ImHasFlag(Flags, ImPlot3DFlags_NoTitle); }
+ inline const char* GetTitle() const { return Title.Buf.Data; }
+
+ void ExtendFit(const ImPlot3DPoint& point);
+ ImPlot3DPoint RangeMin() const;
+ ImPlot3DPoint RangeMax() const;
+ ImPlot3DPoint RangeCenter() const;
+ void SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max);
+};
+
+struct ImPlot3DContext {
+ ImPool Plots;
+ ImPlot3DPlot* CurrentPlot;
+ ImPlot3DItemGroup* CurrentItems;
+ ImPlot3DNextItemData NextItemData;
+ ImPlot3DStyle Style;
+ ImVector ColorModifiers;
+ ImVector StyleModifiers;
+ ImVector ColormapModifiers;
+ ImPlot3DColormapData ColormapData;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Context Pointer
+//-----------------------------------------------------------------------------
+
+namespace ImPlot3D {
+
+#ifndef GImPlot3D
+extern IMPLOT3D_API ImPlot3DContext* GImPlot3D; // Current context pointer
+#endif
+
+//-----------------------------------------------------------------------------
+// [SECTION] Context Utils
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_API void InitializeContext(ImPlot3DContext* ctx); // Initialize ImPlot3DContext
+IMPLOT3D_API void ResetContext(ImPlot3DContext* ctx); // Reset ImPlot3DContext
+
+//-----------------------------------------------------------------------------
+// [SECTION] Style Utils
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_API bool IsColorAuto(const ImVec4& col);
+IMPLOT3D_API bool IsColorAuto(ImPlot3DCol idx);
+IMPLOT3D_API ImVec4 GetAutoColor(ImPlot3DCol idx);
+IMPLOT3D_API const char* GetStyleColorName(ImPlot3DCol idx);
+
+// Get styling data for next item (call between BeginItem/EndItem)
+IMPLOT3D_API const ImPlot3DNextItemData& GetItemData();
+
+// Returns a color from the Color map given an index >= 0 (modulo will be performed)
+IMPLOT3D_API ImU32 GetColormapColorU32(int idx, ImPlot3DColormap cmap);
+
+// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired
+IMPLOT3D_API ImU32 NextColormapColorU32();
+
+//-----------------------------------------------------------------------------
+// [SECTION] Item Utils
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_API bool BeginItem(const char* label_id, ImPlot3DItemFlags flags = 0, ImPlot3DCol recolor_from = IMPLOT3D_AUTO);
+IMPLOT3D_API void EndItem();
+
+// Register or get an existing item from the current plot
+IMPLOT3D_API ImPlot3DItem* RegisterOrGetItem(const char* label_id, ImPlot3DItemFlags flags, bool* just_created = nullptr);
+
+// Busts the cache for every item for every plot in the current context
+IMPLOT3D_API void BustItemCache();
+
+// TODO move to another place
+IMPLOT3D_API void AddTextRotated(ImDrawList* draw_list, ImVec2 pos, float angle, ImU32 col, const char* text_begin, const char* text_end = nullptr);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plot Utils
+//-----------------------------------------------------------------------------
+
+// Gets the current plot from the current ImPlot3DContext
+IMPLOT3D_API ImPlot3DPlot* GetCurrentPlot();
+
+// Busts the cache for every plot in the current context
+IMPLOT3D_API void BustPlotCache();
+
+IMPLOT3D_API ImVec2 GetFramePos(); // Get the current frame position (top-left) in pixels
+IMPLOT3D_API ImVec2 GetFrameSize(); // Get the current frame size in pixels
+
+// Convert a position in the current plot's coordinate system to the current plot's normalized device coordinate system (NDC)
+// When the cube aspect ratio is [1,1,1], the NDC varies from [-0.5, 0.5] in each axis
+IMPLOT3D_API ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point);
+IMPLOT3D_API ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point);
+// Convert a position in the current plot's NDC to pixels
+IMPLOT3D_API ImVec2 NDCToPixels(const ImPlot3DPoint& point);
+// Convert a pixel coordinate to a ray in the NDC
+IMPLOT3D_API ImPlot3DRay PixelsToNDCRay(const ImVec2& pix);
+// Convert a ray in the NDC to a ray in the current plot's coordinate system
+IMPLOT3D_API ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray);
+
+//-----------------------------------------------------------------------------
+// [SECTION] Setup Utils
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_API void SetupLock();
+
+//-----------------------------------------------------------------------------
+// [SECTION] Formatter
+//-----------------------------------------------------------------------------
+
+int Formatter_Default(float value, char* buff, int size, void* data);
+
+//------------------------------------------------------------------------------
+// [SECTION] Locator
+//------------------------------------------------------------------------------
+
+void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data);
+
+} // namespace ImPlot3D
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/lib/third_party/imgui/implot3d/source/implot3d.cpp b/lib/third_party/imgui/implot3d/source/implot3d.cpp
new file mode 100644
index 000000000..2a30ef062
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/source/implot3d.cpp
@@ -0,0 +1,3011 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d.cpp
+// Date: 2024-11-16
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] Includes
+// [SECTION] Macros
+// [SECTION] Context
+// [SECTION] Text Utils
+// [SECTION] Legend Utils
+// [SECTION] Mouse Position Utils
+// [SECTION] Plot Box Utils
+// [SECTION] Formatter
+// [SECTION] Locator
+// [SECTION] Context Menus
+// [SECTION] Begin/End Plot
+// [SECTION] Setup
+// [SECTION] Plot Utils
+// [SECTION] Setup Utils
+// [SECTION] Miscellaneous
+// [SECTION] Styles
+// [SECTION] Colormaps
+// [SECTION] Context Utils
+// [SECTION] Style Utils
+// [SECTION] ImPlot3DPoint
+// [SECTION] ImPlot3DBox
+// [SECTION] ImPlot3DRange
+// [SECTION] ImPlot3DQuat
+// [SECTION] ImDrawList3D
+// [SECTION] ImPlot3DAxis
+// [SECTION] ImPlot3DPlot
+// [SECTION] ImPlot3DStyle
+
+//-----------------------------------------------------------------------------
+// [SECTION] Includes
+//-----------------------------------------------------------------------------
+
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+
+#include "implot3d.h"
+#include "implot3d_internal.h"
+
+#ifndef IMGUI_DISABLE
+
+//-----------------------------------------------------------------------------
+// [SECTION] Macros
+//-----------------------------------------------------------------------------
+
+#define IMPLOT3D_CHECK_CTX() IM_ASSERT_USER_ERROR(GImPlot3D != nullptr, "No current context. Did you call ImPlot3D::CreateContext() or ImPlot3D::SetCurrentContext()?")
+#define IMPLOT3D_CHECK_PLOT() IM_ASSERT_USER_ERROR(GImPlot3D->CurrentPlot != nullptr, "No active plot. Did you call ImPlot3D::BeginPlot()?")
+
+//-----------------------------------------------------------------------------
+// [SECTION] Context
+//-----------------------------------------------------------------------------
+
+namespace ImPlot3D {
+
+// Global ImPlot3D context
+#ifndef GImPlot3D
+ImPlot3DContext* GImPlot3D = nullptr;
+#endif
+
+static ImPlot3DQuat init_rotation = ImPlot3DQuat(-0.513269, -0.212596, -0.318184, 0.76819);
+
+ImPlot3DContext* CreateContext() {
+ ImPlot3DContext* ctx = IM_NEW(ImPlot3DContext)();
+ if (GImPlot3D == nullptr)
+ SetCurrentContext(ctx);
+ InitializeContext(ctx);
+ return ctx;
+}
+
+void DestroyContext(ImPlot3DContext* ctx) {
+ if (ctx == nullptr)
+ ctx = GImPlot3D;
+ if (GImPlot3D == ctx)
+ SetCurrentContext(nullptr);
+ IM_DELETE(ctx);
+}
+
+ImPlot3DContext* GetCurrentContext() { return GImPlot3D; }
+
+void SetCurrentContext(ImPlot3DContext* ctx) { GImPlot3D = ctx; }
+
+//-----------------------------------------------------------------------------
+// [SECTION] Text Utils
+//-----------------------------------------------------------------------------
+
+void AddTextRotated(ImDrawList* draw_list, ImVec2 pos, float angle, ImU32 col, const char* text_begin, const char* text_end) {
+ if (!text_end)
+ text_end = text_begin + strlen(text_begin);
+
+ ImGuiContext& g = *GImGui;
+ ImFont* font = g.Font;
+
+ // Align to be pixel perfect
+ pos.x = IM_FLOOR(pos.x);
+ pos.y = IM_FLOOR(pos.y);
+
+ const float scale = g.FontSize / font->FontSize;
+
+ // Measure the size of the text in unrotated coordinates
+ ImVec2 text_size = font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, text_begin, text_end, nullptr);
+
+ // Precompute sine and cosine of the angle (note: angle should be positive for rotation in ImGui)
+ float cos_a = cosf(-angle);
+ float sin_a = sinf(-angle);
+
+ const char* s = text_begin;
+ int chars_total = (int)(text_end - s);
+ int chars_rendered = 0;
+ const int vtx_count_max = chars_total * 4;
+ const int idx_count_max = chars_total * 6;
+ draw_list->PrimReserve(idx_count_max, vtx_count_max);
+
+ // Adjust pen position to center the text
+ ImVec2 pen = ImVec2(-text_size.x * 0.5f, -text_size.y * 0.5f);
+
+ while (s < text_end) {
+ unsigned int c = (unsigned int)*s;
+ if (c < 0x80) {
+ s += 1;
+ } else {
+ s += ImTextCharFromUtf8(&c, s, text_end);
+ if (c == 0) // Malformed UTF-8?
+ break;
+ }
+
+ const ImFontGlyph* glyph = font->FindGlyph((ImWchar)c);
+ if (glyph == nullptr) {
+ continue;
+ }
+
+ // Glyph dimensions and positions
+ ImVec2 glyph_offset = ImVec2(glyph->X0, glyph->Y0) * scale;
+ ImVec2 glyph_size = ImVec2(glyph->X1 - glyph->X0, glyph->Y1 - glyph->Y0) * scale;
+
+ // Corners of the glyph quad in unrotated space
+ ImVec2 corners[4];
+ corners[0] = pen + glyph_offset;
+ corners[1] = pen + glyph_offset + ImVec2(glyph_size.x, 0);
+ corners[2] = pen + glyph_offset + glyph_size;
+ corners[3] = pen + glyph_offset + ImVec2(0, glyph_size.y);
+
+ // Rotate and translate the corners
+ for (int i = 0; i < 4; i++) {
+ float x = corners[i].x;
+ float y = corners[i].y;
+ corners[i].x = x * cos_a - y * sin_a + pos.x;
+ corners[i].y = x * sin_a + y * cos_a + pos.y;
+ }
+
+ // Texture coordinates
+ ImVec2 uv0 = ImVec2(glyph->U0, glyph->V0);
+ ImVec2 uv1 = ImVec2(glyph->U1, glyph->V1);
+
+ // Render the glyph quad
+ draw_list->PrimQuadUV(corners[0], corners[1], corners[2], corners[3],
+ uv0, ImVec2(glyph->U1, glyph->V0),
+ uv1, ImVec2(glyph->U0, glyph->V1),
+ col);
+
+ // Advance the pen position
+ pen.x += glyph->AdvanceX * scale;
+
+ chars_rendered++;
+ }
+
+ // Return unused vertices
+ int chars_skipped = chars_total - chars_rendered;
+ draw_list->PrimUnreserve(chars_skipped * 6, chars_skipped * 4);
+}
+
+void AddTextCentered(ImDrawList* draw_list, ImVec2 top_center, ImU32 col, const char* text_begin) {
+ const char* text_end = ImGui::FindRenderedTextEnd(text_begin);
+ ImVec2 text_size = ImGui::CalcTextSize(text_begin, text_end, true);
+ draw_list->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y), col, text_begin, text_end);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Legend Utils
+//-----------------------------------------------------------------------------
+
+ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlot3DLocation loc, const ImVec2& pad) {
+ ImVec2 pos;
+ // Legend x coordinate
+ if (ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_West) && !ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_East))
+ pos.x = outer_rect.Min.x + pad.x;
+ else if (!ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_West) && ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_East))
+ pos.x = outer_rect.Max.x - pad.x - inner_size.x;
+ else
+ pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f;
+ // Legend y coordinate
+ if (ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_North) && !ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_South))
+ pos.y = outer_rect.Min.y + pad.y;
+ else if (!ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_North) && ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_South))
+ pos.y = outer_rect.Max.y - pad.y - inner_size.y;
+ else
+ pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f;
+ pos.x = IM_ROUND(pos.x);
+ pos.y = IM_ROUND(pos.y);
+ return pos;
+}
+
+ImVec2 CalcLegendSize(ImPlot3DItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) {
+ const int nItems = items.GetLegendCount();
+ const float txt_ht = ImGui::GetTextLineHeight();
+ const float icon_size = txt_ht;
+ // Get label max width
+ float max_label_width = 0;
+ float sum_label_width = 0;
+ for (int i = 0; i < nItems; i++) {
+ const char* label = items.GetLegendLabel(i);
+ const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
+ max_label_width = label_width > max_label_width ? label_width : max_label_width;
+ sum_label_width += label_width;
+ }
+ // Compute legend size
+ const ImVec2 legend_size = vertical ? ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) : ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht);
+ return legend_size;
+}
+
+void ShowLegendEntries(ImPlot3DItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& draw_list) {
+ const float txt_ht = ImGui::GetTextLineHeight();
+ const float icon_size = txt_ht;
+ const float icon_shrink = 2;
+ ImU32 col_txt = GetStyleColorU32(ImPlot3DCol_LegendText);
+ ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f);
+ float sum_label_width = 0;
+
+ const int num_items = items.GetLegendCount();
+ if (num_items == 0)
+ return;
+ ImPlot3DContext& gp = *GImPlot3D;
+
+ // Render legend items
+ for (int i = 0; i < num_items; i++) {
+ const int idx = i;
+ ImPlot3DItem* item = items.GetLegendItem(idx);
+ const char* label = items.GetLegendLabel(idx);
+ const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
+ const ImVec2 top_left = vertical ? legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) : legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0);
+ sum_label_width += label_width;
+ ImRect icon_bb;
+ icon_bb.Min = top_left + ImVec2(icon_shrink, icon_shrink);
+ icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink);
+ ImRect label_bb;
+ label_bb.Min = top_left;
+ label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size);
+ ImU32 col_txt_hl;
+ ImU32 col_item = ImAlphaU32(item->Color, 1);
+
+ ImRect button_bb(icon_bb.Min, label_bb.Max);
+
+ ImGui::KeepAliveID(item->ID);
+
+ bool item_hov = false;
+ bool item_hld = false;
+ bool item_clk = ImPlot3D::ImHasFlag(items.Legend.Flags, ImPlot3DLegendFlags_NoButtons)
+ ? false
+ : ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld);
+
+ if (item_clk)
+ item->Show = !item->Show;
+
+ const bool hovering = item_hov && !ImPlot3D::ImHasFlag(items.Legend.Flags, ImPlot3DLegendFlags_NoHighlightItem);
+
+ if (hovering) {
+ item->LegendHovered = true;
+ col_txt_hl = ImPlot3D::ImMixU32(col_txt, col_item, 64);
+ } else {
+ item->LegendHovered = false;
+ col_txt_hl = ImGui::GetColorU32(col_txt);
+ }
+
+ ImU32 col_icon;
+ if (item_hld)
+ col_icon = item->Show ? ImAlphaU32(col_item, 0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);
+ else if (item_hov)
+ col_icon = item->Show ? ImAlphaU32(col_item, 0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f);
+ else
+ col_icon = item->Show ? col_item : col_txt_dis;
+
+ draw_list.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon);
+ const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr);
+ if (label != text_display_end)
+ draw_list.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl : col_txt_dis, label, text_display_end);
+ }
+}
+
+void RenderLegend() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ if (ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend) || plot.Items.GetLegendCount() == 0)
+ return;
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImDrawList* draw_list = window->DrawList;
+ const ImGuiIO& IO = ImGui::GetIO();
+
+ ImPlot3DLegend& legend = plot.Items.Legend;
+ const bool legend_horz = ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal);
+ const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);
+ const ImVec2 legend_pos = GetLocationPos(plot.PlotRect,
+ legend_size,
+ legend.Location,
+ gp.Style.LegendPadding);
+ legend.Rect = ImRect(legend_pos, legend_pos + legend_size);
+
+ // Test hover
+ legend.Hovered = legend.Rect.Contains(IO.MousePos);
+
+ // Render background
+ ImU32 col_bg = GetStyleColorU32(ImPlot3DCol_LegendBg);
+ ImU32 col_bd = GetStyleColorU32(ImPlot3DCol_LegendBorder);
+ draw_list->AddRectFilled(legend.Rect.Min, legend.Rect.Max, col_bg);
+ draw_list->AddRect(legend.Rect.Min, legend.Rect.Max, col_bd);
+
+ // Render legends
+ ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, *draw_list);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Mouse Position Utils
+//-----------------------------------------------------------------------------
+
+void RenderMousePos() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ if (ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoMouseText))
+ return;
+
+ ImVec2 mouse_pos = ImGui::GetMousePos();
+ ImPlot3DPoint mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_YZ, true);
+ if (mouse_plot_pos.IsNaN())
+ mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_XZ, true);
+ if (mouse_plot_pos.IsNaN())
+ mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_XY, true);
+
+ char buff[IMPLOT3D_LABEL_MAX_SIZE];
+ if (!mouse_plot_pos.IsNaN()) {
+ ImGuiTextBuffer builder;
+ builder.append("(");
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+ if (i > 0)
+ builder.append(", ");
+ axis.Formatter(mouse_plot_pos[i], buff, IMPLOT3D_LABEL_MAX_SIZE, axis.FormatterData);
+ builder.append(buff);
+ }
+ builder.append(")");
+
+ const ImVec2 size = ImGui::CalcTextSize(builder.c_str());
+ // TODO custom location/padding
+ const ImVec2 pos = GetLocationPos(plot.PlotRect, size, ImPlot3DLocation_SouthEast, ImVec2(10, 10));
+ ImDrawList& draw_list = *ImGui::GetWindowDrawList();
+ draw_list.AddText(pos, GetStyleColorU32(ImPlot3DCol_InlayText), builder.c_str());
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plot Box Utils
+//-----------------------------------------------------------------------------
+
+// Faces of the box (defined by 4 corner indices)
+static const int faces[6][4] = {
+ {0, 3, 7, 4}, // X-min face
+ {0, 4, 5, 1}, // Y-min face
+ {0, 1, 2, 3}, // Z-min face
+ {1, 2, 6, 5}, // X-max face
+ {3, 7, 6, 2}, // Y-max face
+ {4, 5, 6, 7}, // Z-max face
+};
+
+// Edges of the box (defined by 2 corner indices)
+static const int edges[12][2] = {
+ // Bottom face edges
+ {0, 1},
+ {1, 2},
+ {2, 3},
+ {3, 0},
+ // Top face edges
+ {4, 5},
+ {5, 6},
+ {6, 7},
+ {7, 4},
+ // Vertical edges
+ {0, 4},
+ {1, 5},
+ {2, 6},
+ {3, 7},
+};
+
+// Face edges (4 edge indices for each face)
+static const int face_edges[6][4] = {
+ {3, 11, 8, 7}, // X-min face
+ {0, 8, 4, 9}, // Y-min face
+ {0, 1, 2, 3}, // Z-min face
+ {1, 9, 5, 10}, // X-max face
+ {2, 10, 6, 11}, // Y-max face
+ {4, 5, 6, 7}, // Z-max face
+};
+
+// Lookup table for axis_corners based on active_faces (3D plot)
+static const int axis_corners_lookup_3d[8][3][2] = {
+ // Index 0: active_faces = {0, 0, 0}
+ {{3, 2}, {1, 2}, {1, 5}},
+ // Index 1: active_faces = {0, 0, 1}
+ {{7, 6}, {5, 6}, {1, 5}},
+ // Index 2: active_faces = {0, 1, 0}
+ {{0, 1}, {1, 2}, {2, 6}},
+ // Index 3: active_faces = {0, 1, 1}
+ {{4, 5}, {5, 6}, {2, 6}},
+ // Index 4: active_faces = {1, 0, 0}
+ {{3, 2}, {0, 3}, {0, 4}},
+ // Index 5: active_faces = {1, 0, 1}
+ {{7, 6}, {4, 7}, {0, 4}},
+ // Index 6: active_faces = {1, 1, 0}
+ {{0, 1}, {0, 3}, {3, 7}},
+ // Index 7: active_faces = {1, 1, 1}
+ {{4, 5}, {4, 7}, {3, 7}},
+};
+
+int GetMouseOverPlane(const ImPlot3DPlot& plot, const bool* active_faces, const ImVec2* corners_pix, int* plane_out = nullptr) {
+ ImGuiIO& io = ImGui::GetIO();
+ ImVec2 mouse_pos = io.MousePos;
+ if (plane_out)
+ *plane_out = -1;
+
+ // Check each active face
+ for (int a = 0; a < 3; a++) {
+ int face_idx = a + 3 * active_faces[a];
+ ImVec2 p0 = corners_pix[faces[face_idx][0]];
+ ImVec2 p1 = corners_pix[faces[face_idx][1]];
+ ImVec2 p2 = corners_pix[faces[face_idx][2]];
+ ImVec2 p3 = corners_pix[faces[face_idx][3]];
+
+ // Check if the mouse is inside the face's quad (using a triangle check)
+ if (ImTriangleContainsPoint(p0, p1, p2, mouse_pos) || ImTriangleContainsPoint(p2, p3, p0, mouse_pos)) {
+ if (plane_out)
+ *plane_out = a;
+ return a; // Return the plane index: 0 -> YZ, 1 -> XZ, 2 -> XY
+ }
+ }
+
+ return -1; // Not over any active plane
+}
+
+int GetMouseOverAxis(const ImPlot3DPlot& plot, const bool* active_faces, const ImVec2* corners_pix, const int plane_2d, int* edge_out = nullptr) {
+ const float axis_proximity_threshold = 15.0f; // Distance in pixels to consider the mouse "close" to an axis
+
+ ImGuiIO& io = ImGui::GetIO();
+ ImVec2 mouse_pos = io.MousePos;
+ if (edge_out)
+ *edge_out = -1;
+
+ bool visible_edges[12];
+ for (int i = 0; i < 12; i++)
+ visible_edges[i] = false;
+ for (int a = 0; a < 3; a++) {
+ int face_idx = a + 3 * active_faces[a];
+ if (plane_2d != -1 && a != plane_2d)
+ continue;
+ for (size_t i = 0; i < 4; i++)
+ visible_edges[face_edges[face_idx][i]] = true;
+ }
+
+ // Check each edge for proximity to the mouse
+ for (int edge = 0; edge < 12; edge++) {
+ if (!visible_edges[edge])
+ continue;
+
+ ImVec2 p0 = corners_pix[edges[edge][0]];
+ ImVec2 p1 = corners_pix[edges[edge][1]];
+
+ // Check distance to the edge
+ ImVec2 closest_point = ImLineClosestPoint(p0, p1, mouse_pos);
+ float dist = ImLengthSqr(mouse_pos - closest_point);
+ if (dist <= axis_proximity_threshold) {
+ if (edge_out)
+ *edge_out = edge;
+
+ // Determine which axis the edge belongs to
+ if (edge == 0 || edge == 2 || edge == 4 || edge == 6)
+ return 0; // X-axis
+ else if (edge == 1 || edge == 3 || edge == 5 || edge == 7)
+ return 1; // Y-axis
+ else
+ return 2; // Z-axis
+ }
+ }
+
+ return -1; // Not over any axis
+}
+
+void RenderPlotBackground(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImVec2* corners_pix, const bool* active_faces, const int plane_2d) {
+ const ImVec4 col_bg = GetStyleColorVec4(ImPlot3DCol_PlotBg);
+ const ImVec4 col_bg_hov = col_bg + ImVec4(0.03, 0.03, 0.03, 0.0);
+
+ int hovered_plane = -1;
+ if (!plot.Held) {
+ // If the mouse is not held, highlight plane hovering when mouse over it
+ hovered_plane = GetMouseOverPlane(plot, active_faces, corners_pix);
+ if (GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d) != -1)
+ hovered_plane = -1;
+ } else {
+ // If the mouse is held, highlight the held plane
+ hovered_plane = plot.HeldPlaneIdx;
+ }
+
+ for (int a = 0; a < 3; a++) {
+ int idx[4]; // Corner indices
+ for (int i = 0; i < 4; i++)
+ idx[i] = faces[a + 3 * active_faces[a]][i];
+ const ImU32 col = ImGui::ColorConvertFloat4ToU32((hovered_plane == a) ? col_bg_hov : col_bg);
+ draw_list->AddQuadFilled(corners_pix[idx[0]], corners_pix[idx[1]], corners_pix[idx[2]], corners_pix[idx[3]], col);
+ }
+}
+
+void RenderPlotBorder(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImVec2* corners_pix, const bool* active_faces, const int plane_2d) {
+ ImGuiIO& io = ImGui::GetIO();
+
+ int hovered_edge = -1;
+ if (!plot.Held)
+ GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d, &hovered_edge);
+ else
+ hovered_edge = plot.HeldEdgeIdx;
+
+ bool render_edge[12];
+ for (int i = 0; i < 12; i++)
+ render_edge[i] = false;
+ for (int a = 0; a < 3; a++) {
+ int face_idx = a + 3 * active_faces[a];
+ if (plane_2d != -1 && a != plane_2d)
+ continue;
+ for (size_t i = 0; i < 4; i++)
+ render_edge[face_edges[face_idx][i]] = true;
+ }
+
+ ImU32 col_bd = GetStyleColorU32(ImPlot3DCol_PlotBorder);
+ for (int i = 0; i < 12; i++) {
+ if (render_edge[i]) {
+ int idx0 = edges[i][0];
+ int idx1 = edges[i][1];
+ float thickness = i == hovered_edge ? 3.0f : 1.0f;
+ draw_list->AddLine(corners_pix[idx0], corners_pix[idx1], col_bd, thickness);
+ }
+ }
+}
+
+void RenderGrid(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const bool* active_faces, const int plane_2d) {
+ ImVec4 col_grid = GetStyleColorVec4(ImPlot3DCol_AxisGrid);
+ ImU32 col_grid_minor = ImGui::GetColorU32(col_grid * ImVec4(1, 1, 1, 0.3f));
+ ImU32 col_grid_major = ImGui::GetColorU32(col_grid * ImVec4(1, 1, 1, 0.6f));
+ for (int face = 0; face < 3; face++) {
+ if (plane_2d != -1 && face != plane_2d)
+ continue;
+ int face_idx = face + 3 * active_faces[face];
+ const ImPlot3DAxis& axis_u = plot.Axes[(face + 1) % 3];
+ const ImPlot3DAxis& axis_v = plot.Axes[(face + 2) % 3];
+
+ // Get the two axes (u and v) that define the face plane
+ int idx0 = faces[face_idx][0];
+ int idx1 = faces[face_idx][1];
+ int idx2 = faces[face_idx][2];
+ int idx3 = faces[face_idx][3];
+
+ // Corners of the face in plot space
+ ImPlot3DPoint p0 = corners[idx0];
+ ImPlot3DPoint p1 = corners[idx1];
+ ImPlot3DPoint p2 = corners[idx2];
+ ImPlot3DPoint p3 = corners[idx3];
+
+ // Vectors along the edges
+ ImPlot3DPoint u_vec = p1 - p0;
+ ImPlot3DPoint v_vec = p3 - p0;
+
+ // Render grid lines along u axis (axis_u)
+ if (!ImPlot3D::ImHasFlag(axis_u.Flags, ImPlot3DAxisFlags_NoGridLines))
+ for (int t = 0; t < axis_u.Ticker.TickCount(); ++t) {
+ const ImPlot3DTick& tick = axis_u.Ticker.Ticks[t];
+
+ // Compute position along u
+ float t_u = (tick.PlotPos - axis_u.Range.Min) / (axis_u.Range.Max - axis_u.Range.Min);
+ ImPlot3DPoint p_start = p0 + u_vec * t_u;
+ ImPlot3DPoint p_end = p3 + u_vec * t_u;
+
+ // Convert to pixel coordinates
+ ImVec2 p_start_pix = PlotToPixels(p_start);
+ ImVec2 p_end_pix = PlotToPixels(p_end);
+
+ // Get color
+ ImU32 col_line = tick.Major ? col_grid_major : col_grid_minor;
+
+ // Draw the grid line
+ draw_list->AddLine(p_start_pix, p_end_pix, col_line);
+ }
+
+ // Render grid lines along v axis (axis_v)
+ if (!ImPlot3D::ImHasFlag(axis_v.Flags, ImPlot3DAxisFlags_NoGridLines))
+ for (int t = 0; t < axis_v.Ticker.TickCount(); ++t) {
+ const ImPlot3DTick& tick = axis_v.Ticker.Ticks[t];
+
+ // Compute position along v
+ float t_v = (tick.PlotPos - axis_v.Range.Min) / (axis_v.Range.Max - axis_v.Range.Min);
+ ImPlot3DPoint p_start = p0 + v_vec * t_v;
+ ImPlot3DPoint p_end = p1 + v_vec * t_v;
+
+ // Convert to pixel coordinates
+ ImVec2 p_start_pix = PlotToPixels(p_start);
+ ImVec2 p_end_pix = PlotToPixels(p_end);
+
+ // Get color
+ ImU32 col_line = tick.Major ? col_grid_major : col_grid_minor;
+
+ // Draw the grid line
+ draw_list->AddLine(p_start_pix, p_end_pix, col_line);
+ }
+ }
+}
+
+void RenderTickMarks(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2], const int plane_2d) {
+ ImU32 col_tick = GetStyleColorU32(ImPlot3DCol_AxisTick);
+
+ auto DeterminePlaneForAxis = [&](int axis_idx) {
+ if (plane_2d != -1)
+ return plane_2d;
+ // If no plane chosen (-1), use:
+ // X or Y axis -> XY plane (2)
+ // Z axis -> YZ plane (0)
+ if (axis_idx == 2)
+ return 1; // Z-axis use XZ plane
+ else
+ return 2; // X or Y-axis use XY plane
+ };
+
+ for (int a = 0; a < 3; a++) {
+ const ImPlot3DAxis& axis = plot.Axes[a];
+ if (ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_NoTickMarks))
+ continue;
+
+ int idx0 = axis_corners[a][0];
+ int idx1 = axis_corners[a][1];
+ if (idx0 == idx1) // axis not visible or invalid
+ continue;
+
+ ImPlot3DPoint axis_start = corners[idx0];
+ ImPlot3DPoint axis_end = corners[idx1];
+ ImPlot3DPoint axis_dir = axis_end - axis_start;
+ float axis_len = axis_dir.Length();
+ if (axis_len < 1e-12f)
+ continue;
+ axis_dir /= axis_len;
+
+ // Draw axis line
+ ImVec2 axis_start_pix = corners_pix[idx0];
+ ImVec2 axis_end_pix = corners_pix[idx1];
+ draw_list->AddLine(axis_start_pix, axis_end_pix, col_tick);
+
+ // Choose plane
+ int chosen_plane = DeterminePlaneForAxis(a);
+
+ // Project axis_dir onto chosen plane
+ ImPlot3DPoint proj_dir = axis_dir;
+ if (chosen_plane == 0) {
+ // YZ plane: zero out x
+ proj_dir.x = 0.0f;
+ } else if (chosen_plane == 1) {
+ // XZ plane: zero out y
+ proj_dir.y = 0.0f;
+ } else if (chosen_plane == 2) {
+ // XY plane: zero out z
+ proj_dir.z = 0.0f;
+ }
+
+ float proj_len = proj_dir.Length();
+ if (proj_len < 1e-12f) {
+ // Axis is parallel to plane normal or something degenerate, skip ticks
+ continue;
+ }
+ proj_dir /= proj_len;
+
+ // Rotate 90 degrees in chosen plane
+ ImPlot3DPoint tick_dir;
+ if (chosen_plane == 0) {
+ // YZ plane
+ // proj_dir=(0,py,pz), rotate 90°: (py,pz) -> (-pz,py)
+ tick_dir = ImPlot3DPoint(0, -proj_dir.z, proj_dir.y);
+ } else if (chosen_plane == 1) {
+ // XZ plane (plane=1)
+ // proj_dir=(px,0,pz), rotate 90°: (px,pz) -> (-pz,px)
+ tick_dir = ImPlot3DPoint(-proj_dir.z, 0, proj_dir.x);
+ } else {
+ // XY plane
+ // proj_dir=(px,py,0), rotate by 90°: (px,py) -> (-py,px)
+ tick_dir = ImPlot3DPoint(-proj_dir.y, proj_dir.x, 0);
+ }
+ tick_dir.Normalize();
+
+ // Tick lengths in NDC units
+ const float major_size_ndc = 0.06f;
+ const float minor_size_ndc = 0.03f;
+
+ for (int t = 0; t < axis.Ticker.TickCount(); ++t) {
+ const ImPlot3DTick& tick = axis.Ticker.Ticks[t];
+ float v = (tick.PlotPos - axis.Range.Min) / (axis.Range.Max - axis.Range.Min);
+
+ ImPlot3DPoint tick_pos_ndc = PlotToNDC(axis_start + axis_dir * (v * axis_len));
+
+ // Half tick on each side of the axis line
+ float size_tick_ndc = tick.Major ? major_size_ndc : minor_size_ndc;
+ ImPlot3DPoint half_tick_ndc = tick_dir * (size_tick_ndc * 0.5f);
+
+ ImPlot3DPoint T1_ndc = tick_pos_ndc - half_tick_ndc;
+ ImPlot3DPoint T2_ndc = tick_pos_ndc + half_tick_ndc;
+
+ ImVec2 T1_screen = NDCToPixels(T1_ndc);
+ ImVec2 T2_screen = NDCToPixels(T2_ndc);
+
+ draw_list->AddLine(T1_screen, T2_screen, col_tick);
+ }
+ }
+}
+
+void RenderTickLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2]) {
+ ImVec2 box_center_pix = PlotToPixels(plot.RangeCenter());
+ ImU32 col_tick_txt = GetStyleColorU32(ImPlot3DCol_AxisText);
+
+ for (int a = 0; a < 3; a++) {
+ const ImPlot3DAxis& axis = plot.Axes[a];
+ if (ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_NoTickLabels))
+ continue;
+
+ // Corner indices for this axis
+ int idx0 = axis_corners[a][0];
+ int idx1 = axis_corners[a][1];
+
+ // If normal to the 2D plot, ignore the ticks
+ if (idx0 == idx1)
+ continue;
+
+ // Start and end points of the axis in plot space
+ ImPlot3DPoint axis_start = corners[idx0];
+ ImPlot3DPoint axis_end = corners[idx1];
+
+ // Direction vector along the axis
+ ImPlot3DPoint axis_dir = axis_end - axis_start;
+
+ // Convert axis start and end to screen space
+ ImVec2 axis_start_pix = corners_pix[idx0];
+ ImVec2 axis_end_pix = corners_pix[idx1];
+
+ // Screen space axis direction
+ ImVec2 axis_screen_dir = axis_end_pix - axis_start_pix;
+ float axis_length = ImSqrt(ImLengthSqr(axis_screen_dir));
+ if (axis_length != 0.0f)
+ axis_screen_dir /= axis_length;
+ else
+ axis_screen_dir = ImVec2(1.0f, 0.0f); // Default direction if length is zero
+
+ // Perpendicular direction in screen space
+ ImVec2 offset_dir_pix = ImVec2(-axis_screen_dir.y, axis_screen_dir.x);
+
+ // Make sure direction points away from cube center
+ ImVec2 box_center_pix = PlotToPixels(plot.RangeCenter());
+ ImVec2 axis_center_pix = (axis_start_pix + axis_end_pix) * 0.5f;
+ ImVec2 center_to_axis_pix = axis_center_pix - box_center_pix;
+ center_to_axis_pix /= ImSqrt(ImLengthSqr(center_to_axis_pix));
+ if (ImDot(offset_dir_pix, center_to_axis_pix) < 0.0f)
+ offset_dir_pix = -offset_dir_pix;
+
+ // Adjust the offset magnitude
+ float offset_magnitude = 20.0f; // TODO Calculate based on label size
+ ImVec2 offset_pix = offset_dir_pix * offset_magnitude;
+
+ // Compute angle perpendicular to axis in screen space
+ float angle = atan2f(-axis_screen_dir.y, axis_screen_dir.x) + IM_PI * 0.5f;
+
+ // Normalize angle to be between -π and π
+ if (angle > IM_PI)
+ angle -= 2 * IM_PI;
+ if (angle < -IM_PI)
+ angle += 2 * IM_PI;
+
+ // Adjust angle to keep labels upright
+ if (angle > IM_PI * 0.5f)
+ angle -= IM_PI;
+ if (angle < -IM_PI * 0.5f)
+ angle += IM_PI;
+
+ // Loop over ticks
+ for (int t = 0; t < axis.Ticker.TickCount(); ++t) {
+ const ImPlot3DTick& tick = axis.Ticker.Ticks[t];
+ if (!tick.ShowLabel)
+ continue;
+
+ // Compute position along the axis
+ float t_axis = (tick.PlotPos - axis.Range.Min) / (axis.Range.Max - axis.Range.Min);
+ ImPlot3DPoint tick_pos = axis_start + axis_dir * t_axis;
+
+ // Convert to pixel coordinates
+ ImVec2 tick_pos_pix = PlotToPixels(tick_pos);
+
+ // Get the tick label text
+ const char* label = axis.Ticker.GetText(tick);
+
+ // Adjust label position by offset
+ ImVec2 label_pos_pix = tick_pos_pix + offset_pix;
+
+ // Render the tick label
+ AddTextRotated(draw_list, label_pos_pix, angle, col_tick_txt, label);
+ }
+ }
+}
+
+void RenderAxisLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2]) {
+ ImPlot3DPoint range_center = plot.RangeCenter();
+ for (int a = 0; a < 3; a++) {
+ const ImPlot3DAxis& axis = plot.Axes[a];
+ if (!axis.HasLabel())
+ continue;
+
+ const char* label = axis.GetLabel();
+
+ // Corner indices
+ int idx0 = axis_corners[a][0];
+ int idx1 = axis_corners[a][1];
+
+ // If normal to the 2D plot, ignore axis label
+ if (idx0 == idx1)
+ continue;
+
+ // Position at the end of the axis
+ ImPlot3DPoint label_pos = (corners[idx0] + corners[idx1]) * 0.5f;
+ // Add offset
+ label_pos += (label_pos - range_center) * 0.4f;
+
+ // Convert to pixel coordinates
+ ImVec2 label_pos_pix = PlotToPixels(label_pos);
+
+ // Adjust label position and angle
+ ImU32 col_ax_txt = GetStyleColorU32(ImPlot3DCol_AxisText);
+
+ // Compute text angle
+ ImVec2 screen_delta = corners_pix[idx1] - corners_pix[idx0];
+ float angle = atan2f(-screen_delta.y, screen_delta.x);
+ if (angle > IM_PI * 0.5f)
+ angle -= IM_PI;
+ if (angle < -IM_PI * 0.5f)
+ angle += IM_PI;
+
+ AddTextRotated(draw_list, label_pos_pix, angle, col_ax_txt, label);
+ }
+}
+
+// Function to compute active faces based on the rotation
+// If the plot is close to 2D, plane_2d is set to the plane index (0 -> YZ, 1 -> XZ, 2 -> XY)
+// plane_2d is set to -1 otherwise
+void ComputeActiveFaces(bool* active_faces, const ImPlot3DQuat& rotation, int* plane_2d = nullptr) {
+ if (plane_2d)
+ *plane_2d = -1;
+
+ ImPlot3DPoint rot_face_n[3] = {
+ rotation * ImPlot3DPoint(1.0f, 0.0f, 0.0f),
+ rotation * ImPlot3DPoint(0.0f, 1.0f, 0.0f),
+ rotation * ImPlot3DPoint(0.0f, 0.0f, 1.0f),
+ };
+
+ int num_deg = 0; // Check number of planes that are degenerate (seen as a line)
+ for (int i = 0; i < 3; ++i) {
+ // Determine the active face based on the Z component
+ if (fabs(rot_face_n[i].z) < 0.025) {
+ // If aligned with the plane, choose the min face for bottom/left
+ active_faces[i] = rot_face_n[i].x + rot_face_n[i].y < 0.0f;
+ num_deg++;
+ } else {
+ // Otherwise, determine based on the Z component
+ active_faces[i] = rot_face_n[i].z < 0.0f;
+ // Set this plane as possible 2d plane
+ if (plane_2d)
+ *plane_2d = i;
+ }
+ }
+ // Only return 2d plane if there are exactly 2 degenerate planes
+ if (num_deg != 2 && plane_2d)
+ *plane_2d = -1;
+}
+
+// Function to compute the box corners in plot space
+void ComputeBoxCorners(ImPlot3DPoint* corners, const ImPlot3DPoint& range_min, const ImPlot3DPoint& range_max) {
+ corners[0] = ImPlot3DPoint(range_min.x, range_min.y, range_min.z); // 0
+ corners[1] = ImPlot3DPoint(range_max.x, range_min.y, range_min.z); // 1
+ corners[2] = ImPlot3DPoint(range_max.x, range_max.y, range_min.z); // 2
+ corners[3] = ImPlot3DPoint(range_min.x, range_max.y, range_min.z); // 3
+ corners[4] = ImPlot3DPoint(range_min.x, range_min.y, range_max.z); // 4
+ corners[5] = ImPlot3DPoint(range_max.x, range_min.y, range_max.z); // 5
+ corners[6] = ImPlot3DPoint(range_max.x, range_max.y, range_max.z); // 6
+ corners[7] = ImPlot3DPoint(range_min.x, range_max.y, range_max.z); // 7
+}
+
+// Function to compute the box corners in pixel space
+void ComputeBoxCornersPix(ImVec2* corners_pix, const ImPlot3DPoint* corners) {
+ for (int i = 0; i < 8; i++) {
+ corners_pix[i] = PlotToPixels(corners[i]);
+ }
+}
+
+void RenderPlotBox(ImDrawList* draw_list, const ImPlot3DPlot& plot) {
+ // Get plot parameters
+ const ImRect& plot_area = plot.PlotRect;
+ const ImPlot3DQuat& rotation = plot.Rotation;
+ ImPlot3DPoint range_min = plot.RangeMin();
+ ImPlot3DPoint range_max = plot.RangeMax();
+ ImPlot3DPoint range_center = plot.RangeCenter();
+
+ // Compute active faces
+ bool active_faces[3];
+ int plane_2d = -1;
+ ComputeActiveFaces(active_faces, rotation, &plane_2d);
+ bool is_2d = plane_2d != -1;
+
+ // Compute box corners in plot space
+ ImPlot3DPoint corners[8];
+ ComputeBoxCorners(corners, range_min, range_max);
+
+ // Compute box corners in pixel space
+ ImVec2 corners_pix[8];
+ ComputeBoxCornersPix(corners_pix, corners);
+
+ // Compute axes start and end corners (given current rotation)
+ int axis_corners[3][2];
+ if (is_2d) {
+ int face = plane_2d + 3 * active_faces[plane_2d]; // Face of the 2D plot
+ int common_edges[2] = {-1, -1}; // Edges shared by the 3 faces
+
+ // Find the common edges between the 3 faces
+ for (int i = 0; i < 4; i++) {
+ int edge = face_edges[face][i];
+ for (int j = 0; j < 2; j++) {
+ int axis = (plane_2d + 1 + j) % 3;
+ int face_idx = axis + active_faces[axis] * 3;
+ for (int k = 0; k < 4; k++) {
+ if (face_edges[face_idx][k] == edge) {
+ common_edges[j] = edge;
+ break;
+ }
+ }
+ }
+ }
+
+ // Get corners from 2 edges (origin is the corner in common)
+ int origin_corner = -1;
+ int x_corner = -1;
+ int y_corner = -1;
+ for (int i = 0; i < 2; i++)
+ for (int j = 0; j < 2; j++)
+ if (edges[common_edges[0]][i] == edges[common_edges[1]][j]) {
+ origin_corner = edges[common_edges[0]][i];
+ x_corner = edges[common_edges[0]][!i];
+ y_corner = edges[common_edges[1]][!j];
+ }
+
+ // Swap x and y if they are flipped
+ ImVec2 x_vec = corners_pix[x_corner] - corners_pix[origin_corner];
+ ImVec2 y_vec = corners_pix[y_corner] - corners_pix[origin_corner];
+ if (y_vec.x > x_vec.x)
+ ImSwap(x_corner, y_corner);
+
+ // Check which 3d axis the 2d axis refers to
+ ImPlot3DPoint origin_3d = corners[origin_corner];
+ ImPlot3DPoint x_3d = (corners[x_corner] - origin_3d).Normalized();
+ ImPlot3DPoint y_3d = (corners[y_corner] - origin_3d).Normalized();
+ int x_axis = -1;
+ bool x_inverted = false;
+ int y_axis = -1;
+ bool y_inverted = false;
+ for (int i = 0; i < 2; i++) {
+ int axis_i = (plane_2d + 1 + i) % 3;
+ if (y_axis != -1 || (ImAbs(x_3d[axis_i]) > 1e-8f && x_axis == -1)) {
+ x_axis = axis_i;
+ x_inverted = x_3d[axis_i] < 0.0f;
+ } else {
+ y_axis = axis_i;
+ y_inverted = y_3d[axis_i] < 0.0f;
+ }
+ }
+
+ // Set the 3d axis corners based on the 2d axis corners
+ axis_corners[plane_2d][0] = -1;
+ axis_corners[plane_2d][1] = -1;
+ if (x_inverted) {
+ axis_corners[x_axis][0] = x_corner;
+ axis_corners[x_axis][1] = origin_corner;
+ } else {
+ axis_corners[x_axis][0] = origin_corner;
+ axis_corners[x_axis][1] = x_corner;
+ }
+ if (y_inverted) {
+ axis_corners[y_axis][0] = y_corner;
+ axis_corners[y_axis][1] = origin_corner;
+ } else {
+ axis_corners[y_axis][0] = origin_corner;
+ axis_corners[y_axis][1] = y_corner;
+ }
+ } else {
+ int index = (active_faces[0] << 2) | (active_faces[1] << 1) | (active_faces[2]);
+ for (int a = 0; a < 3; a++) {
+ axis_corners[a][0] = axis_corners_lookup_3d[index][a][0];
+ axis_corners[a][1] = axis_corners_lookup_3d[index][a][1];
+ }
+ }
+
+ // Render components
+ RenderPlotBackground(draw_list, plot, corners_pix, active_faces, plane_2d);
+ RenderPlotBorder(draw_list, plot, corners_pix, active_faces, plane_2d);
+ RenderGrid(draw_list, plot, corners, active_faces, plane_2d);
+ RenderTickMarks(draw_list, plot, corners, corners_pix, axis_corners, plane_2d);
+ RenderTickLabels(draw_list, plot, corners, corners_pix, axis_corners);
+ RenderAxisLabels(draw_list, plot, corners, corners_pix, axis_corners);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Formatter
+//-----------------------------------------------------------------------------
+
+int Formatter_Default(float value, char* buff, int size, void* data) {
+ char* fmt = (char*)data;
+ return ImFormatString(buff, size, fmt, value);
+}
+
+//------------------------------------------------------------------------------
+// [SECTION] Locator
+//------------------------------------------------------------------------------
+
+double NiceNum(double x, bool round) {
+ double f;
+ double nf;
+ int expv = (int)floor(ImLog10(x));
+ f = x / ImPow(10.0, (double)expv);
+ if (round)
+ if (f < 1.5)
+ nf = 1;
+ else if (f < 3)
+ nf = 2;
+ else if (f < 7)
+ nf = 5;
+ else
+ nf = 10;
+ else if (f <= 1)
+ nf = 1;
+ else if (f <= 2)
+ nf = 2;
+ else if (f <= 5)
+ nf = 5;
+ else
+ nf = 10;
+ return nf * ImPow(10.0, expv);
+}
+
+void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data) {
+ if (range.Min == range.Max)
+ return;
+ const int nMinor = 5;
+ const int nMajor = 3;
+ const int max_ticks_labels = 7;
+ const double nice_range = NiceNum(range.Size() * 0.99, false);
+ const double interval = NiceNum(nice_range / (nMajor - 1), true);
+ const double graphmin = floor(range.Min / interval) * interval;
+ const double graphmax = ceil(range.Max / interval) * interval;
+ bool first_major_set = false;
+ int first_major_idx = 0;
+ const int idx0 = ticker.TickCount(); // ticker may have user custom ticks
+ ImVec2 total_size(0, 0);
+ for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
+ // is this zero? combat zero formatting issues
+ if (major - interval < 0 && major + interval > 0)
+ major = 0;
+ if (range.Contains(major)) {
+ if (!first_major_set) {
+ first_major_idx = ticker.TickCount();
+ first_major_set = true;
+ }
+ total_size += ticker.AddTick(major, true, true, formatter, formatter_data).LabelSize;
+ }
+ for (int i = 1; i < nMinor; ++i) {
+ double minor = major + i * interval / nMinor;
+ if (range.Contains(minor)) {
+ total_size += ticker.AddTick(minor, false, true, formatter, formatter_data).LabelSize;
+ }
+ }
+ }
+
+ // Prune tick labels
+ if (ticker.TickCount() > max_ticks_labels) {
+ for (int i = first_major_idx - 1; i >= idx0; i -= 2)
+ ticker.Ticks[i].ShowLabel = false;
+ for (int i = first_major_idx + 1; i < ticker.TickCount(); i += 2)
+ ticker.Ticks[i].ShowLabel = false;
+ }
+}
+
+//------------------------------------------------------------------------------
+// [SECTION] Context Menus
+//------------------------------------------------------------------------------
+
+bool ShowLegendContextMenu(ImPlot3DLegend& legend, bool visible) {
+ const float s = ImGui::GetFrameHeight();
+ bool ret = false;
+ if (ImGui::Checkbox("Show", &visible))
+ ret = true;
+ if (ImGui::RadioButton("H", ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal)))
+ legend.Flags |= ImPlot3DLegendFlags_Horizontal;
+ ImGui::SameLine();
+ if (ImGui::RadioButton("V", !ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal)))
+ legend.Flags &= ~ImPlot3DLegendFlags_Horizontal;
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
+ // clang-format off
+ if (ImGui::Button("NW",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_NorthWest; } ImGui::SameLine();
+ if (ImGui::Button("N", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_North; } ImGui::SameLine();
+ if (ImGui::Button("NE",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_NorthEast; }
+ if (ImGui::Button("W", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_West; } ImGui::SameLine();
+ if (ImGui::InvisibleButton("C", ImVec2(1.5f*s,s))) { } ImGui::SameLine();
+ if (ImGui::Button("E", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_East; }
+ if (ImGui::Button("SW",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_SouthWest; } ImGui::SameLine();
+ if (ImGui::Button("S", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_South; } ImGui::SameLine();
+ if (ImGui::Button("SE",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_SouthEast; }
+ // clang-format on
+ ImGui::PopStyleVar();
+ return ret;
+}
+
+void ShowAxisContextMenu(ImPlot3DAxis& axis) {
+ ImGui::PushItemWidth(75);
+ bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting();
+ bool label = axis.HasLabel();
+ bool grid = axis.HasGridLines();
+ bool ticks = axis.HasTickMarks();
+ bool labels = axis.HasTickLabels();
+ double drag_speed = (axis.Range.Size() <= FLT_EPSILON) ? FLT_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits.
+
+ ImGui::BeginDisabled(always_locked);
+ ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_LockMin);
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ ImGui::BeginDisabled(axis.IsLockedMin() || always_locked);
+ float temp_min = axis.Range.Min;
+ if (ImGui::DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - FLT_EPSILON)) {
+ axis.SetMin(temp_min, true);
+ }
+ ImGui::EndDisabled();
+
+ ImGui::BeginDisabled(always_locked);
+ ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_LockMax);
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+ ImGui::BeginDisabled(axis.IsLockedMax() || always_locked);
+ float temp_max = axis.Range.Max;
+ if (ImGui::DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + FLT_EPSILON, HUGE_VAL)) {
+ axis.SetMax(temp_max, true);
+ }
+ ImGui::EndDisabled();
+
+ ImGui::Separator();
+
+ // Flags
+ ImGui::CheckboxFlags("Auto-Fit", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_AutoFit);
+ ImGui::Separator();
+
+ ImGui::BeginDisabled(axis.Label.empty());
+ if (ImGui::Checkbox("Label", &label))
+ ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoLabel);
+ ImGui::EndDisabled();
+
+ if (ImGui::Checkbox("Grid Lines", &grid))
+ ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoGridLines);
+ if (ImGui::Checkbox("Tick Marks", &ticks))
+ ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoTickMarks);
+ if (ImGui::Checkbox("Tick Labels", &labels))
+ ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoTickLabels);
+}
+
+void ShowPlotContextMenu(ImPlot3DPlot& plot) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ const bool owns_legend = gp.CurrentItems == &plot.Items;
+
+ char buf[16] = {};
+
+ const char* axis_labels[3] = {"X-Axis", "Y-Axis", "Z-Axis"};
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+ ImGui::PushID(i);
+ ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "X-Axis" : "X-Axis %d", i + 1);
+ if (ImGui::BeginMenu(axis.HasLabel() ? axis.GetLabel() : axis_labels[i])) {
+ ShowAxisContextMenu(axis);
+ ImGui::EndMenu();
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::Separator();
+ if ((ImGui::BeginMenu("Legend"))) {
+ if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend)))
+ ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend);
+ ImGui::EndMenu();
+ }
+
+ if ((ImGui::BeginMenu("Settings"))) {
+ ImGui::BeginDisabled(plot.Title.empty());
+ if (ImGui::MenuItem("Title", nullptr, plot.HasTitle()))
+ ImFlipFlag(plot.Flags, ImPlot3DFlags_NoTitle);
+ ImGui::EndDisabled();
+ ImGui::EndMenu();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Begin/End Plot
+//-----------------------------------------------------------------------------
+
+bool BeginPlot(const char* title_id, const ImVec2& size, ImPlot3DFlags flags) {
+ IMPLOT3D_CHECK_CTX();
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Mismatched BeginPlot()/EndPlot()!");
+
+ // Get window
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ // Skip if needed
+ if (window->SkipItems)
+ return false;
+
+ // Get or create plot
+ const ImGuiID ID = window->GetID(title_id);
+ const bool just_created = gp.Plots.GetByKey(ID) == nullptr;
+ gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID);
+ gp.CurrentItems = &gp.CurrentPlot->Items;
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+
+ // Populate plot
+ plot.ID = ID;
+ plot.JustCreated = just_created;
+ if (just_created) {
+ plot.Rotation = init_rotation;
+ plot.FitThisFrame = true;
+ for (int i = 0; i < 3; i++) {
+ plot.Axes[i] = ImPlot3DAxis();
+ plot.Axes[i].FitThisFrame = true;
+ }
+ }
+ if (plot.PreviousFlags != flags)
+ plot.Flags = flags;
+ plot.PreviousFlags = flags;
+ plot.SetupLocked = false;
+ plot.OpenContextThisFrame = false;
+
+ // Populate title
+ plot.SetTitle(title_id);
+
+ // Calculate frame size
+ ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);
+ if (frame_size.x < gp.Style.PlotMinSize.x && size.x < 0.0f)
+ frame_size.x = gp.Style.PlotMinSize.x;
+ if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f)
+ frame_size.y = gp.Style.PlotMinSize.y;
+
+ // Create child window to capture scroll
+ ImGui::BeginChild(title_id, frame_size, false, ImGuiWindowFlags_NoScrollbar);
+ window = ImGui::GetCurrentWindow();
+ window->ScrollMax.y = 1.0f;
+
+ plot.FrameRect = ImRect(window->DC.CursorPos, window->DC.CursorPos + frame_size);
+ ImGui::ItemSize(plot.FrameRect);
+ if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect)) {
+ gp.CurrentPlot = nullptr;
+ gp.CurrentItems = nullptr;
+ ImGui::EndChild();
+ return false;
+ }
+
+ // Reset legend
+ plot.Items.Legend.Reset();
+
+ // Push frame rect clipping
+ ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
+ plot.DrawList._Flags = window->DrawList->Flags;
+ plot.DrawList._SharedData = ImGui::GetDrawListSharedData();
+
+ return true;
+}
+
+void EndPlot() {
+ IMPLOT3D_CHECK_CTX();
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+
+ // Move triangles from 3D draw list to ImGui draw list
+ plot.DrawList.SortedMoveToImGuiDrawList();
+
+ // Handle data fitting
+ if (plot.FitThisFrame) {
+ plot.FitThisFrame = false;
+ for (int i = 0; i < 3; i++) {
+ if (plot.Axes[i].FitThisFrame) {
+ plot.Axes[i].FitThisFrame = false;
+ plot.Axes[i].ApplyFit();
+ }
+ }
+ }
+
+ // Lock setup if not already done
+ SetupLock();
+
+ // Reset legend hover
+ plot.Items.Legend.Hovered = false;
+
+ // Render legend
+ RenderLegend();
+
+ // Render mouse position
+ RenderMousePos();
+
+ // Legend context menu
+ if (ImGui::BeginPopup("##LegendContext")) {
+ ImGui::Text("Legend");
+ ImGui::Separator();
+ if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend)))
+ ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend);
+ ImGui::EndPopup();
+ }
+
+ // Axis context menus
+ static const char* axis_contexts[3] = {"##XAxisContext", "##YAxisContext", "##ZAxisContext"};
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+ if (ImGui::BeginPopup(axis_contexts[i])) {
+ ImGui::Text(axis.HasLabel() ? axis.GetLabel() : "%c-Axis", 'X' + i);
+ ImGui::Separator();
+ ShowAxisContextMenu(axis);
+ ImGui::EndPopup();
+ }
+ }
+
+ // Plot context menu
+ if (ImGui::BeginPopup("##PlotContext")) {
+ ShowPlotContextMenu(plot);
+ ImGui::EndPopup();
+ }
+
+ // Pop frame rect clipping
+ ImGui::PopClipRect();
+
+ // End child window
+ ImGui::EndChild();
+
+ // Reset current plot
+ gp.CurrentPlot = nullptr;
+ gp.CurrentItems = nullptr;
+
+ // Reset the plot items for the next frame
+ for (int i = 0; i < plot.Items.GetItemCount(); i++)
+ plot.Items.GetItemByIndex(i)->SeenThisFrame = false;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Setup
+//-----------------------------------------------------------------------------
+
+void SetupAxis(ImAxis3D idx, const char* label, ImPlot3DAxisFlags flags) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
+ "SetupAxis() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!");
+
+ // Get plot and axis
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ ImPlot3DAxis& axis = plot.Axes[idx];
+ if (axis.PreviousFlags != flags)
+ axis.Flags = flags;
+ axis.PreviousFlags = flags;
+ axis.SetLabel(label);
+}
+
+void SetupAxisLimits(ImAxis3D idx, double min_lim, double max_lim, ImPlot3DCond cond) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
+ "SetupAxisLimits() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ ImPlot3DAxis& axis = plot.Axes[idx];
+ if (!plot.Initialized || cond == ImPlot3DCond_Always) {
+ axis.SetRange(min_lim, max_lim);
+ axis.RangeCond = cond;
+ axis.FitThisFrame = false;
+ }
+}
+
+void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags, ImPlot3DAxisFlags y_flags, ImPlot3DAxisFlags z_flags) {
+ SetupAxis(ImAxis3D_X, x_label, x_flags);
+ SetupAxis(ImAxis3D_Y, y_label, y_flags);
+ SetupAxis(ImAxis3D_Z, z_label, z_flags);
+}
+
+void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, ImPlot3DCond cond) {
+ SetupAxisLimits(ImAxis3D_X, x_min, x_max, cond);
+ SetupAxisLimits(ImAxis3D_Y, y_min, y_max, cond);
+ SetupAxisLimits(ImAxis3D_Z, z_min, z_max, cond);
+ if (cond == ImPlot3DCond_Once)
+ GImPlot3D->CurrentPlot->FitThisFrame = false;
+}
+
+void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
+ "SetupLegend() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!");
+ IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr,
+ "SetupLegend() needs to be called within an itemized context!");
+ ImPlot3DLegend& legend = gp.CurrentItems->Legend;
+ if (legend.PreviousLocation != location)
+ legend.Location = location;
+ legend.PreviousLocation = location;
+ if (legend.PreviousFlags != flags)
+ legend.Flags = flags;
+ legend.PreviousFlags = flags;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plot Utils
+//-----------------------------------------------------------------------------
+
+ImPlot3DPlot* GetCurrentPlot() {
+ return GImPlot3D->CurrentPlot;
+}
+
+void BustPlotCache() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ gp.Plots.Clear();
+}
+
+ImVec2 PlotToPixels(const ImPlot3DPoint& point) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!");
+ return NDCToPixels(PlotToNDC(point));
+}
+
+ImVec2 PlotToPixels(double x, double y, double z) {
+ return PlotToPixels(ImPlot3DPoint(x, y, z));
+}
+
+ImPlot3DRay PixelsToPlotRay(const ImVec2& pix) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlotRay() needs to be called between BeginPlot() and EndPlot()!");
+ return NDCRayToPlotRay(PixelsToNDCRay(pix));
+}
+
+ImPlot3DRay PixelsToPlotRay(double x, double y) {
+ return PixelsToPlotRay(ImVec2(x, y));
+}
+
+ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlotPlane() needs to be called between BeginPlot() and EndPlot()!");
+
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ ImPlot3DRay ray = PixelsToNDCRay(pix);
+ const ImPlot3DPoint& O = ray.Origin;
+ const ImPlot3DPoint& D = ray.Direction;
+
+ // Helper lambda to check intersection with a given coordinate and return intersection point if valid.
+ auto IntersectPlane = [&](float coord) -> ImPlot3DPoint {
+ // Solve for t in O[axis] + D[axis]*t = coord
+ float denom = 0.0f;
+ float numer = 0.0f;
+ if (plane == ImPlane3D_YZ) {
+ denom = D.x;
+ numer = coord - O.x;
+ } else if (plane == ImPlane3D_XZ) {
+ denom = D.y;
+ numer = coord - O.y;
+ } else if (plane == ImPlane3D_XY) {
+ denom = D.z;
+ numer = coord - O.z;
+ }
+
+ if (ImAbs(denom) < 1e-12f) {
+ // Ray is parallel or nearly parallel to the plane
+ return ImPlot3DPoint(NAN, NAN, NAN);
+ }
+
+ float t = numer / denom;
+ if (t < 0.0f) {
+ // Intersection behind the ray origin
+ return ImPlot3DPoint(NAN, NAN, NAN);
+ }
+
+ return O + D * t;
+ };
+
+ // Helper lambda to check if point P is within the plot box
+ auto InRange = [&](const ImPlot3DPoint& P) {
+ return P.x >= -0.5 && P.x <= 0.5 &&
+ P.y >= -0.5 && P.y <= 0.5 &&
+ P.z >= -0.5 && P.z <= 0.5;
+ };
+
+ // Compute which plane to intersect with
+ bool active_faces[3];
+ ComputeActiveFaces(active_faces, plot.Rotation);
+
+ // Calculate intersection point with the planes
+ ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5 : -0.5);
+ if (P.IsNaN())
+ return P;
+
+ // Handle mask (if one of the intersections is out of range, set it to NAN)
+ if (mask) {
+ switch (plane) {
+ case ImPlane3D_YZ:
+ if (!InRange(ImPlot3DPoint(0.0, P.y, P.z)))
+ return ImPlot3DPoint(NAN, NAN, NAN);
+ break;
+ case ImPlane3D_XZ:
+ if (!InRange(ImPlot3DPoint(P.x, 0.0, P.z)))
+ return ImPlot3DPoint(NAN, NAN, NAN);
+ break;
+ case ImPlane3D_XY:
+ if (!InRange(ImPlot3DPoint(P.x, P.y, 0.0)))
+ return ImPlot3DPoint(NAN, NAN, NAN);
+ break;
+ }
+ }
+
+ return NDCToPlot(P);
+}
+
+ImPlot3DPoint PixelsToPlotPlane(double x, double y, ImPlane3D plane, bool mask) {
+ return PixelsToPlotPlane(ImVec2(x, y), plane, mask);
+}
+
+ImVec2 GetPlotPos() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!");
+ SetupLock();
+ return gp.CurrentPlot->PlotRect.Min;
+}
+
+ImVec2 GetPlotSize() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!");
+ SetupLock();
+ return gp.CurrentPlot->PlotRect.GetSize();
+}
+
+ImVec2 GetFramePos() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetFramePos() needs to be called between BeginPlot() and EndPlot()!");
+ return gp.CurrentPlot->FrameRect.Min;
+}
+
+ImVec2 GetFrameSize() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetFrameSize() needs to be called between BeginPlot() and EndPlot()!");
+ return gp.CurrentPlot->FrameRect.GetSize();
+}
+
+ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToNDC() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ SetupLock();
+
+ ImPlot3DPoint ndc_point;
+ for (int i = 0; i < 3; i++)
+ ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]);
+ return ndc_point;
+}
+
+ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCToPlot() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ SetupLock();
+
+ ImPlot3DPoint plot_point;
+ for (int i = 0; i < 3; i++)
+ plot_point[i] = plot.Axes[i].NDCToPlot(point[i]);
+ return plot_point;
+}
+
+ImVec2 NDCToPixels(const ImPlot3DPoint& point) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCToPixels() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ SetupLock();
+
+ float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
+ ImVec2 center = plot.PlotRect.GetCenter();
+ ImPlot3DPoint point_pix = zoom * (plot.Rotation * point);
+ point_pix.y *= -1.0f; // Invert y-axis
+ point_pix.x += center.x;
+ point_pix.y += center.y;
+
+ return {point_pix.x, point_pix.y};
+}
+
+ImPlot3DRay PixelsToNDCRay(const ImVec2& pix) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToNDCRay() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ SetupLock();
+
+ // Calculate zoom factor and plot center
+ float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
+ ImVec2 center = plot.PlotRect.GetCenter();
+
+ // Undo screen transformations to get back to NDC space
+ float x = (pix.x - center.x) / zoom;
+ float y = -(pix.y - center.y) / zoom; // Invert y-axis
+
+ // Define near and far points in NDC space along the z-axis
+ ImPlot3DPoint ndc_near = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, -10.0f);
+ ImPlot3DPoint ndc_far = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, 10.0f);
+
+ // Create the ray in NDC space
+ ImPlot3DRay ndc_ray;
+ ndc_ray.Origin = ndc_near;
+ ndc_ray.Direction = (ndc_far - ndc_near).Normalized();
+
+ return ndc_ray;
+}
+
+ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCRayToPlotRay() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ SetupLock();
+
+ // Convert NDC origin and a point along the ray to plot coordinates
+ ImPlot3DPoint plot_origin = NDCToPlot(ray.Origin);
+ ImPlot3DPoint ndc_point_along_ray = ray.Origin + ray.Direction;
+ ImPlot3DPoint plot_point_along_ray = NDCToPlot(ndc_point_along_ray);
+
+ // Compute the direction in plot coordinates
+ ImPlot3DPoint plot_direction = (plot_point_along_ray - plot_origin).Normalized();
+
+ // Create the ray in plot coordinates
+ ImPlot3DRay plot_ray;
+ plot_ray.Origin = plot_origin;
+ plot_ray.Direction = plot_direction;
+
+ return plot_ray;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Setup Utils
+//-----------------------------------------------------------------------------
+
+static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f;
+static const float ANIMATION_ANGULAR_VELOCITY = 2 * 3.1415f;
+
+void HandleInput(ImPlot3DPlot& plot) {
+ ImGuiIO& IO = ImGui::GetIO();
+
+ // clang-format off
+ const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowOverlap
+ | ImGuiButtonFlags_PressedOnClick
+ | ImGuiButtonFlags_PressedOnDoubleClick
+ | ImGuiButtonFlags_MouseButtonLeft
+ | ImGuiButtonFlags_MouseButtonRight
+ | ImGuiButtonFlags_MouseButtonMiddle;
+ // clang-format on
+ const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect, plot.ID, &plot.Hovered, &plot.Held, plot_button_flags);
+#if (IMGUI_VERSION_NUM < 18966)
+ ImGui::SetItemAllowOverlap(); // Handled by ButtonBehavior()
+#endif
+
+ // State
+ const ImVec2 rot_drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
+ const bool rotating = ImLengthSqr(rot_drag) > MOUSE_CURSOR_DRAG_THRESHOLD;
+
+ // Check if any axis/plane is hovered
+ const ImPlot3DQuat& rotation = plot.Rotation;
+ ImPlot3DPoint range_min = plot.RangeMin();
+ ImPlot3DPoint range_max = plot.RangeMax();
+ bool active_faces[3];
+ int plane_2d = -1;
+ ComputeActiveFaces(active_faces, rotation, &plane_2d);
+ ImPlot3DPoint corners[8];
+ ComputeBoxCorners(corners, range_min, range_max);
+ ImVec2 corners_pix[8];
+ ComputeBoxCornersPix(corners_pix, corners);
+ int hovered_plane_idx = -1;
+ int hovered_plane = GetMouseOverPlane(plot, active_faces, corners_pix, &hovered_plane_idx);
+ int hovered_edge_idx = -1;
+ int hovered_axis = GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d, &hovered_edge_idx);
+ if (hovered_axis != -1) {
+ hovered_plane_idx = -1;
+ hovered_plane = -1;
+ }
+
+ // If the user is no longer pressing the translation/zoom buttons, set axes as not held
+ if (!ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
+ for (int i = 0; i < 3; i++)
+ plot.Axes[i].Held = false;
+ }
+
+ // Reset held edge/plane indices (it will be set if mouse button is down)
+ if (!plot.Held) {
+ plot.HeldEdgeIdx = -1;
+ plot.HeldPlaneIdx = -1;
+ }
+
+ // Check which axes should be transformed (fit/zoom/translate)
+ bool any_axis_held = plot.Axes[0].Held || plot.Axes[1].Held || plot.Axes[2].Held;
+ static bool transform_axis[3] = {false, false, false};
+ if (!any_axis_held) {
+ // Only update the transformation axes if the user is not already performing a transformation
+ transform_axis[0] = transform_axis[1] = transform_axis[2] = false;
+ if (hovered_axis != -1) {
+ transform_axis[hovered_axis] = true;
+ } else if (hovered_plane != -1) {
+ transform_axis[(hovered_plane + 1) % 3] = true;
+ transform_axis[(hovered_plane + 2) % 3] = true;
+ } else {
+ transform_axis[0] = transform_axis[1] = transform_axis[2] = true;
+ }
+ }
+
+ // Handle translation/zoom fit with double click
+ if (plot_clicked && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
+ plot.FitThisFrame = true;
+ for (int i = 0; i < 3; i++)
+ plot.Axes[i].FitThisFrame = transform_axis[i];
+ }
+
+ // Handle auto fit
+ for (int i = 0; i < 3; i++)
+ if (plot.Axes[i].IsAutoFitting()) {
+ plot.FitThisFrame = true;
+ plot.Axes[i].FitThisFrame = true;
+ }
+
+ // Handle translation with right mouse button
+ if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
+ ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y);
+
+ if (transform_axis[0] && transform_axis[1] && transform_axis[2]) {
+ // Perform unconstrained translation (translate on the viewer plane)
+
+ // Compute delta_pixels in 3D (invert y-axis)
+ ImPlot3DPoint delta_pixels(delta.x, -delta.y, 0.0f);
+
+ // Convert delta to NDC space
+ float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
+ ImPlot3DPoint delta_NDC = plot.Rotation.Inverse() * (delta_pixels / zoom);
+
+ // Convert delta to plot space
+ ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin());
+
+ // Adjust plot range to translate the plot
+ for (int i = 0; i < 3; i++) {
+ if (transform_axis[i]) {
+ plot.Axes[i].SetRange(plot.Axes[i].Range.Min - delta_plot[i], plot.Axes[i].Range.Max - delta_plot[i]);
+ plot.Axes[i].Held = true;
+ }
+ // If no axis was held before (user started translating in this frame), set the held edge/plane indices
+ if (!any_axis_held) {
+ plot.HeldEdgeIdx = hovered_edge_idx;
+ plot.HeldPlaneIdx = hovered_plane_idx;
+ }
+ }
+ } else if (transform_axis[0] || transform_axis[1] || transform_axis[2]) {
+ // Translate along plane/axis
+
+ // Mouse delta in pixels
+ ImVec2 mouse_pos = ImGui::GetMousePos();
+ ImVec2 mouse_delta(IO.MouseDelta.x, IO.MouseDelta.y);
+
+ // TODO Choose best plane given transform_axis and current view
+ // For now it crashes when transforming only one axis in the 2D view
+ ImPlane3D plane = ImPlane3D_XY;
+ if (transform_axis[1] && transform_axis[2])
+ plane = ImPlane3D_YZ;
+ else if (transform_axis[0] && transform_axis[2])
+ plane = ImPlane3D_XZ;
+ else if (transform_axis[2])
+ plane = ImPlane3D_YZ;
+
+ ImPlot3DPoint mouse_plot = PixelsToPlotPlane(mouse_pos, plane, false);
+ ImPlot3DPoint mouse_delta_plot = PixelsToPlotPlane(mouse_pos + mouse_delta, plane, false);
+ ImPlot3DPoint delta_plot = mouse_delta_plot - mouse_plot;
+
+ // Apply translation to the selected axes
+ for (int i = 0; i < 3; i++) {
+ if (transform_axis[i]) {
+ plot.Axes[i].SetRange(plot.Axes[i].Range.Min - delta_plot[i],
+ plot.Axes[i].Range.Max - delta_plot[i]);
+ plot.Axes[i].Held = true;
+ }
+ if (!any_axis_held) {
+ plot.HeldEdgeIdx = hovered_edge_idx;
+ plot.HeldPlaneIdx = hovered_plane_idx;
+ }
+ }
+ }
+ }
+
+ // Handle context click with right mouse button
+ if (plot.Held && ImGui::IsMouseClicked(ImGuiMouseButton_Right))
+ plot.ContextClick = true;
+ if (rotating || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right))
+ plot.ContextClick = false;
+
+ // Handle reset rotation with left mouse double click
+ if (plot.Held && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
+ plot.RotationAnimationEnd = plot.Rotation;
+
+ // Calculate rotation to align the z-axis with the camera direction
+ if (hovered_plane == -1) {
+ plot.RotationAnimationEnd = init_rotation;
+ } else {
+ // Compute plane normal
+ ImPlot3DPoint axis_normal = ImPlot3DPoint(0.0f, 0.0f, 0.0f);
+ axis_normal[hovered_plane] = active_faces[hovered_plane] ? -1.0f : 1.0f;
+
+ // Compute rotation to align the plane normal with the z-axis
+ ImPlot3DQuat align_normal = ImPlot3DQuat::FromTwoVectors(plot.RotationAnimationEnd * axis_normal, ImPlot3DPoint(0.0f, 0.0f, 1.0f));
+ plot.RotationAnimationEnd = align_normal * plot.RotationAnimationEnd;
+
+ if (hovered_plane != 2) {
+ // Compute rotation to point z-axis up
+ ImPlot3DQuat align_up = ImPlot3DQuat::FromTwoVectors(plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, 0.0f, 1.0f), ImPlot3DPoint(0.0f, 1.0f, 0.0f));
+ plot.RotationAnimationEnd = align_up * plot.RotationAnimationEnd;
+ } else {
+ // Find the axis most aligned with the up direction
+ ImPlot3DPoint up(0.0f, 1.0f, 0.0f);
+ ImPlot3DPoint x_axis = plot.RotationAnimationEnd * ImPlot3DPoint(1.0f, 0.0f, 0.0f);
+ ImPlot3DPoint y_axis = plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, 1.0f, 0.0f);
+ ImPlot3DPoint neg_x_axis = plot.RotationAnimationEnd * ImPlot3DPoint(-1.0f, 0.0f, 0.0f);
+ ImPlot3DPoint neg_y_axis = plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, -1.0f, 0.0f);
+
+ struct AxisAlignment {
+ ImPlot3DPoint axis;
+ float dot;
+ };
+
+ AxisAlignment candidates[] = {
+ {x_axis, x_axis.Dot(up)},
+ {y_axis, y_axis.Dot(up)},
+ {neg_x_axis, neg_x_axis.Dot(up)},
+ {neg_y_axis, neg_y_axis.Dot(up)},
+ };
+
+ // Find the candidate with the maximum dot product
+ AxisAlignment* best_candidate = &candidates[0];
+ for (int i = 1; i < 4; ++i) {
+ if (candidates[i].dot > best_candidate->dot) {
+ best_candidate = &candidates[i];
+ }
+ }
+
+ // Compute the rotation to align the best candidate with the up direction
+ ImPlot3DQuat align_up = ImPlot3DQuat::FromTwoVectors(best_candidate->axis, up);
+ plot.RotationAnimationEnd = align_up * plot.RotationAnimationEnd;
+ }
+ }
+
+ // Compute the angular distance between current and target rotation
+ float dot_product = ImClamp(plot.Rotation.Dot(plot.RotationAnimationEnd), -1.0f, 1.0f);
+ float angle = 2.0f * acosf(fabsf(dot_product));
+
+ // Calculate animation time for constant the angular velocity
+ plot.AnimationTime = angle / ANIMATION_ANGULAR_VELOCITY;
+ }
+
+ // Handle rotation with left mouse dragging
+ if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
+ ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y);
+
+ // Map delta to rotation angles (in radians)
+ float angle_x = delta.x * (3.1415f / 180.0f);
+ float angle_y = delta.y * (3.1415f / 180.0f);
+
+ // Create quaternions for the rotations
+ ImPlot3DQuat quat_x(angle_y, ImPlot3DPoint(1.0f, 0.0f, 0.0f));
+ ImPlot3DQuat quat_z(angle_x, ImPlot3DPoint(0.0f, 0.0f, 1.0f));
+
+ // Combine the new rotations with the current rotation
+ plot.Rotation = quat_x * plot.Rotation * quat_z;
+ plot.Rotation.Normalize();
+ }
+
+ // Handle zoom with mouse wheel
+ if (plot.Hovered && (ImGui::IsMouseDown(ImGuiMouseButton_Middle) || IO.MouseWheel != 0)) {
+ float delta = ImGui::IsMouseDown(ImGuiMouseButton_Middle) ? (-0.01f * IO.MouseDelta.y) : (-0.1f * IO.MouseWheel);
+ float zoom = 1.0f + delta;
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+ float center = (axis.Range.Min + axis.Range.Max) * 0.5f;
+ float size = axis.Range.Max - axis.Range.Min;
+ size *= zoom;
+ if (transform_axis[i]) {
+ plot.Axes[i].SetRange(center - size * 0.5f, center + size * 0.5f);
+ plot.Axes[i].Held = true;
+ }
+ // If no axis was held before (user started zoom in this frame), set the held edge/plane indices
+ if (!any_axis_held) {
+ plot.HeldEdgeIdx = hovered_edge_idx;
+ plot.HeldPlaneIdx = hovered_plane_idx;
+ }
+ }
+ }
+
+ // Handle context menu (should not happen if it is not a double click action)
+ bool not_double_click = (float)(ImGui::GetTime() - IO.MouseClickedTime[ImGuiMouseButton_Right]) > IO.MouseDoubleClickTime;
+ if (plot.Hovered && plot.ContextClick && not_double_click && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
+ plot.ContextClick = false;
+ plot.OpenContextThisFrame = true;
+ }
+
+ // TODO Only open context menu if the mouse is not in the middle of double click action
+ const char* axis_contexts[3] = {"##XAxisContext", "##YAxisContext", "##ZAxisContext"};
+ if (plot.OpenContextThisFrame) {
+ if (plot.Items.Legend.Hovered)
+ ImGui::OpenPopup("##LegendContext");
+ else if (hovered_axis != -1) {
+ ImGui::OpenPopup(axis_contexts[hovered_axis]);
+ } else if (hovered_plane != -1) {
+ ImGui::OpenPopup(axis_contexts[hovered_plane]);
+ } else if (plot.Hovered) {
+ ImGui::OpenPopup("##PlotContext");
+ }
+ }
+}
+
+void SetupLock() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetupLock() needs to be called between BeginPlot() and EndPlot()!");
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ if (plot.SetupLocked)
+ return;
+ // Lock setup
+ plot.SetupLocked = true;
+
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ ImDrawList* draw_list = window->DrawList;
+
+ ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
+
+ // Set default formatter/locator
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+
+ // Set formatter
+ if (axis.Formatter == nullptr) {
+ axis.Formatter = Formatter_Default;
+ if (axis.FormatterData == nullptr)
+ axis.FormatterData = (void*)IMPLOT3D_LABEL_FORMAT;
+ }
+
+ // Set locator
+ if (axis.Locator == nullptr)
+ axis.Locator = Locator_Default;
+ }
+
+ // Draw frame background
+ ImU32 f_bg_color = GetStyleColorU32(ImPlot3DCol_FrameBg);
+ draw_list->AddRectFilled(plot.FrameRect.Min, plot.FrameRect.Max, f_bg_color);
+
+ // Compute canvas/canvas rectangle
+ plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding);
+ plot.PlotRect = plot.CanvasRect;
+
+ // Compute ticks
+ for (int i = 0; i < 3; i++) {
+ ImPlot3DAxis& axis = plot.Axes[i];
+ axis.Ticker.Reset();
+ axis.Locator(axis.Ticker, axis.Range, axis.Formatter, axis.FormatterData);
+ }
+
+ // Render title
+ if (plot.HasTitle()) {
+ ImU32 col = GetStyleColorU32(ImPlot3DCol_TitleText);
+ ImVec2 top_center = ImVec2(plot.FrameRect.GetCenter().x, plot.CanvasRect.Min.y);
+ AddTextCentered(draw_list, top_center, col, plot.GetTitle());
+ plot.PlotRect.Min.y += ImGui::GetTextLineHeight() + gp.Style.LabelPadding.y;
+ }
+
+ // Handle animation
+ if (plot.AnimationTime > 0.0f) {
+ float dt = ImGui::GetIO().DeltaTime;
+ float t = ImClamp(dt / plot.AnimationTime, 0.0f, 1.0f);
+ plot.AnimationTime -= dt;
+ if (plot.AnimationTime < 0.0f)
+ plot.AnimationTime = 0.0f;
+ plot.Rotation = ImPlot3DQuat::Slerp(plot.Rotation, plot.RotationAnimationEnd, t);
+ }
+
+ plot.Initialized = true;
+
+ // Handle user input
+ HandleInput(plot);
+
+ // Render plot box
+ RenderPlotBox(draw_list, plot);
+
+ ImGui::PopClipRect();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Miscellaneous
+//-----------------------------------------------------------------------------
+
+ImDrawList* GetPlotDrawList() {
+ return ImGui::GetWindowDrawList();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Styles
+//-----------------------------------------------------------------------------
+
+struct ImPlot3DStyleVarInfo {
+ ImGuiDataType Type;
+ ImU32 Count;
+ ImU32 Offset;
+ void* GetVarPtr(ImPlot3DStyle* style) const { return (void*)((unsigned char*)style + Offset); }
+};
+
+static const ImPlot3DStyleVarInfo GPlot3DStyleVarInfo[] =
+ {
+ // Item style
+ {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LineWeight)}, // ImPlot3DStyleVar_LineWeight
+ {ImGuiDataType_S32, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, Marker)}, // ImPlot3DStyleVar_Marker
+ {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, MarkerSize)}, // ImPlot3DStyleVar_MarkerSize
+ {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, MarkerWeight)}, // ImPlot3DStyleVar_MarkerWeight
+ {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, FillAlpha)}, // ImPlot3DStyleVar_FillAlpha
+
+ // Plot style
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotDefaultSize)}, // ImPlot3DStyleVar_Plot3DDefaultSize
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotMinSize)}, // ImPlot3DStyleVar_Plot3DMinSize
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotPadding)}, // ImPlot3DStyleVar_Plot3DPadding
+
+ // Label style
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LabelPadding)}, // ImPlot3DStyleVar_LabelPaddine
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendPadding)}, // ImPlot3DStyleVar_LegendPadding
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendInnerPadding)}, // ImPlot3DStyleVar_LegendInnerPadding
+ {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendSpacing)}, // ImPlot3DStyleVar_LegendSpacing
+};
+
+static const ImPlot3DStyleVarInfo* GetPlotStyleVarInfo(ImPlot3DStyleVar idx) {
+ IM_ASSERT(idx >= 0 && idx < ImPlot3DStyleVar_COUNT);
+ IM_ASSERT(IM_ARRAYSIZE(GPlot3DStyleVarInfo) == ImPlot3DStyleVar_COUNT);
+ return &GPlot3DStyleVarInfo[idx];
+}
+
+ImPlot3DStyle& GetStyle() { return GImPlot3D->Style; }
+
+void StyleColorsAuto(ImPlot3DStyle* dst) {
+ ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
+ ImVec4* colors = style->Colors;
+
+ colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_TitleText] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_InlayText] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_FrameBg] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_PlotBg] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_PlotBorder] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_LegendBg] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_LegendBorder] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_LegendText] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_AxisText] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_AxisGrid] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
+}
+
+void StyleColorsDark(ImPlot3DStyle* dst) {
+ ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
+ ImVec4* colors = style->Colors;
+
+ colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_TitleText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_InlayText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
+ colors[ImPlot3DCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
+ colors[ImPlot3DCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
+ colors[ImPlot3DCol_LegendBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
+ colors[ImPlot3DCol_LegendBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
+ colors[ImPlot3DCol_LegendText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_AxisText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);
+ colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
+}
+
+void StyleColorsLight(ImPlot3DStyle* dst) {
+ ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
+ ImVec4* colors = style->Colors;
+
+ colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f);
+ colors[ImPlot3DCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+ colors[ImPlot3DCol_LegendBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);
+ colors[ImPlot3DCol_LegendBorder] = ImVec4(0.82f, 0.82f, 0.82f, 0.80f);
+ colors[ImPlot3DCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
+}
+
+void StyleColorsClassic(ImPlot3DStyle* dst) {
+ ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
+ ImVec4* colors = style->Colors;
+
+ colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_TitleText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ colors[ImPlot3DCol_InlayText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ colors[ImPlot3DCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
+ colors[ImPlot3DCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f);
+ colors[ImPlot3DCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
+ colors[ImPlot3DCol_LegendBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
+ colors[ImPlot3DCol_LegendBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
+ colors[ImPlot3DCol_LegendText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ colors[ImPlot3DCol_AxisText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ colors[ImPlot3DCol_AxisGrid] = ImVec4(0.90f, 0.90f, 0.90f, 0.25f);
+ colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
+}
+
+void PushStyleColor(ImPlot3DCol idx, ImU32 col) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImGuiColorMod backup;
+ backup.Col = (ImGuiCol)idx;
+ backup.BackupValue = gp.Style.Colors[idx];
+ gp.ColorModifiers.push_back(backup);
+ gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col);
+}
+
+void PushStyleColor(ImPlot3DCol idx, const ImVec4& col) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImGuiColorMod backup;
+ backup.Col = (ImGuiCol)idx;
+ backup.BackupValue = gp.Style.Colors[idx];
+ gp.ColorModifiers.push_back(backup);
+ gp.Style.Colors[idx] = col;
+}
+
+void PopStyleColor(int count) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!");
+ while (count > 0) {
+ ImGuiColorMod& backup = gp.ColorModifiers.back();
+ gp.Style.Colors[backup.Col] = backup.BackupValue;
+ gp.ColorModifiers.pop_back();
+ count--;
+ }
+}
+
+void PushStyleVar(ImPlot3DStyleVar idx, float val) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
+ if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
+ float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
+ gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
+ *pvar = val;
+ return;
+ }
+ IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
+}
+
+void PushStyleVar(ImPlot3DStyleVar idx, int val) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
+ if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) {
+ int* pvar = (int*)var_info->GetVarPtr(&gp.Style);
+ gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
+ *pvar = val;
+ return;
+ } else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
+ float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
+ gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
+ *pvar = (float)val;
+ return;
+ }
+ IM_ASSERT(0 && "Called PushStyleVar() int variant but variable is not a int!");
+}
+
+void PushStyleVar(ImPlot3DStyleVar idx, const ImVec2& val) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
+ if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) {
+ ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style);
+ gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
+ *pvar = val;
+ return;
+ }
+ IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
+}
+
+void PopStyleVar(int count) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!");
+ while (count > 0) {
+ ImGuiStyleMod& backup = gp.StyleModifiers.back();
+ const ImPlot3DStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx);
+ void* data = info->GetVarPtr(&gp.Style);
+ if (info->Type == ImGuiDataType_Float && info->Count == 1) {
+ ((float*)data)[0] = backup.BackupFloat[0];
+ } else if (info->Type == ImGuiDataType_Float && info->Count == 2) {
+ ((float*)data)[0] = backup.BackupFloat[0];
+ ((float*)data)[1] = backup.BackupFloat[1];
+ } else if (info->Type == ImGuiDataType_S32 && info->Count == 1) {
+ ((int*)data)[0] = backup.BackupInt[0];
+ }
+ gp.StyleModifiers.pop_back();
+ count--;
+ }
+}
+
+ImVec4 GetStyleColorVec4(ImPlot3DCol idx) {
+ return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot3D->Style.Colors[idx];
+}
+
+ImU32 GetStyleColorU32(ImPlot3DCol idx) {
+ return ImGui::ColorConvertFloat4ToU32(ImPlot3D::GetStyleColorVec4(idx));
+}
+
+//------------------------------------------------------------------------------
+// [SECTION] Colormaps
+//------------------------------------------------------------------------------
+
+ImPlot3DColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
+ IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!");
+ ImVector buffer;
+ buffer.resize(size);
+ for (int i = 0; i < size; ++i)
+ buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]);
+ return gp.ColormapData.Append(name, buffer.Data, size, qual);
+}
+
+ImPlot3DColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
+ IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!");
+ return gp.ColormapData.Append(name, colormap, size, qual);
+}
+
+int GetColormapCount() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ return gp.ColormapData.Count;
+}
+
+const char* GetColormapName(ImPlot3DColormap colormap) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ return gp.ColormapData.GetName(colormap);
+}
+
+ImPlot3DColormap GetColormapIndex(const char* name) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ return gp.ColormapData.GetIndex(name);
+}
+
+void PushColormap(ImPlot3DColormap colormap) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!");
+ gp.ColormapModifiers.push_back(gp.Style.Colormap);
+ gp.Style.Colormap = colormap;
+}
+
+void PushColormap(const char* name) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DColormap idx = gp.ColormapData.GetIndex(name);
+ IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!");
+ PushColormap(idx);
+}
+
+void PopColormap(int count) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!");
+ while (count > 0) {
+ const ImPlot3DColormap& backup = gp.ColormapModifiers.back();
+ gp.Style.Colormap = backup;
+ gp.ColormapModifiers.pop_back();
+ count--;
+ }
+}
+
+ImU32 NextColormapColorU32() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!");
+ int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap);
+ ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx);
+ gp.CurrentItems->ColormapIdx++;
+ return col;
+}
+
+ImVec4 NextColormapColor() {
+ return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32());
+}
+
+int GetColormapSize(ImPlot3DColormap cmap) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
+ IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
+ return gp.ColormapData.GetKeyCount(cmap);
+}
+
+ImU32 GetColormapColorU32(int idx, ImPlot3DColormap cmap) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
+ IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
+ idx = idx % gp.ColormapData.GetKeyCount(cmap);
+ return gp.ColormapData.GetKeyColor(cmap, idx);
+}
+
+ImVec4 GetColormapColor(int idx, ImPlot3DColormap cmap) {
+ return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx, cmap));
+}
+
+ImU32 SampleColormapU32(float t, ImPlot3DColormap cmap) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
+ IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
+ return gp.ColormapData.LerpTable(cmap, t);
+}
+
+ImVec4 SampleColormap(float t, ImPlot3DColormap cmap) {
+ return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t, cmap));
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Context Utils
+//-----------------------------------------------------------------------------
+
+#define IMPLOT3D_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name) / sizeof(ImU32), qual)
+#define IM_RGB(r, g, b) IM_COL32(r, g, b, 255)
+
+void InitializeContext(ImPlot3DContext* ctx) {
+ ResetContext(ctx);
+
+ const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396};
+ const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409};
+ const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986};
+ const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481};
+ const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301};
+ const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752};
+ const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295};
+ const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015};
+ const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295};
+ const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335};
+ const ImU32 Twilight[] = {IM_RGB(226, 217, 226), IM_RGB(166, 191, 202), IM_RGB(109, 144, 192), IM_RGB(95, 88, 176), IM_RGB(83, 30, 124), IM_RGB(47, 20, 54), IM_RGB(100, 25, 75), IM_RGB(159, 60, 80), IM_RGB(192, 117, 94), IM_RGB(208, 179, 158), IM_RGB(226, 217, 226)};
+ const ImU32 RdBu[] = {IM_RGB(103, 0, 31), IM_RGB(178, 24, 43), IM_RGB(214, 96, 77), IM_RGB(244, 165, 130), IM_RGB(253, 219, 199), IM_RGB(247, 247, 247), IM_RGB(209, 229, 240), IM_RGB(146, 197, 222), IM_RGB(67, 147, 195), IM_RGB(33, 102, 172), IM_RGB(5, 48, 97)};
+ const ImU32 BrBG[] = {IM_RGB(84, 48, 5), IM_RGB(140, 81, 10), IM_RGB(191, 129, 45), IM_RGB(223, 194, 125), IM_RGB(246, 232, 195), IM_RGB(245, 245, 245), IM_RGB(199, 234, 229), IM_RGB(128, 205, 193), IM_RGB(53, 151, 143), IM_RGB(1, 102, 94), IM_RGB(0, 60, 48)};
+ const ImU32 PiYG[] = {IM_RGB(142, 1, 82), IM_RGB(197, 27, 125), IM_RGB(222, 119, 174), IM_RGB(241, 182, 218), IM_RGB(253, 224, 239), IM_RGB(247, 247, 247), IM_RGB(230, 245, 208), IM_RGB(184, 225, 134), IM_RGB(127, 188, 65), IM_RGB(77, 146, 33), IM_RGB(39, 100, 25)};
+ const ImU32 Spectral[] = {IM_RGB(158, 1, 66), IM_RGB(213, 62, 79), IM_RGB(244, 109, 67), IM_RGB(253, 174, 97), IM_RGB(254, 224, 139), IM_RGB(255, 255, 191), IM_RGB(230, 245, 152), IM_RGB(171, 221, 164), IM_RGB(102, 194, 165), IM_RGB(50, 136, 189), IM_RGB(94, 79, 162)};
+ const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK};
+
+ IMPLOT3D_APPEND_CMAP(Deep, true);
+ IMPLOT3D_APPEND_CMAP(Dark, true);
+ IMPLOT3D_APPEND_CMAP(Pastel, true);
+ IMPLOT3D_APPEND_CMAP(Paired, true);
+ IMPLOT3D_APPEND_CMAP(Viridis, false);
+ IMPLOT3D_APPEND_CMAP(Plasma, false);
+ IMPLOT3D_APPEND_CMAP(Hot, false);
+ IMPLOT3D_APPEND_CMAP(Cool, false);
+ IMPLOT3D_APPEND_CMAP(Pink, false);
+ IMPLOT3D_APPEND_CMAP(Jet, false);
+ IMPLOT3D_APPEND_CMAP(Twilight, false);
+ IMPLOT3D_APPEND_CMAP(RdBu, false);
+ IMPLOT3D_APPEND_CMAP(BrBG, false);
+ IMPLOT3D_APPEND_CMAP(PiYG, false);
+ IMPLOT3D_APPEND_CMAP(Spectral, false);
+ IMPLOT3D_APPEND_CMAP(Greys, false);
+}
+
+void ResetContext(ImPlot3DContext* ctx) {
+ ctx->Plots.Clear();
+ ctx->CurrentPlot = nullptr;
+ ctx->CurrentItems = nullptr;
+ ctx->NextItemData.Reset();
+ ctx->Style = ImPlot3DStyle();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Style Utils
+//-----------------------------------------------------------------------------
+
+bool IsColorAuto(const ImVec4& col) {
+ return col.w == -1.0f;
+}
+
+bool IsColorAuto(ImPlot3DCol idx) {
+ return IsColorAuto(GImPlot3D->Style.Colors[idx]);
+}
+
+ImVec4 GetAutoColor(ImPlot3DCol idx) {
+ switch (idx) {
+ case ImPlot3DCol_Line: return IMPLOT3D_AUTO_COL; // Plot dependent
+ case ImPlot3DCol_Fill: return IMPLOT3D_AUTO_COL; // Plot dependent
+ case ImPlot3DCol_MarkerOutline: return IMPLOT3D_AUTO_COL; // Plot dependent
+ case ImPlot3DCol_MarkerFill: return IMPLOT3D_AUTO_COL; // Plot dependent
+ case ImPlot3DCol_TitleText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ case ImPlot3DCol_InlayText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ case ImPlot3DCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
+ case ImPlot3DCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
+ case ImPlot3DCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border);
+ case ImPlot3DCol_LegendBg: return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg);
+ case ImPlot3DCol_LegendBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border);
+ case ImPlot3DCol_LegendText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ case ImPlot3DCol_AxisText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ case ImPlot3DCol_AxisGrid: return ImGui::GetStyleColorVec4(ImGuiCol_Text) * ImVec4(1, 1, 1, 0.25f);
+ case ImPlot3DCol_AxisTick: return GetStyleColorVec4(ImPlot3DCol_AxisGrid);
+ default: return IMPLOT3D_AUTO_COL;
+ }
+}
+
+const char* GetStyleColorName(ImPlot3DCol idx) {
+ static const char* color_names[ImPlot3DCol_COUNT] = {
+ "Line",
+ "Fill",
+ "MarkerOutline",
+ "MarkerFill",
+ "TitleText",
+ "InlayText",
+ "FrameBg",
+ "PlotBg",
+ "PlotBorder",
+ "LegendBg",
+ "LegendBorder",
+ "LegendText",
+ "AxisText",
+ "AxisGrid",
+ "AxisTick",
+ };
+ return color_names[idx];
+}
+
+const ImPlot3DNextItemData& GetItemData() { return GImPlot3D->NextItemData; }
+
+} // namespace ImPlot3D
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DPoint
+//-----------------------------------------------------------------------------
+
+ImPlot3DPoint ImPlot3DPoint::operator*(float rhs) const { return ImPlot3DPoint(x * rhs, y * rhs, z * rhs); }
+ImPlot3DPoint ImPlot3DPoint::operator/(float rhs) const { return ImPlot3DPoint(x / rhs, y / rhs, z / rhs); }
+ImPlot3DPoint ImPlot3DPoint::operator+(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x + rhs.x, y + rhs.y, z + rhs.z); }
+ImPlot3DPoint ImPlot3DPoint::operator-(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x - rhs.x, y - rhs.y, z - rhs.z); }
+ImPlot3DPoint ImPlot3DPoint::operator*(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x * rhs.x, y * rhs.y, z * rhs.z); }
+ImPlot3DPoint ImPlot3DPoint::operator/(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x / rhs.x, y / rhs.y, z / rhs.z); }
+ImPlot3DPoint ImPlot3DPoint::operator-() const { return ImPlot3DPoint(-x, -y, -z); }
+
+ImPlot3DPoint& ImPlot3DPoint::operator*=(float rhs) {
+ x *= rhs;
+ y *= rhs;
+ z *= rhs;
+ return *this;
+}
+ImPlot3DPoint& ImPlot3DPoint::operator/=(float rhs) {
+ x /= rhs;
+ y /= rhs;
+ z /= rhs;
+ return *this;
+}
+ImPlot3DPoint& ImPlot3DPoint::operator+=(const ImPlot3DPoint& rhs) {
+ x += rhs.x;
+ y += rhs.y;
+ z += rhs.z;
+ return *this;
+}
+ImPlot3DPoint& ImPlot3DPoint::operator-=(const ImPlot3DPoint& rhs) {
+ x -= rhs.x;
+ y -= rhs.y;
+ z -= rhs.z;
+ return *this;
+}
+ImPlot3DPoint& ImPlot3DPoint::operator*=(const ImPlot3DPoint& rhs) {
+ x *= rhs.x;
+ y *= rhs.y;
+ z *= rhs.z;
+ return *this;
+}
+ImPlot3DPoint& ImPlot3DPoint::operator/=(const ImPlot3DPoint& rhs) {
+ x /= rhs.x;
+ y /= rhs.y;
+ z /= rhs.z;
+ return *this;
+}
+
+bool ImPlot3DPoint::operator==(const ImPlot3DPoint& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
+bool ImPlot3DPoint::operator!=(const ImPlot3DPoint& rhs) const { return !(*this == rhs); }
+
+float ImPlot3DPoint::Dot(const ImPlot3DPoint& rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; }
+
+ImPlot3DPoint ImPlot3DPoint::Cross(const ImPlot3DPoint& rhs) const {
+ return ImPlot3DPoint(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
+}
+
+float ImPlot3DPoint::Length() const { return ImSqrt(x * x + y * y + z * z); }
+
+float ImPlot3DPoint::LengthSquared() const { return x * x + y * y + z * z; }
+
+void ImPlot3DPoint::Normalize() {
+ float l = Length();
+ x /= l;
+ y /= l;
+ z /= l;
+}
+
+ImPlot3DPoint ImPlot3DPoint::Normalized() const {
+ float l = Length();
+ return ImPlot3DPoint(x / l, y / l, z / l);
+}
+
+ImPlot3DPoint operator*(float lhs, const ImPlot3DPoint& rhs) {
+ return ImPlot3DPoint(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
+}
+
+bool ImPlot3DPoint::IsNaN() const {
+ return ImPlot3D::ImNan(x) || ImPlot3D::ImNan(y) || ImPlot3D::ImNan(z);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DBox
+//-----------------------------------------------------------------------------
+
+void ImPlot3DBox::Expand(const ImPlot3DPoint& point) {
+ Min.x = ImMin(Min.x, point.x);
+ Min.y = ImMin(Min.y, point.y);
+ Min.z = ImMin(Min.z, point.z);
+ Max.x = ImMax(Max.x, point.x);
+ Max.y = ImMax(Max.y, point.y);
+ Max.z = ImMax(Max.z, point.z);
+}
+
+bool ImPlot3DBox::Contains(const ImPlot3DPoint& point) const {
+ return (point.x >= Min.x && point.x <= Max.x) &&
+ (point.y >= Min.y && point.y <= Max.y) &&
+ (point.z >= Min.z && point.z <= Max.z);
+}
+
+bool ImPlot3DBox::ClipLineSegment(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, ImPlot3DPoint& p0_clipped, ImPlot3DPoint& p1_clipped) const {
+ // Check if the line segment is completely inside the box
+ if (Contains(p0) && Contains(p1)) {
+ p0_clipped = p0;
+ p1_clipped = p1;
+ return true;
+ }
+
+ // Perform Liang-Barsky 3D clipping
+ double t0 = 0.0;
+ double t1 = 1.0;
+ ImPlot3DPoint d = p1 - p0;
+
+ // Define the clipping boundaries
+ const double xmin = Min.x, xmax = Max.x;
+ const double ymin = Min.y, ymax = Max.y;
+ const double zmin = Min.z, zmax = Max.z;
+
+ // Lambda function to update t0 and t1
+ auto update = [&](double p, double q) -> bool {
+ if (p == 0.0) {
+ if (q < 0.0)
+ return false; // Line is parallel and outside the boundary
+ else
+ return true; // Line is parallel and inside or coincident with boundary
+ }
+ double r = q / p;
+ if (p < 0.0) {
+ if (r > t1)
+ return false; // Line is outside
+ if (r > t0)
+ t0 = r; // Move up t0
+ } else {
+ if (r < t0)
+ return false; // Line is outside
+ if (r < t1)
+ t1 = r; // Move down t1
+ }
+ return true;
+ };
+
+ // Clip against each boundary
+ if (!update(-d.x, p0.x - xmin))
+ return false; // Left
+ if (!update(d.x, xmax - p0.x))
+ return false; // Right
+ if (!update(-d.y, p0.y - ymin))
+ return false; // Bottom
+ if (!update(d.y, ymax - p0.y))
+ return false; // Top
+ if (!update(-d.z, p0.z - zmin))
+ return false; // Near
+ if (!update(d.z, zmax - p0.z))
+ return false; // Far
+
+ // Compute clipped points
+ p0_clipped = p0 + d * t0;
+ p1_clipped = p0 + d * t1;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DRange
+//-----------------------------------------------------------------------------
+
+void ImPlot3DRange::Expand(float value) {
+ Min = ImMin(Min, value);
+ Max = ImMax(Max, value);
+}
+
+bool ImPlot3DRange::Contains(float value) const {
+ return value >= Min && value <= Max;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DQuat
+//-----------------------------------------------------------------------------
+
+ImPlot3DQuat::ImPlot3DQuat(float _angle, const ImPlot3DPoint& _axis) {
+ float half_angle = _angle * 0.5f;
+ float s = std::sin(half_angle);
+ x = s * _axis.x;
+ y = s * _axis.y;
+ z = s * _axis.z;
+ w = std::cos(half_angle);
+}
+
+ImPlot3DQuat ImPlot3DQuat::FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3DPoint& v1) {
+ ImPlot3DQuat q;
+
+ // Compute the dot product and lengths of the vectors
+ float dot = v0.Dot(v1);
+ float length_v0 = v0.Length();
+ float length_v1 = v1.Length();
+
+ // Normalize the dot product
+ float normalized_dot = dot / (length_v0 * length_v1);
+
+ // Handle edge cases: if vectors are very close or identical
+ const float epsilon = 1e-6f;
+ if (std::fabs(normalized_dot - 1.0f) < epsilon) {
+ // v0 and v1 are nearly identical; return an identity quaternion
+ q.x = 0.0f;
+ q.y = 0.0f;
+ q.z = 0.0f;
+ q.w = 1.0f;
+ return q;
+ }
+
+ // Handle edge case: if vectors are opposite
+ if (std::fabs(normalized_dot + 1.0f) < epsilon) {
+ // v0 and v1 are opposite; choose an arbitrary orthogonal axis
+ ImPlot3DPoint arbitrary_axis = std::fabs(v0.x) > std::fabs(v0.z) ? ImPlot3DPoint(-v0.y, v0.x, 0.0f)
+ : ImPlot3DPoint(0.0f, -v0.z, v0.y);
+ arbitrary_axis.Normalize();
+ q.x = arbitrary_axis.x;
+ q.y = arbitrary_axis.y;
+ q.z = arbitrary_axis.z;
+ q.w = 0.0f;
+ return q;
+ }
+
+ // General case
+ ImPlot3DPoint axis = v0.Cross(v1);
+ axis.Normalize();
+ float angle = std::acos(normalized_dot);
+ float half_angle = angle * 0.5f;
+ float s = std::sin(half_angle);
+ q.x = s * axis.x;
+ q.y = s * axis.y;
+ q.z = s * axis.z;
+ q.w = std::cos(half_angle);
+
+ return q;
+}
+
+float ImPlot3DQuat::Length() const {
+ return std::sqrt(x * x + y * y + z * z + w * w);
+}
+
+ImPlot3DQuat ImPlot3DQuat::Normalized() const {
+ float l = Length();
+ return ImPlot3DQuat(x / l, y / l, z / l, w / l);
+}
+
+ImPlot3DQuat ImPlot3DQuat::Conjugate() const {
+ return ImPlot3DQuat(-x, -y, -z, w);
+}
+
+ImPlot3DQuat ImPlot3DQuat::Inverse() const {
+ float l_squared = x * x + y * y + z * z + w * w;
+ return ImPlot3DQuat(-x / l_squared, -y / l_squared, -z / l_squared, w / l_squared);
+}
+
+ImPlot3DQuat ImPlot3DQuat::operator*(const ImPlot3DQuat& rhs) const {
+ return ImPlot3DQuat(
+ w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y,
+ w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x,
+ w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w,
+ w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z);
+}
+
+ImPlot3DQuat& ImPlot3DQuat::Normalize() {
+ float l = Length();
+ x /= l;
+ y /= l;
+ z /= l;
+ w /= l;
+ return *this;
+}
+
+ImPlot3DPoint ImPlot3DQuat::operator*(const ImPlot3DPoint& point) const {
+ // Extract vector part of the quaternion
+ ImPlot3DPoint qv(x, y, z);
+
+ // Compute the cross products needed for rotation
+ ImPlot3DPoint uv = qv.Cross(point); // uv = qv x point
+ ImPlot3DPoint uuv = qv.Cross(uv); // uuv = qv x uv
+
+ // Compute the rotated vector
+ return point + (uv * w * 2.0f) + (uuv * 2.0f);
+}
+
+bool ImPlot3DQuat::operator==(const ImPlot3DQuat& rhs) const {
+ return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w;
+}
+
+bool ImPlot3DQuat::operator!=(const ImPlot3DQuat& rhs) const {
+ return !(*this == rhs);
+}
+
+ImPlot3DQuat ImPlot3DQuat::Slerp(const ImPlot3DQuat& q1, const ImPlot3DQuat& q2, float t) {
+ // Clamp t to [0, 1]
+ t = ImClamp(t, 0.0f, 1.0f);
+
+ // Compute the dot product (cosine of the angle between quaternions)
+ float dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
+
+ // If the dot product is negative, negate one quaternion to take the shorter path
+ ImPlot3DQuat q2_ = q2;
+ if (dot < 0.0f) {
+ q2_ = ImPlot3DQuat(-q2.x, -q2.y, -q2.z, -q2.w);
+ dot = -dot;
+ }
+
+ // If the quaternions are very close, use linear interpolation to avoid numerical instability
+ if (dot > 0.9995f) {
+ return ImPlot3DQuat(
+ q1.x + t * (q2_.x - q1.x),
+ q1.y + t * (q2_.y - q1.y),
+ q1.z + t * (q2_.z - q1.z),
+ q1.w + t * (q2_.w - q1.w))
+ .Normalized();
+ }
+
+ // Compute the angle and the interpolation factors
+ float theta_0 = std::acos(dot); // Angle between input quaternions
+ float theta = theta_0 * t; // Interpolated angle
+ float sin_theta = std::sin(theta); // Sine of interpolated angle
+ float sin_theta_0 = std::sin(theta_0); // Sine of original angle
+
+ float s1 = std::cos(theta) - dot * sin_theta / sin_theta_0;
+ float s2 = sin_theta / sin_theta_0;
+
+ // Interpolate and return the result
+ return ImPlot3DQuat(
+ s1 * q1.x + s2 * q2_.x,
+ s1 * q1.y + s2 * q2_.y,
+ s1 * q1.z + s2 * q2_.z,
+ s1 * q1.w + s2 * q2_.w);
+}
+
+float ImPlot3DQuat::Dot(const ImPlot3DQuat& rhs) const {
+ return x * rhs.x + y * rhs.y + z * rhs.z + w * rhs.w;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImDrawList3D
+//-----------------------------------------------------------------------------
+
+void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) {
+ IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
+
+ int vtx_buffer_old_size = VtxBuffer.Size;
+ VtxBuffer.resize(vtx_buffer_old_size + vtx_count);
+ _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size;
+
+ int idx_buffer_old_size = IdxBuffer.Size;
+ IdxBuffer.resize(idx_buffer_old_size + idx_count);
+ _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size;
+
+ int z_buffer_old_size = ZBuffer.Size;
+ ZBuffer.resize(z_buffer_old_size + idx_count / 3);
+ _ZWritePtr = ZBuffer.Data + z_buffer_old_size;
+}
+
+void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count) {
+ IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
+
+ VtxBuffer.shrink(VtxBuffer.Size - vtx_count);
+ IdxBuffer.shrink(IdxBuffer.Size - idx_count);
+ ZBuffer.shrink(ZBuffer.Size - idx_count / 3);
+}
+
+void ImDrawList3D::SortedMoveToImGuiDrawList() {
+ ImDrawList& draw_list = *ImGui::GetWindowDrawList();
+
+ const int tri_count = ZBuffer.Size;
+ if (tri_count == 0) {
+ // No triangles, just clear and return
+ VtxBuffer.clear();
+ IdxBuffer.clear();
+ ZBuffer.clear();
+ _VtxCurrentIdx = 0;
+ _VtxWritePtr = VtxBuffer.Data;
+ _IdxWritePtr = IdxBuffer.Data;
+ _ZWritePtr = ZBuffer.Data;
+ return;
+ }
+
+ // Build an array of (z, tri_idx)
+ struct TriRef {
+ float z;
+ int tri_idx;
+ };
+ TriRef* tris = (TriRef*)IM_ALLOC(sizeof(TriRef) * tri_count);
+ for (int i = 0; i < tri_count; i++) {
+ tris[i].z = ZBuffer[i];
+ tris[i].tri_idx = i;
+ }
+
+ // Sort by z (distance from viewer)
+ ImQsort(tris, (size_t)tri_count, sizeof(TriRef),
+ [](const void* a, const void* b) {
+ float za = ((const TriRef*)a)->z;
+ float zb = ((const TriRef*)b)->z;
+ return (za < zb) ? -1 : (za > zb) ? 1
+ : 0;
+ });
+
+ // Reserve space in the ImGui draw list
+ draw_list.PrimReserve(IdxBuffer.Size, VtxBuffer.Size);
+
+ // Copy vertices (no reordering needed)
+ memcpy(draw_list._VtxWritePtr, VtxBuffer.Data, VtxBuffer.Size * sizeof(ImDrawVert));
+ unsigned int idx_offset = draw_list._VtxCurrentIdx;
+ draw_list._VtxWritePtr += VtxBuffer.Size;
+ draw_list._VtxCurrentIdx += (unsigned int)VtxBuffer.Size;
+
+ // Maximum index allowed to not overflow ImDrawIdx
+ unsigned int max_index_allowed = MaxIdx() - idx_offset;
+
+ // Copy indices with triangle sorting based on distance from viewer
+ ImDrawIdx* idx_out = draw_list._IdxWritePtr;
+ ImDrawIdx* idx_in = IdxBuffer.Data;
+ int triangles_added = 0;
+ for (int i = 0; i < tri_count; i++) {
+ int tri_i = tris[i].tri_idx;
+ int base_idx = tri_i * 3;
+ unsigned int i0 = (unsigned int)idx_in[base_idx + 0];
+ unsigned int i1 = (unsigned int)idx_in[base_idx + 1];
+ unsigned int i2 = (unsigned int)idx_in[base_idx + 2];
+
+ // Check if after adding offset any of these indices exceed max_index_allowed
+ if (i0 > max_index_allowed || i1 > max_index_allowed || i2 > max_index_allowed)
+ break;
+
+ idx_out[0] = (ImDrawIdx)(i0 + idx_offset);
+ idx_out[1] = (ImDrawIdx)(i1 + idx_offset);
+ idx_out[2] = (ImDrawIdx)(i2 + idx_offset);
+
+ idx_out += 3;
+ triangles_added++;
+ }
+ draw_list._IdxWritePtr = idx_out;
+
+ // Clear local buffers since we've moved them
+ VtxBuffer.clear();
+ IdxBuffer.clear();
+ ZBuffer.clear();
+ _VtxCurrentIdx = 0;
+ _VtxWritePtr = VtxBuffer.Data;
+ _IdxWritePtr = IdxBuffer.Data;
+ _ZWritePtr = ZBuffer.Data;
+
+ IM_FREE(tris);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DAxis
+//-----------------------------------------------------------------------------
+
+bool ImPlot3DAxis::HasLabel() const { return !Label.empty() && !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoLabel); }
+bool ImPlot3DAxis::HasGridLines() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoGridLines); }
+bool ImPlot3DAxis::HasTickLabels() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoTickLabels); }
+bool ImPlot3DAxis::HasTickMarks() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoTickMarks); }
+bool ImPlot3DAxis::IsAutoFitting() const { return ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_AutoFit); }
+
+void ImPlot3DAxis::ExtendFit(float value) {
+ FitExtents.Min = ImMin(FitExtents.Min, value);
+ FitExtents.Max = ImMax(FitExtents.Max, value);
+}
+
+void ImPlot3DAxis::ApplyFit() {
+ if (!IsLockedMin() && !ImPlot3D::ImNanOrInf(FitExtents.Min))
+ Range.Min = FitExtents.Min;
+ if (!IsLockedMax() && !ImPlot3D::ImNanOrInf(FitExtents.Max))
+ Range.Max = FitExtents.Max;
+ if (ImPlot3D::ImAlmostEqual(Range.Min, Range.Max)) {
+ Range.Max += 0.5;
+ Range.Min -= 0.5;
+ }
+ FitExtents.Min = HUGE_VAL;
+ FitExtents.Max = -HUGE_VAL;
+}
+
+float ImPlot3DAxis::PlotToNDC(float value) const {
+ return (value - Range.Min) / (Range.Max - Range.Min) - 0.5f;
+}
+
+float ImPlot3DAxis::NDCToPlot(float value) const {
+ return Range.Min + (value + 0.5f) * (Range.Max - Range.Min);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DPlot
+//-----------------------------------------------------------------------------
+
+void ImPlot3DPlot::ExtendFit(const ImPlot3DPoint& point) {
+ for (int i = 0; i < 3; i++) {
+ if (!ImPlot3D::ImNanOrInf(point[i]) && Axes[i].FitThisFrame)
+ Axes[i].ExtendFit(point[i]);
+ }
+}
+
+ImPlot3DPoint ImPlot3DPlot::RangeMin() const {
+ return ImPlot3DPoint(Axes[0].Range.Min, Axes[1].Range.Min, Axes[2].Range.Min);
+}
+
+ImPlot3DPoint ImPlot3DPlot::RangeMax() const {
+ return ImPlot3DPoint(Axes[0].Range.Max, Axes[1].Range.Max, Axes[2].Range.Max);
+}
+
+ImPlot3DPoint ImPlot3DPlot::RangeCenter() const {
+ return ImPlot3DPoint(
+ (Axes[0].Range.Min + Axes[0].Range.Max) * 0.5f,
+ (Axes[1].Range.Min + Axes[1].Range.Max) * 0.5f,
+ (Axes[2].Range.Min + Axes[2].Range.Max) * 0.5f);
+}
+
+void ImPlot3DPlot::SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max) {
+ Axes[0].SetRange(min.x, max.x);
+ Axes[1].SetRange(min.y, max.y);
+ Axes[2].SetRange(min.z, max.z);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] ImPlot3DStyle
+//-----------------------------------------------------------------------------
+
+ImPlot3DStyle::ImPlot3DStyle() {
+ // Item style
+ LineWeight = 1.0f;
+ Marker = ImPlot3DMarker_None;
+ MarkerSize = 4.0f;
+ MarkerWeight = 1.0f;
+ FillAlpha = 1.0f;
+ // Plot style
+ PlotDefaultSize = ImVec2(400, 400);
+ PlotMinSize = ImVec2(200, 200);
+ PlotPadding = ImVec2(10, 10);
+ LabelPadding = ImVec2(5, 5);
+ // Legend style
+ LegendPadding = ImVec2(10, 10);
+ LegendInnerPadding = ImVec2(5, 5);
+ LegendSpacing = ImVec2(5, 0);
+ // Colors
+ ImPlot3D::StyleColorsAuto(this);
+ Colormap = ImPlot3DColormap_Deep;
+};
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/lib/third_party/imgui/implot3d/source/implot3d_demo.cpp b/lib/third_party/imgui/implot3d/source/implot3d_demo.cpp
new file mode 100644
index 000000000..c06721e5b
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/source/implot3d_demo.cpp
@@ -0,0 +1,1074 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d_demo.cpp
+// Date: 2024-11-17
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] User Namespace
+// [SECTION] Helpers
+// [SECTION] Plots
+// [SECTION] Custom
+// [SECTION] Demo Window
+// [SECTION] Style Editor
+// [SECTION] User Namespace Implementation
+
+#include "implot3d.h"
+#include "implot3d_internal.h"
+
+//-----------------------------------------------------------------------------
+// [SECTION] User Namespace
+//-----------------------------------------------------------------------------
+
+// Encapsulates examples for customizing ImPlot3D
+namespace MyImPlot3D {
+
+// Example for Custom Styles section
+void StyleSeaborn();
+
+} // namespace MyImPlot3D
+
+namespace ImPlot3D {
+
+//-----------------------------------------------------------------------------
+// [SECTION] Helpers
+//-----------------------------------------------------------------------------
+
+static void HelpMarker(const char* desc) {
+ ImGui::TextDisabled("(?)");
+ if (ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
+ ImGui::TextUnformatted(desc);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+}
+
+// Utility structure for realtime plot
+struct ScrollingBuffer {
+ int MaxSize;
+ int Offset;
+ ImVector Data;
+ ScrollingBuffer(int max_size = 2000) {
+ MaxSize = max_size;
+ Offset = 0;
+ Data.reserve(MaxSize);
+ }
+ void AddPoint(float x) {
+ if (Data.size() < MaxSize)
+ Data.push_back(x);
+ else {
+ Data[Offset] = x;
+ Offset = (Offset + 1) % MaxSize;
+ }
+ }
+ void Erase() {
+ if (Data.size() > 0) {
+ Data.shrink(0);
+ Offset = 0;
+ }
+ }
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Plots
+//-----------------------------------------------------------------------------
+
+void DemoLinePlots() {
+ static float xs1[1001], ys1[1001], zs1[1001];
+ for (int i = 0; i < 1001; i++) {
+ xs1[i] = i * 0.001f;
+ ys1[i] = 0.5f + 0.5f * cosf(50 * (xs1[i] + (float)ImGui::GetTime() / 10));
+ zs1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10));
+ }
+ static double xs2[20], ys2[20], zs2[20];
+ for (int i = 0; i < 20; i++) {
+ xs2[i] = i * 1 / 19.0f;
+ ys2[i] = xs2[i] * xs2[i];
+ zs2[i] = xs2[i] * ys2[i];
+ }
+ if (ImPlot3D::BeginPlot("Line Plots")) {
+ ImPlot3D::SetupAxes("x", "y", "z");
+ ImPlot3D::PlotLine("f(x)", xs1, ys1, zs1, 1001);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle);
+ ImPlot3D::PlotLine("g(x)", xs2, ys2, zs2, 20, ImPlot3DLineFlags_Segments);
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoScatterPlots() {
+ srand(0);
+ static float xs1[100], ys1[100], zs1[100];
+ for (int i = 0; i < 100; i++) {
+ xs1[i] = i * 0.01f;
+ ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX);
+ zs1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX);
+ }
+ static float xs2[50], ys2[50], zs2[50];
+ for (int i = 0; i < 50; i++) {
+ xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX);
+ ys2[i] = 0.50f + 0.2f * ((float)rand() / (float)RAND_MAX);
+ zs2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX);
+ }
+
+ if (ImPlot3D::BeginPlot("Scatter Plots")) {
+ ImPlot3D::PlotScatter("Data 1", xs1, ys1, zs1, 100);
+ ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.25f);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 6, ImPlot3D::GetColormapColor(1), IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(1));
+ ImPlot3D::PlotScatter("Data 2", xs2, ys2, zs1, 50);
+ ImPlot3D::PopStyleVar();
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoTrianglePlots() {
+ // Pyramid coordinates
+ // Apex
+ float ax = 0.0f, ay = 0.0f, az = 1.0f;
+ // Square base corners
+ float cx[4] = {-0.5f, 0.5f, 0.5f, -0.5f};
+ float cy[4] = {-0.5f, -0.5f, 0.5f, 0.5f};
+ float cz[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ // We have 6 triangles (18 vertices) total:
+ // Sides:
+ // T1: apex, corner0, corner1
+ // T2: apex, corner1, corner2
+ // T3: apex, corner2, corner3
+ // T4: apex, corner3, corner0
+ // Base (two triangles form a square):
+ // T5: corner0, corner1, corner2
+ // T6: corner0, corner2, corner3
+
+ static float xs[18], ys[18], zs[18];
+ int i = 0;
+
+ // Helper lambda to append a vertex
+ auto AddVertex = [&](float X, float Y, float Z) {
+ xs[i] = X;
+ ys[i] = Y;
+ zs[i] = Z;
+ i++;
+ };
+
+ // Triangle 1
+ AddVertex(ax, ay, az);
+ AddVertex(cx[0], cy[0], cz[0]);
+ AddVertex(cx[1], cy[1], cz[1]);
+
+ // Triangle 2
+ AddVertex(ax, ay, az);
+ AddVertex(cx[1], cy[1], cz[1]);
+ AddVertex(cx[2], cy[2], cz[2]);
+
+ // Triangle 3
+ AddVertex(ax, ay, az);
+ AddVertex(cx[2], cy[2], cz[2]);
+ AddVertex(cx[3], cy[3], cz[3]);
+
+ // Triangle 4
+ AddVertex(ax, ay, az);
+ AddVertex(cx[3], cy[3], cz[3]);
+ AddVertex(cx[0], cy[0], cz[0]);
+
+ // Triangle 5 (base)
+ AddVertex(cx[0], cy[0], cz[0]);
+ AddVertex(cx[1], cy[1], cz[1]);
+ AddVertex(cx[2], cy[2], cz[2]);
+
+ // Triangle 6 (base)
+ AddVertex(cx[0], cy[0], cz[0]);
+ AddVertex(cx[2], cy[2], cz[2]);
+ AddVertex(cx[3], cy[3], cz[3]);
+
+ // Now we have 18 vertices in xs, ys, zs forming the pyramid
+
+ if (ImPlot3D::BeginPlot("Triangle Plots")) {
+ ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -0.5, 1.5);
+
+ // Setup pyramid colors
+ ImPlot3D::SetNextFillStyle(ImPlot3D::GetColormapColor(0));
+ ImPlot3D::SetNextLineStyle(ImPlot3D::GetColormapColor(1), 2);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 3, ImPlot3D::GetColormapColor(2), IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(2));
+
+ // Plot pyramid
+ ImPlot3D::PlotTriangle("Pyramid", xs, ys, zs, 6 * 3); // 6 triangles, 3 vertices each = 18
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoQuadPlots() {
+ static float xs[6 * 4], ys[6 * 4], zs[6 * 4];
+
+ // clang-format off
+ // Initialize the cube vertices for +x and -x faces
+ // +x face
+ xs[0] = 1; ys[0] = -1; zs[0] = -1;
+ xs[1] = 1; ys[1] = 1; zs[1] = -1;
+ xs[2] = 1; ys[2] = 1; zs[2] = 1;
+ xs[3] = 1; ys[3] = -1; zs[3] = 1;
+
+ // -x face
+ xs[4] = -1; ys[4] = -1; zs[4] = -1;
+ xs[5] = -1; ys[5] = 1; zs[5] = -1;
+ xs[6] = -1; ys[6] = 1; zs[6] = 1;
+ xs[7] = -1; ys[7] = -1; zs[7] = 1;
+
+ // Initialize the cube vertices for +y and -y faces
+ // +y face
+ xs[8] = -1; ys[8] = 1; zs[8] = -1;
+ xs[9] = 1; ys[9] = 1; zs[9] = -1;
+ xs[10] = 1; ys[10] = 1; zs[10] = 1;
+ xs[11] = -1; ys[11] = 1; zs[11] = 1;
+
+ // -y face
+ xs[12] = -1; ys[12] = -1; zs[12] = -1;
+ xs[13] = 1; ys[13] = -1; zs[13] = -1;
+ xs[14] = 1; ys[14] = -1; zs[14] = 1;
+ xs[15] = -1; ys[15] = -1; zs[15] = 1;
+
+ // Initialize the cube vertices for +z and -z faces
+ // +z face
+ xs[16] = -1; ys[16] = -1; zs[16] = 1;
+ xs[17] = 1; ys[17] = -1; zs[17] = 1;
+ xs[18] = 1; ys[18] = 1; zs[18] = 1;
+ xs[19] = -1; ys[19] = 1; zs[19] = 1;
+
+ // -z face
+ xs[20] = -1; ys[20] = -1; zs[20] = -1;
+ xs[21] = 1; ys[21] = -1; zs[21] = -1;
+ xs[22] = 1; ys[22] = 1; zs[22] = -1;
+ xs[23] = -1; ys[23] = 1; zs[23] = -1;
+ // clang-format on
+
+ if (ImPlot3D::BeginPlot("Quad Plots")) {
+ ImPlot3D::SetupAxesLimits(-1.5f, 1.5f, -1.5f, 1.5f, -1.5f, 1.5f);
+
+ // Render +x and -x faces
+ static ImVec4 colorX(0.8f, 0.2f, 0.2f, 0.8f); // Red
+ ImPlot3D::SetNextFillStyle(colorX);
+ ImPlot3D::SetNextLineStyle(colorX, 2);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 3, colorX, IMPLOT3D_AUTO, colorX);
+ ImPlot3D::PlotQuad("X", &xs[0], &ys[0], &zs[0], 8);
+
+ // Render +y and -y faces
+ static ImVec4 colorY(0.2f, 0.8f, 0.2f, 0.8f); // Green
+ ImPlot3D::SetNextFillStyle(colorY);
+ ImPlot3D::SetNextLineStyle(colorY, 2);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 3, colorY, IMPLOT3D_AUTO, colorY);
+ ImPlot3D::PlotQuad("Y", &xs[8], &ys[8], &zs[8], 8);
+
+ // Render +z and -z faces
+ static ImVec4 colorZ(0.2f, 0.2f, 0.8f, 0.8f); // Blue
+ ImPlot3D::SetNextFillStyle(colorZ);
+ ImPlot3D::SetNextLineStyle(colorZ, 2);
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 3, colorZ, IMPLOT3D_AUTO, colorZ);
+ ImPlot3D::PlotQuad("Z", &xs[16], &ys[16], &zs[16], 8);
+
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoSurfacePlots() {
+ constexpr int N = 20;
+ static float xs[N * N], ys[N * N], zs[N * N];
+
+ // Define the range for X and Y
+ constexpr float range_min = -5.0f;
+ constexpr float range_max = 5.0f;
+ constexpr float step = (range_max - range_min) / (N - 1);
+
+ // Populate the xs, ys, and zs arrays
+ for (int i = 0; i < N; i++) {
+ for (int j = 0; j < N; j++) {
+ int idx = i * N + j;
+ xs[idx] = range_min + j * step; // X values are constant along rows
+ ys[idx] = range_min + i * step; // Y values are constant along columns
+ zs[idx] = sinf(std::sqrt(xs[idx] * xs[idx] + ys[idx] * ys[idx])); // Z = sin(sqrt(X^2 + Y^2))
+ }
+ }
+
+ // Begin the plot
+ ImPlot3D::PushColormap("Hot");
+ if (ImPlot3D::BeginPlot("Surface Plots")) {
+ // Set styles
+ ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.8f);
+ ImPlot3D::SetNextLineStyle(ImPlot3D::GetColormapColor(1));
+
+ // Plot the surface
+ ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N);
+
+ // End the plot
+ ImPlot3D::PopStyleVar();
+ ImPlot3D::EndPlot();
+ }
+ ImPlot3D::PopColormap();
+}
+
+void DemoMeshPlots() {
+ static int mesh_id = 0;
+ ImGui::Combo("Mesh", &mesh_id, "Duck\0Sphere\0Cube\0\0");
+
+ // Choose fill color
+ static bool set_fill_color = true;
+ static ImVec4 fill_color = ImVec4(0.8f, 0.8f, 0.2f, 0.6f);
+ ImGui::Checkbox("Fill Color", &set_fill_color);
+ if (set_fill_color) {
+ ImGui::SameLine();
+ ImGui::ColorEdit4("##MeshFillColor", (float*)&fill_color);
+ }
+
+ // Choose line color
+ static bool set_line_color = true;
+ static ImVec4 line_color = ImVec4(0.2f, 0.2f, 0.2f, 0.8f);
+ ImGui::Checkbox("Line Color", &set_line_color);
+ if (set_line_color) {
+ ImGui::SameLine();
+ ImGui::ColorEdit4("##MeshLineColor", (float*)&line_color);
+ }
+
+ // Choose marker color
+ static bool set_marker_color = false;
+ static ImVec4 marker_color = ImVec4(0.2f, 0.2f, 0.2f, 0.8f);
+ ImGui::Checkbox("Marker Color", &set_marker_color);
+ if (set_marker_color) {
+ ImGui::SameLine();
+ ImGui::ColorEdit4("##MeshMarkerColor", (float*)&marker_color);
+ }
+
+ if (ImPlot3D::BeginPlot("Mesh Plots")) {
+ ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1, 1);
+
+ // Set colors
+ if (set_fill_color)
+ ImPlot3D::SetNextFillStyle(fill_color);
+ else {
+ // If not set as transparent, the fill color will be determined by the colormap
+ ImPlot3D::SetNextFillStyle(ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
+ }
+ if (set_line_color)
+ ImPlot3D::SetNextLineStyle(line_color);
+ if (set_marker_color)
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 3, marker_color, IMPLOT3D_AUTO, marker_color);
+
+ // Plot mesh
+ if (mesh_id == 0)
+ ImPlot3D::PlotMesh("Duck", duck_vtx, duck_idx, DUCK_VTX_COUNT, DUCK_IDX_COUNT);
+ else if (mesh_id == 1)
+ ImPlot3D::PlotMesh("Sphere", sphere_vtx, sphere_idx, SPHERE_VTX_COUNT, SPHERE_IDX_COUNT);
+ else if (mesh_id == 2)
+ ImPlot3D::PlotMesh("Cube", cube_vtx, cube_idx, CUBE_VTX_COUNT, CUBE_IDX_COUNT);
+
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoRealtimePlots() {
+ ImGui::BulletText("Move your mouse to change the data!");
+ static ScrollingBuffer sdata1, sdata2, sdata3;
+ static ImPlot3DAxisFlags flags = ImPlot3DAxisFlags_NoTickLabels;
+ static float t = 0.0f;
+ static float last_t = -1.0f;
+
+ if (ImPlot3D::BeginPlot("Scrolling Plot", ImVec2(-1, 400))) {
+ // Pool mouse data every 10 ms
+ t += ImGui::GetIO().DeltaTime;
+ if (t - last_t > 0.01f) {
+ last_t = t;
+ ImVec2 mouse = ImGui::GetMousePos();
+ if (ImAbs(mouse.x) < 1e4f && ImAbs(mouse.y) < 1e4f) {
+ ImVec2 plot_center = ImPlot3D::GetFramePos();
+ plot_center.x += ImPlot3D::GetFrameSize().x / 2;
+ plot_center.y += ImPlot3D::GetFrameSize().y / 2;
+ sdata1.AddPoint(t);
+ sdata2.AddPoint(mouse.x - plot_center.x);
+ sdata3.AddPoint(mouse.y - plot_center.y);
+ }
+ }
+
+ ImPlot3D::SetupAxes("Time", "Mouse X", "Mouse Y", flags, flags, flags);
+ ImPlot3D::SetupAxisLimits(ImAxis3D_X, t - 10.0f, t, ImPlot3DCond_Always);
+ ImPlot3D::SetupAxisLimits(ImAxis3D_Y, -400, 400, ImPlot3DCond_Once);
+ ImPlot3D::SetupAxisLimits(ImAxis3D_Z, -400, 400, ImPlot3DCond_Once);
+ ImPlot3D::PlotLine("Mouse", &sdata1.Data[0], &sdata2.Data[0], &sdata3.Data[0], sdata1.Data.size(), 0, sdata1.Offset, sizeof(float));
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoMarkersAndText() {
+ static float mk_size = ImPlot3D::GetStyle().MarkerSize;
+ static float mk_weight = ImPlot3D::GetStyle().MarkerWeight;
+ ImGui::DragFloat("Marker Size", &mk_size, 0.1f, 2.0f, 10.0f, "%.2f px");
+ ImGui::DragFloat("Marker Weight", &mk_weight, 0.05f, 0.5f, 3.0f, "%.2f px");
+
+ if (ImPlot3D::BeginPlot("##MarkerStyles", ImVec2(-1, 0), ImPlot3DFlags_CanvasOnly)) {
+
+ ImPlot3D::SetupAxes(nullptr, nullptr, nullptr, ImPlot3DAxisFlags_NoDecorations, ImPlot3DAxisFlags_NoDecorations, ImPlot3DAxisFlags_NoDecorations);
+ ImPlot3D::SetupAxesLimits(-0.5, 1.5, -0.5, 1.5, 0, ImPlot3DMarker_COUNT + 1);
+
+ float xs[2] = {0, 0};
+ float ys[2] = {0, 0};
+ float zs[2] = {ImPlot3DMarker_COUNT, ImPlot3DMarker_COUNT + 1};
+
+ // Filled markers
+ for (int m = 0; m < ImPlot3DMarker_COUNT; ++m) {
+ xs[1] = xs[0] + ImCos(zs[0] / float(ImPlot3DMarker_COUNT) * 2 * IM_PI) * 0.5;
+ ys[1] = ys[0] + ImSin(zs[0] / float(ImPlot3DMarker_COUNT) * 2 * IM_PI) * 0.5;
+
+ ImGui::PushID(m);
+ ImPlot3D::SetNextMarkerStyle(m, mk_size, IMPLOT3D_AUTO_COL, mk_weight);
+ ImPlot3D::PlotLine("##Filled", xs, ys, zs, 2);
+ ImGui::PopID();
+ zs[0]--;
+ zs[1]--;
+ }
+
+ xs[0] = 1;
+ ys[0] = 1;
+ zs[0] = ImPlot3DMarker_COUNT;
+ zs[1] = zs[0] + 1;
+
+ // Open markers
+ for (int m = 0; m < ImPlot3DMarker_COUNT; ++m) {
+ xs[1] = xs[0] + ImCos(zs[0] / float(ImPlot3DMarker_COUNT) * 2 * IM_PI) * 0.5;
+ ys[1] = ys[0] - ImSin(zs[0] / float(ImPlot3DMarker_COUNT) * 2 * IM_PI) * 0.5;
+
+ ImGui::PushID(m);
+ ImPlot3D::SetNextMarkerStyle(m, mk_size, ImVec4(0, 0, 0, 0), mk_weight);
+ ImPlot3D::PlotLine("##Open", xs, ys, zs, 2);
+ ImGui::PopID();
+ zs[0]--;
+ zs[1]--;
+ }
+
+ ImPlot3D::PlotText("Filled Markers", 0.0f, 0.0f, 6.0f);
+ ImPlot3D::PlotText("Open Markers", 1.0f, 1.0f, 6.0f);
+
+ ImPlot3D::PushStyleColor(ImPlot3DCol_InlayText, ImVec4(1, 0, 1, 1));
+ ImPlot3D::PlotText("Rotated Text", 0.5f, 0.5f, 6.0f, IM_PI / 4, ImVec2(0, 0));
+ ImPlot3D::PopStyleColor();
+
+ ImPlot3D::EndPlot();
+ }
+}
+
+void DemoNaNValues() {
+ static bool include_nan = true;
+ static ImPlot3DLineFlags flags = 0;
+
+ float data1[5] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
+ float data2[5] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
+ float data3[5] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
+
+ if (include_nan)
+ data1[2] = NAN;
+
+ ImGui::Checkbox("Include NaN", &include_nan);
+ ImGui::SameLine();
+ ImGui::CheckboxFlags("Skip NaN", (unsigned int*)&flags, ImPlot3DLineFlags_SkipNaN);
+
+ if (ImPlot3D::BeginPlot("##NaNValues")) {
+ ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square);
+ ImPlot3D::PlotLine("Line", data1, data2, data3, 5, flags);
+ ImPlot3D::EndPlot();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Custom
+//-----------------------------------------------------------------------------
+
+void DemoCustomStyles() {
+ ImPlot3D::PushColormap(ImPlot3DColormap_Deep);
+ // normally you wouldn't change the entire style each frame
+ ImPlot3DStyle backup = ImPlot3D::GetStyle();
+ MyImPlot3D::StyleSeaborn();
+ if (ImPlot3D::BeginPlot("Seaborn Style")) {
+ ImPlot3D::SetupAxes("X-axis", "Y-axis", "Z-axis");
+ ImPlot3D::SetupAxesLimits(-0.5f, 9.5f, -0.5f, 0.5f, 0, 10);
+ unsigned int xs[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ unsigned int ys[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ unsigned int lin[10] = {8, 8, 9, 7, 8, 8, 8, 9, 7, 8};
+ unsigned int dot[10] = {7, 6, 6, 7, 8, 5, 6, 5, 8, 7};
+ ImPlot3D::NextColormapColor(); // Skip blue
+ ImPlot3D::PlotLine("Line", xs, ys, lin, 10);
+ ImPlot3D::NextColormapColor(); // Skip green
+ ImPlot3D::PlotScatter("Scatter", xs, ys, dot, 10);
+ ImPlot3D::EndPlot();
+ }
+ ImPlot3D::GetStyle() = backup;
+ ImPlot3D::PopColormap();
+}
+
+void DemoCustomRendering() {
+ if (ImPlot3D::BeginPlot("##CustomRend")) {
+ ImPlot3D::SetupAxesLimits(-0.1f, 1.1f, -0.1f, 1.1f, -0.1f, 1.1f);
+
+ // Draw circle
+ ImVec2 cntr = ImPlot3D::PlotToPixels(ImPlot3DPoint(0.5f, 0.5f, 0.5f));
+ ImPlot3D::GetPlotDrawList()->AddCircleFilled(cntr, 20, IM_COL32(255, 255, 0, 255), 20);
+
+ // Draw box
+ ImPlot3DPoint corners[8] = {
+ ImPlot3DPoint(0, 0, 0),
+ ImPlot3DPoint(1, 0, 0),
+ ImPlot3DPoint(1, 1, 0),
+ ImPlot3DPoint(0, 1, 0),
+ ImPlot3DPoint(0, 0, 1),
+ ImPlot3DPoint(1, 0, 1),
+ ImPlot3DPoint(1, 1, 1),
+ ImPlot3DPoint(0, 1, 1),
+ };
+ ImVec2 corners_px[8];
+ for (int i = 0; i < 8; i++)
+ corners_px[i] = ImPlot3D::PlotToPixels(corners[i]);
+
+ ImU32 col = IM_COL32(128, 0, 255, 255);
+ for (int i = 0; i < 4; i++) {
+ ImPlot3D::GetPlotDrawList()->AddLine(corners_px[i], corners_px[(i + 1) % 4], col);
+ ImPlot3D::GetPlotDrawList()->AddLine(corners_px[i + 4], corners_px[(i + 1) % 4 + 4], col);
+ ImPlot3D::GetPlotDrawList()->AddLine(corners_px[i], corners_px[i + 4], col);
+ }
+ ImPlot3D::EndPlot();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Demo Window
+//-----------------------------------------------------------------------------
+
+void DemoHelp() {
+ ImGui::SeparatorText("ABOUT THIS DEMO:");
+ ImGui::BulletText("The other tabs are demonstrating many aspects of the library.");
+
+ ImGui::SeparatorText("PROGRAMMER GUIDE:");
+ ImGui::BulletText("See the ShowDemoWindow() code in implot3d_demo.cpp. <- you are here!");
+ ImGui::BulletText("See comments in implot3d_demo.cpp.");
+ ImGui::BulletText("See example application in example/ folder.");
+
+ ImGui::SeparatorText("USER GUIDE:");
+ ImGui::BulletText("Translation");
+ {
+ ImGui::Indent();
+ ImGui::BulletText("Left-click drag to translate.");
+ ImGui::BulletText("If over axis, only that axis will translate.");
+ ImGui::BulletText("If over plane, only that plane will translate.");
+ ImGui::BulletText("If outside plot area, translate in the view plane.");
+ ImGui::Unindent();
+ }
+
+ ImGui::BulletText("Zoom");
+ {
+ ImGui::Indent();
+ ImGui::BulletText("Scroll or middle-click drag to zoom.");
+ ImGui::BulletText("If over axis, only that axis will zoom.");
+ ImGui::BulletText("If over plane, only that plane will zoom.");
+ ImGui::BulletText("If outside plot area, zoom the entire plot.");
+ ImGui::Unindent();
+ }
+
+ ImGui::BulletText("Rotation");
+ {
+ ImGui::Indent();
+ ImGui::BulletText("Right-click drag to rotate.");
+ ImGui::BulletText("To reset rotation, double right-click outside plot area.");
+ ImGui::BulletText("To rotate to plane, double right-click when over the plane.");
+ ImGui::Unindent();
+ }
+
+ ImGui::BulletText("Fit data");
+ {
+ ImGui::Indent();
+ ImGui::BulletText("Double left-click to fit.");
+ ImGui::BulletText("If over axis, fit data to axis.");
+ ImGui::BulletText("If over plane, fit data to plane.");
+ ImGui::BulletText("If outside plot area, fit data to plot.");
+ ImGui::Unindent();
+ }
+
+ ImGui::BulletText("Context Menus");
+ {
+ ImGui::Indent();
+ ImGui::BulletText("Right-click outside plot area to show full context menu.");
+ ImGui::BulletText("Right-click over legend to show legend context menu.");
+ ImGui::BulletText("Right-click over axis to show axis context menu.");
+ ImGui::BulletText("Right-click over plane to show plane context menu.");
+ ImGui::Unindent();
+ }
+
+ ImGui::BulletText("Click legend label icons to show/hide plot items.");
+}
+
+void DemoHeader(const char* label, void (*demo)()) {
+ if (ImGui::TreeNodeEx(label)) {
+ demo();
+ ImGui::TreePop();
+ }
+}
+
+void ShowDemoWindow(bool* p_open) {
+ static bool show_implot3d_style_editor = false;
+ static bool show_imgui_metrics = false;
+ static bool show_imgui_style_editor = false;
+ static bool show_imgui_demo = false;
+
+ if (show_implot3d_style_editor) {
+ ImGui::Begin("Style Editor (ImPlot3D)", &show_implot3d_style_editor);
+ ImPlot3D::ShowStyleEditor();
+ ImGui::End();
+ }
+
+ if (show_imgui_style_editor) {
+ ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
+ ImGui::ShowStyleEditor();
+ ImGui::End();
+ }
+ if (show_imgui_metrics)
+ ImGui::ShowMetricsWindow(&show_imgui_metrics);
+ if (show_imgui_demo)
+ ImGui::ShowDemoWindow(&show_imgui_demo);
+
+
+ ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);
+ ImGui::SetNextWindowSize(ImVec2(600, 750), ImGuiCond_FirstUseEver);
+ ImGui::Begin("ImPlot3D Demo", p_open, ImGuiWindowFlags_MenuBar);
+ if (ImGui::BeginMenuBar()) {
+ if (ImGui::BeginMenu("Tools")) {
+ ImGui::MenuItem("Style Editor", nullptr, &show_implot3d_style_editor);
+ ImGui::Separator();
+ ImGui::MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
+ ImGui::MenuItem("ImGui Style Editor", nullptr, &show_imgui_style_editor);
+ ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenuBar();
+ }
+
+ ImGui::Text("ImPlot3D says olá! (%s)", IMPLOT3D_VERSION);
+
+ ImGui::Spacing();
+
+ if (ImGui::BeginTabBar("ImPlot3DDemoTabs")) {
+ if (ImGui::BeginTabItem("Plots")) {
+ DemoHeader("Line Plots", DemoLinePlots);
+ DemoHeader("Scatter Plots", DemoScatterPlots);
+ DemoHeader("Triangle Plots", DemoTrianglePlots);
+ DemoHeader("Quad Plots", DemoQuadPlots);
+ DemoHeader("Surface Plots", DemoSurfacePlots);
+ DemoHeader("Mesh Plots", DemoMeshPlots);
+ DemoHeader("Realtime Plots", DemoRealtimePlots);
+ DemoHeader("Markers and Text", DemoMarkersAndText);
+ DemoHeader("NaN Values", DemoNaNValues);
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem("Custom")) {
+ DemoHeader("Custom Styles", DemoCustomStyles);
+ DemoHeader("Custom Rendering", DemoCustomRendering);
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem("Help")) {
+ DemoHelp();
+ ImGui::EndTabItem();
+ }
+ ImGui::EndTabBar();
+ }
+ ImGui::End();
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Style Editor
+//-----------------------------------------------------------------------------
+
+bool ShowStyleSelector(const char* label) {
+ static int style_idx = -1;
+ if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0")) {
+ switch (style_idx) {
+ case 0: StyleColorsAuto(); break;
+ case 1: StyleColorsClassic(); break;
+ case 2: StyleColorsDark(); break;
+ case 3: StyleColorsLight(); break;
+ }
+ return true;
+ }
+ return false;
+}
+
+void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) {
+ const int n = continuous ? size - 1 : size;
+ ImU32 col1, col2;
+ if (vert) {
+ const float step = bounds.GetHeight() / n;
+ ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step);
+ for (int i = 0; i < n; ++i) {
+ if (reversed) {
+ col1 = colors[size - i - 1];
+ col2 = continuous ? colors[size - i - 2] : col1;
+ } else {
+ col1 = colors[i];
+ col2 = continuous ? colors[i + 1] : col1;
+ }
+ DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2);
+ rect.TranslateY(step);
+ }
+ } else {
+ const float step = bounds.GetWidth() / n;
+ ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y);
+ for (int i = 0; i < n; ++i) {
+ if (reversed) {
+ col1 = colors[size - i - 1];
+ col2 = continuous ? colors[size - i - 2] : col1;
+ } else {
+ col1 = colors[i];
+ col2 = continuous ? colors[i + 1] : col1;
+ }
+ DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1);
+ rect.TranslateX(step);
+ }
+ }
+}
+
+static inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299f + bg.y * 0.587f + bg.z * 0.114f) > 0.5f ? IM_COL32_BLACK : IM_COL32_WHITE; }
+static inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); }
+
+bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlot3DColormap cmap) {
+ ImGuiContext& G = *GImGui;
+ const ImGuiStyle& style = G.Style;
+ ImGuiWindow* Window = G.CurrentWindow;
+ if (Window->SkipItems)
+ return false;
+ ImPlot3DContext& gp = *GImPlot3D;
+ cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
+ IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
+ const ImU32* keys = gp.ColormapData.GetKeys(cmap);
+ const int count = gp.ColormapData.GetKeyCount(cmap);
+ const bool qual = gp.ColormapData.IsQual(cmap);
+ const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos;
+ const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);
+ ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
+ const ImRect rect = ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y);
+ RenderColorBar(keys, count, *ImGui::GetWindowDrawList(), rect, false, false, !qual);
+ const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap, G.Style.ButtonTextAlign.x));
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 1, 1, 0.1f));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 1, 1, 0.2f));
+ ImGui::PushStyleColor(ImGuiCol_Text, text);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ const bool pressed = ImGui::Button(label, size);
+ ImGui::PopStyleColor(4);
+ ImGui::PopStyleVar(1);
+ return pressed;
+}
+
+void ShowStyleEditor(ImPlot3DStyle* ref) {
+ ImPlot3DContext& gp = *GImPlot3D;
+
+ // Handle style internal storage
+ ImPlot3DStyle& style = GetStyle();
+ static ImPlot3DStyle ref_saved_style;
+ static bool init = true;
+ if (init && ref == nullptr)
+ ref_saved_style = style;
+ init = false;
+ if (ref == nullptr)
+ ref = &ref_saved_style;
+
+ // Handle flash style color
+ static float flash_color_time = 0.5f;
+ static ImPlot3DCol flash_color_idx = ImPlot3DCol_COUNT;
+ static ImVec4 flash_color_backup = ImVec4(0, 0, 0, 0);
+ if (flash_color_idx != ImPlot3DCol_COUNT) {
+ // Flash color
+ ImVec4& color = style.Colors[flash_color_idx];
+ ImGui::ColorConvertHSVtoRGB(ImCos(flash_color_time * 6.0f) * 0.5f + 0.5f, 0.5f, 0.5f, color.x, color.y, color.z);
+ color.w = 1.0f;
+
+ // Decrease timer until zero
+ if ((flash_color_time -= ImGui::GetIO().DeltaTime) <= 0.0f) {
+ // When timer reaches zero, restore the backup color
+ style.Colors[flash_color_idx] = flash_color_backup;
+ flash_color_idx = ImPlot3DCol_COUNT;
+ flash_color_time = 0.5f;
+ }
+ }
+
+ // Style selector
+ if (ImPlot3D::ShowStyleSelector("Colors##Selector"))
+ ref_saved_style = style;
+
+ // Save/Revert button
+ if (ImGui::Button("Save Ref"))
+ *ref = ref_saved_style = style;
+ ImGui::SameLine();
+ if (ImGui::Button("Revert Ref"))
+ style = *ref;
+ ImGui::SameLine();
+ HelpMarker(
+ "Save/Revert in local non-persistent storage. Default Colors definition are not affected. "
+ "Use \"Export\" below to save them somewhere.");
+
+ ImGui::Separator();
+
+ if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) {
+ if (ImGui::BeginTabItem("Variables")) {
+ ImGui::Text("Item Styling");
+ ImGui::SliderFloat("LineWeight", &style.LineWeight, 0.0f, 5.0f, "%.1f");
+ ImGui::SliderFloat("MarkerSize", &style.MarkerSize, 2.0f, 10.0f, "%.1f");
+ ImGui::SliderFloat("MarkerWeight", &style.MarkerWeight, 0.0f, 5.0f, "%.1f");
+ ImGui::SliderFloat("FillAlpha", &style.FillAlpha, 0.0f, 1.0f, "%.2f");
+ ImGui::Text("Plot Styling");
+ ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f");
+ ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f");
+ ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f");
+ ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f");
+ ImGui::Text("Legend Styling");
+ ImGui::SliderFloat2("LegendPadding", (float*)&style.LegendPadding, 0.0f, 20.0f, "%.0f");
+ ImGui::SliderFloat2("LegendInnerPadding", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, "%.0f");
+ ImGui::SliderFloat2("LegendSpacing", (float*)&style.LegendSpacing, 0.0f, 5.0f, "%.0f");
+ ImGui::EndTabItem();
+ }
+
+ if (ImGui::BeginTabItem("Colors")) {
+ static int output_dest = 0;
+ static bool output_only_modified = true;
+ if (ImGui::Button("Export")) {
+ if (output_dest == 0)
+ ImGui::LogToClipboard();
+ else
+ ImGui::LogToTTY();
+ ImGui::LogText("ImVec4* colors = ImPlot3D::GetStyle().Colors;\n");
+ for (int i = 0; i < ImPlot3DCol_COUNT; i++) {
+ const ImVec4& col = style.Colors[i];
+ const char* name = ImPlot3D::GetStyleColorName(i);
+ if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0)
+ ImGui::LogText("colors[ImPlot3DCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\n",
+ name, 15 - (int)strlen(name), "", col.x, col.y, col.z, col.w);
+ }
+ ImGui::LogFinish();
+ }
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(120);
+ ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
+ ImGui::SameLine();
+ ImGui::Checkbox("Only Modified Colors", &output_only_modified);
+
+ static ImGuiTextFilter filter;
+ filter.Draw("Filter colors", ImGui::GetFontSize() * 16);
+
+ static ImGuiColorEditFlags alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf;
+ if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None))
+ alpha_flags = ImGuiColorEditFlags_None;
+ ImGui::SameLine();
+ if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview))
+ alpha_flags = ImGuiColorEditFlags_AlphaPreview;
+ ImGui::SameLine();
+ if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf))
+ alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf;
+ ImGui::SameLine();
+ HelpMarker(
+ "In the color list:\n"
+ "Left-click on color square to open color picker,\n"
+ "Right-click to open edit options menu.");
+
+ ImGui::Separator();
+
+ for (int i = 0; i < ImPlot3DCol_COUNT; i++) {
+ const char* name = ImPlot3D::GetStyleColorName(i);
+ if (!filter.PassFilter(name))
+ continue;
+ ImGui::PushID(i);
+
+ // Flash color
+ if (ImGui::Button("?")) {
+ if (flash_color_idx != ImPlot3DCol_COUNT)
+ style.Colors[flash_color_idx] = flash_color_backup;
+ flash_color_time = 0.5f;
+ flash_color_idx = (ImPlot3DCol)i;
+ flash_color_backup = style.Colors[i];
+ }
+ ImGui::SetItemTooltip("Flash given color to identify places where it is used.");
+ ImGui::SameLine();
+
+ // Handle auto color selection
+ const bool is_auto = IsColorAuto(style.Colors[i]);
+ if (is_auto)
+ ImGui::BeginDisabled();
+ if (ImGui::Button("Auto"))
+ style.Colors[i] = IMPLOT3D_AUTO_COL;
+ if (is_auto)
+ ImGui::EndDisabled();
+
+ // Color selection
+ ImGui::SameLine();
+ if (ImGui::ColorEdit4("##Color", (float*)&style.Colors[i], ImGuiColorEditFlags_NoInputs | alpha_flags)) {
+ if (style.Colors[i].w == -1)
+ style.Colors[i].w = 1;
+ }
+
+ // Save/Revert buttons if color changed
+ if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) {
+ ImGui::SameLine();
+ if (ImGui::Button("Save"))
+ ref->Colors[i] = style.Colors[i];
+ ImGui::SameLine();
+ if (ImGui::Button("Revert"))
+ style.Colors[i] = ref->Colors[i];
+ }
+ ImGui::SameLine();
+ ImGui::TextUnformatted(name);
+ ImGui::PopID();
+ }
+ ImGui::EndTabItem();
+ }
+
+ if (ImGui::BeginTabItem("Colormaps")) {
+ static int output_dest = 0;
+ if (ImGui::Button("Export", ImVec2(75, 0))) {
+ if (output_dest == 0)
+ ImGui::LogToClipboard();
+ else
+ ImGui::LogToTTY();
+ int size = GetColormapSize();
+ const char* name = GetColormapName(gp.Style.Colormap);
+ ImGui::LogText("static const ImU32 %s_Data[%d] = {\n", name, size);
+ for (int i = 0; i < size; ++i) {
+ ImU32 col = GetColormapColorU32(i, gp.Style.Colormap);
+ ImGui::LogText(" %u%s\n", col, i == size - 1 ? "" : ",");
+ }
+ ImGui::LogText("};\nImPlotColormap %s = ImPlot::AddColormap(\"%s\", %s_Data, %d);", name, name, name, size);
+ ImGui::LogFinish();
+ }
+ ImGui::SameLine();
+ ImGui::SetNextItemWidth(120);
+ ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
+ ImGui::SameLine();
+ static bool edit = false;
+ ImGui::Checkbox("Edit Mode", &edit);
+
+ // built-in/added
+ ImGui::Separator();
+ for (int i = 0; i < gp.ColormapData.Count; ++i) {
+ ImGui::PushID(i);
+ int size = gp.ColormapData.GetKeyCount(i);
+ bool selected = i == gp.Style.Colormap;
+
+ const char* name = GetColormapName(i);
+ if (!selected)
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);
+ if (ImGui::Button(name, ImVec2(100, 0))) {
+ gp.Style.Colormap = i;
+ BustItemCache();
+ }
+ if (!selected)
+ ImGui::PopStyleVar();
+ ImGui::SameLine();
+ ImGui::BeginGroup();
+ if (edit) {
+ for (int c = 0; c < size; ++c) {
+ ImGui::PushID(c);
+ ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i, c));
+ if (ImGui::ColorEdit4("", &col4.x, ImGuiColorEditFlags_NoInputs)) {
+ ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4);
+ gp.ColormapData.SetKeyColor(i, c, col32);
+ BustItemCache();
+ }
+ if ((c + 1) % 12 != 0 && c != size - 1)
+ ImGui::SameLine();
+ ImGui::PopID();
+ }
+ } else {
+ if (ColormapButton("##", ImVec2(-1, 0), i))
+ edit = true;
+ }
+ ImGui::EndGroup();
+ ImGui::PopID();
+ }
+
+ static ImVector custom;
+ if (custom.Size == 0) {
+ custom.push_back(ImVec4(1, 0, 0, 1));
+ custom.push_back(ImVec4(0, 1, 0, 1));
+ custom.push_back(ImVec4(0, 0, 1, 1));
+ }
+ ImGui::Separator();
+ ImGui::BeginGroup();
+ static char name[16] = "MyColormap";
+
+ if (ImGui::Button("+", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x) / 2, 0)))
+ custom.push_back(ImVec4(0, 0, 0, 1));
+ ImGui::SameLine();
+ if (ImGui::Button("-", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x) / 2, 0)) && custom.Size > 2)
+ custom.pop_back();
+ ImGui::SetNextItemWidth(100);
+ ImGui::InputText("##Name", name, 16, ImGuiInputTextFlags_CharsNoBlank);
+ static bool qual = true;
+ ImGui::Checkbox("Qualitative", &qual);
+ if (ImGui::Button("Add", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name) == -1)
+ AddColormap(name, custom.Data, custom.Size, qual);
+
+ ImGui::EndGroup();
+ ImGui::SameLine();
+ ImGui::BeginGroup();
+ for (int c = 0; c < custom.Size; ++c) {
+ ImGui::PushID(c);
+ if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs)) {
+ }
+ if ((c + 1) % 12 != 0)
+ ImGui::SameLine();
+ ImGui::PopID();
+ }
+ ImGui::EndGroup();
+
+ ImGui::EndTabItem();
+ }
+
+ ImGui::EndTabBar();
+ }
+}
+
+} // namespace ImPlot3D
+
+//-----------------------------------------------------------------------------
+// [SECTION] User Namespace Implementation
+//-----------------------------------------------------------------------------
+
+namespace MyImPlot3D {
+
+void StyleSeaborn() {
+
+ ImPlot3DStyle& style = ImPlot3D::GetStyle();
+
+ ImVec4* colors = style.Colors;
+ colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
+ colors[ImPlot3DCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ colors[ImPlot3DCol_PlotBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f);
+ colors[ImPlot3DCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
+ colors[ImPlot3DCol_LegendBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f);
+ colors[ImPlot3DCol_LegendBorder] = ImVec4(0.80f, 0.81f, 0.85f, 1.00f);
+ colors[ImPlot3DCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
+ colors[ImPlot3DCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+
+ style.LineWeight = 1.5;
+ style.Marker = ImPlot3DMarker_None;
+ style.MarkerSize = 4;
+ style.MarkerWeight = 1;
+ style.FillAlpha = 1.0f;
+ style.PlotPadding = ImVec2(12, 12);
+ style.LabelPadding = ImVec2(5, 5);
+ style.LegendPadding = ImVec2(5, 5);
+ style.PlotMinSize = ImVec2(300, 225);
+}
+
+} // namespace MyImPlot3D
diff --git a/lib/third_party/imgui/implot3d/source/implot3d_items.cpp b/lib/third_party/imgui/implot3d/source/implot3d_items.cpp
new file mode 100644
index 000000000..8b7d637ce
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/source/implot3d_items.cpp
@@ -0,0 +1,1338 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d_items.cpp
+// Date: 2024-11-26
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] Includes
+// [SECTION] Macros & Defines
+// [SECTION] Template instantiation utility
+// [SECTION] Item Utils
+// [SECTION] Draw Utils
+// [SECTION] Renderers
+// [SECTION] Indexers
+// [SECTION] Getters
+// [SECTION] RenderPrimitives
+// [SECTION] Markers
+// [SECTION] PlotScatter
+// [SECTION] PlotLine
+// [SECTION] PlotTriangle
+// [SECTION] PlotText
+
+//-----------------------------------------------------------------------------
+// [SECTION] Includes
+//-----------------------------------------------------------------------------
+
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+
+#include "implot3d.h"
+#include "implot3d_internal.h"
+
+#ifndef IMGUI_DISABLE
+
+//-----------------------------------------------------------------------------
+// [SECTION] Macros & Defines
+//-----------------------------------------------------------------------------
+
+#define SQRT_1_2 0.70710678118f
+#define SQRT_3_2 0.86602540378f
+
+// clang-format off
+#ifndef IMPLOT3D_NO_FORCE_INLINE
+ #ifdef _MSC_VER
+ #define IMPLOT3D_INLINE __forceinline
+ #elif defined(__GNUC__)
+ #define IMPLOT3D_INLINE inline __attribute__((__always_inline__))
+ #elif defined(__CLANG__)
+ #if __has_attribute(__always_inline__)
+ #define IMPLOT3D_INLINE inline __attribute__((__always_inline__))
+ #else
+ #define IMPLOT3D_INLINE inline
+ #endif
+ #else
+ #define IMPLOT3D_INLINE inline
+ #endif
+#else
+ #define IMPLOT3D_INLINE inline
+#endif
+// clang-format on
+
+#define IMPLOT3D_NORMALIZE2F(VX, VY) \
+ do { \
+ float d2 = VX * VX + VY * VY; \
+ if (d2 > 0.0f) { \
+ float inv_len = ImRsqrt(d2); \
+ VX *= inv_len; \
+ VY *= inv_len; \
+ } \
+ } while (0)
+
+IMPLOT3D_INLINE void GetLineRenderProps(const ImDrawList3D& draw_list_3d, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) {
+ const bool aa = ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLines) &&
+ ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLinesUseTex);
+ if (aa) {
+ ImVec4 tex_uvs = draw_list_3d._SharedData->TexUvLines[(int)(half_weight * 2)];
+ tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y);
+ tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w);
+ half_weight += 1;
+ } else {
+ tex_uv0 = tex_uv1 = draw_list_3d._SharedData->TexUvWhitePixel;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Template instantiation utility
+//-----------------------------------------------------------------------------
+
+// By default, templates are instantiated for `float`, `double`, and for the following integer types, which are defined in imgui.h:
+// signed char ImS8; // 8-bit signed integer
+// unsigned char ImU8; // 8-bit unsigned integer
+// signed short ImS16; // 16-bit signed integer
+// unsigned short ImU16; // 16-bit unsigned integer
+// signed int ImS32; // 32-bit signed integer == int
+// unsigned int ImU32; // 32-bit unsigned integer
+// signed long long ImS64; // 64-bit signed integer
+// unsigned long long ImU64; // 64-bit unsigned integer
+// (note: this list does *not* include `long`, `unsigned long` and `long double`)
+//
+// You can customize the supported types by defining IMPLOT3D_CUSTOM_NUMERIC_TYPES at compile time to define your own type list.
+// As an example, you could use the compile time define given by the line below in order to support only float and double.
+// -DIMPLOT3D_CUSTOM_NUMERIC_TYPES="(float)(double)"
+// In order to support all known C++ types, use:
+// -DIMPLOT3D_CUSTOM_NUMERIC_TYPES="(signed char)(unsigned char)(signed short)(unsigned short)(signed int)(unsigned int)(signed long)(unsigned long)(signed long long)(unsigned long long)(float)(double)(long double)"
+
+#ifdef IMPLOT3D_CUSTOM_NUMERIC_TYPES
+#define IMPLOT3D_NUMERIC_TYPES IMPLOT3D_CUSTOM_NUMERIC_TYPES
+#else
+#define IMPLOT3D_NUMERIC_TYPES (ImS8)(ImU8)(ImS16)(ImU16)(ImS32)(ImU32)(ImS64)(ImU64)(float)(double)
+#endif
+
+// CALL_INSTANTIATE_FOR_NUMERIC_TYPES will duplicate the template instantiation code `INSTANTIATE_MACRO(T)` on supported types.
+#define _CAT(x, y) _CAT_(x, y)
+#define _CAT_(x, y) x##y
+#define _INSTANTIATE_FOR_NUMERIC_TYPES(chain) _CAT(_INSTANTIATE_FOR_NUMERIC_TYPES_1 chain, _END)
+#define _INSTANTIATE_FOR_NUMERIC_TYPES_1(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_2
+#define _INSTANTIATE_FOR_NUMERIC_TYPES_2(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_1
+#define _INSTANTIATE_FOR_NUMERIC_TYPES_1_END
+#define _INSTANTIATE_FOR_NUMERIC_TYPES_2_END
+#define CALL_INSTANTIATE_FOR_NUMERIC_TYPES() _INSTANTIATE_FOR_NUMERIC_TYPES(IMPLOT3D_NUMERIC_TYPES)
+
+//-----------------------------------------------------------------------------
+// [SECTION] Item Utils
+//-----------------------------------------------------------------------------
+namespace ImPlot3D {
+
+static const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f;
+static const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f;
+
+bool BeginItem(const char* label_id, ImPlot3DItemFlags flags, ImPlot3DCol recolor_from) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotX() needs to be called between BeginPlot() and EndPlot()!");
+
+ // Lock setup
+ SetupLock();
+
+ ImPlot3DStyle& style = gp.Style;
+ ImPlot3DNextItemData& n = gp.NextItemData;
+
+ // Register item
+ bool just_created;
+ ImPlot3DItem* item = RegisterOrGetItem(label_id, flags, &just_created);
+
+ // Set/override item color
+ if (recolor_from != -1) {
+ if (!IsColorAuto(n.Colors[recolor_from]))
+ item->Color = ImGui::ColorConvertFloat4ToU32(n.Colors[recolor_from]);
+ else if (!IsColorAuto(gp.Style.Colors[recolor_from]))
+ item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]);
+ else if (just_created)
+ item->Color = NextColormapColorU32();
+ } else if (just_created) {
+ item->Color = NextColormapColorU32();
+ }
+
+ // Set next item color
+ ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color);
+ n.IsAutoLine = IsColorAuto(n.Colors[ImPlot3DCol_Line]) && IsColorAuto(ImPlot3DCol_Line);
+ n.IsAutoFill = IsColorAuto(n.Colors[ImPlot3DCol_Fill]) && IsColorAuto(ImPlot3DCol_Fill);
+ n.Colors[ImPlot3DCol_Line] = IsColorAuto(n.Colors[ImPlot3DCol_Line]) ? (IsColorAuto(ImPlot3DCol_Line) ? item_color : gp.Style.Colors[ImPlot3DCol_Line]) : n.Colors[ImPlot3DCol_Line];
+ n.Colors[ImPlot3DCol_Fill] = IsColorAuto(n.Colors[ImPlot3DCol_Fill]) ? (IsColorAuto(ImPlot3DCol_Fill) ? item_color : gp.Style.Colors[ImPlot3DCol_Fill]) : n.Colors[ImPlot3DCol_Fill];
+ n.Colors[ImPlot3DCol_MarkerOutline] = IsColorAuto(n.Colors[ImPlot3DCol_MarkerOutline]) ? (IsColorAuto(ImPlot3DCol_MarkerOutline) ? n.Colors[ImPlot3DCol_Line] : gp.Style.Colors[ImPlot3DCol_MarkerOutline]) : n.Colors[ImPlot3DCol_MarkerOutline];
+ n.Colors[ImPlot3DCol_MarkerFill] = IsColorAuto(n.Colors[ImPlot3DCol_MarkerFill]) ? (IsColorAuto(ImPlot3DCol_MarkerFill) ? n.Colors[ImPlot3DCol_Line] : gp.Style.Colors[ImPlot3DCol_MarkerFill]) : n.Colors[ImPlot3DCol_MarkerFill];
+
+ // Set size & weight
+ n.LineWeight = n.LineWeight < 0.0f ? style.LineWeight : n.LineWeight;
+ n.Marker = n.Marker < 0 ? style.Marker : n.Marker;
+ n.MarkerSize = n.MarkerSize < 0.0f ? style.MarkerSize : n.MarkerSize;
+ n.MarkerWeight = n.MarkerWeight < 0.0f ? style.MarkerWeight : n.MarkerWeight;
+ n.FillAlpha = n.FillAlpha < 0 ? gp.Style.FillAlpha : n.FillAlpha;
+
+ // Apply alpha modifiers
+ n.Colors[ImPlot3DCol_Fill].w *= n.FillAlpha;
+ n.Colors[ImPlot3DCol_MarkerFill].w *= n.FillAlpha;
+
+ // Set render flags
+ n.RenderLine = n.Colors[ImPlot3DCol_Line].w > 0 && n.LineWeight > 0;
+ n.RenderFill = n.Colors[ImPlot3DCol_Fill].w > 0;
+ n.RenderMarkerFill = n.Colors[ImPlot3DCol_MarkerFill].w > 0;
+ n.RenderMarkerLine = n.Colors[ImPlot3DCol_MarkerOutline].w > 0 && n.MarkerWeight > 0;
+
+ // Don't render if item is hidden
+ if (!item->Show) {
+ EndItem();
+ return false;
+ } else {
+ // Legend hover highlight
+ if (item->LegendHovered) {
+ if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlot3DLegendFlags_NoHighlightItem)) {
+ n.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE;
+ n.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE;
+ n.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE;
+ }
+ }
+ }
+
+ return true;
+}
+
+template
+bool BeginItemEx(const char* label_id, const _Getter& getter, ImPlot3DItemFlags flags = 0, ImPlot3DCol recolor_from = IMPLOT3D_AUTO) {
+ if (BeginItem(label_id, flags, recolor_from)) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ if (plot.FitThisFrame && !ImHasFlag(flags, ImPlot3DItemFlags_NoFit)) {
+ for (int i = 0; i < getter.Count; i++)
+ plot.ExtendFit(getter(i));
+ }
+ return true;
+ }
+ return false;
+}
+
+void EndItem() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ gp.NextItemData.Reset();
+}
+
+ImPlot3DItem* RegisterOrGetItem(const char* label_id, ImPlot3DItemFlags flags, bool* just_created) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DItemGroup& Items = *gp.CurrentItems;
+ ImGuiID id = Items.GetItemID(label_id);
+ if (just_created != nullptr)
+ *just_created = Items.GetItem(id) == nullptr;
+ ImPlot3DItem* item = Items.GetOrAddItem(id);
+
+ // Avoid re-adding the same item to the legend (the legend is reset every frame)
+ if (item->SeenThisFrame)
+ return item;
+ item->SeenThisFrame = true;
+
+ // Add item to the legend
+ int idx = Items.GetItemIndex(item);
+ item->ID = id;
+ if (!ImHasFlag(flags, ImPlot3DItemFlags_NoLegend) && ImGui::FindRenderedTextEnd(label_id, nullptr) != label_id) {
+ Items.Legend.Indices.push_back(idx);
+ item->NameOffset = Items.Legend.Labels.size();
+ Items.Legend.Labels.append(label_id, label_id + strlen(label_id) + 1);
+ }
+ return item;
+}
+
+void BustItemCache() {
+ ImPlot3DContext& gp = *GImPlot3D;
+ for (int p = 0; p < gp.Plots.GetBufSize(); ++p) {
+ ImPlot3DPlot& plot = *gp.Plots.GetByIndex(p);
+ plot.Items.Reset();
+ }
+}
+
+void SetNextLineStyle(const ImVec4& col, float weight) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DNextItemData& n = gp.NextItemData;
+ n.Colors[ImPlot3DCol_Line] = col;
+ n.LineWeight = weight;
+}
+
+void SetNextFillStyle(const ImVec4& col, float alpha) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DNextItemData& n = gp.NextItemData;
+ gp.NextItemData.Colors[ImPlot3DCol_Fill] = col;
+ gp.NextItemData.FillAlpha = alpha;
+}
+
+void SetNextMarkerStyle(ImPlot3DMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DNextItemData& n = gp.NextItemData;
+ n.Marker = marker;
+ n.Colors[ImPlot3DCol_MarkerFill] = fill;
+ n.MarkerSize = size;
+ n.Colors[ImPlot3DCol_MarkerOutline] = outline;
+ n.MarkerWeight = weight;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Draw Utils
+//-----------------------------------------------------------------------------
+
+IMPLOT3D_INLINE void PrimLine(ImDrawList3D& draw_list_3d, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, const ImVec2 tex_uv1, float z) {
+ float dx = P2.x - P1.x;
+ float dy = P2.y - P1.y;
+ IMPLOT3D_NORMALIZE2F(dx, dy);
+ dx *= half_weight;
+ dy *= half_weight;
+ draw_list_3d._VtxWritePtr[0].pos.x = P1.x + dy;
+ draw_list_3d._VtxWritePtr[0].pos.y = P1.y - dx;
+ draw_list_3d._VtxWritePtr[0].uv = tex_uv0;
+ draw_list_3d._VtxWritePtr[0].col = col;
+ draw_list_3d._VtxWritePtr[1].pos.x = P2.x + dy;
+ draw_list_3d._VtxWritePtr[1].pos.y = P2.y - dx;
+ draw_list_3d._VtxWritePtr[1].uv = tex_uv0;
+ draw_list_3d._VtxWritePtr[1].col = col;
+ draw_list_3d._VtxWritePtr[2].pos.x = P2.x - dy;
+ draw_list_3d._VtxWritePtr[2].pos.y = P2.y + dx;
+ draw_list_3d._VtxWritePtr[2].uv = tex_uv1;
+ draw_list_3d._VtxWritePtr[2].col = col;
+ draw_list_3d._VtxWritePtr[3].pos.x = P1.x - dy;
+ draw_list_3d._VtxWritePtr[3].pos.y = P1.y + dx;
+ draw_list_3d._VtxWritePtr[3].uv = tex_uv1;
+ draw_list_3d._VtxWritePtr[3].col = col;
+ draw_list_3d._VtxWritePtr += 4;
+ draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
+ draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+ draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+ draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);
+ draw_list_3d._IdxWritePtr += 6;
+ draw_list_3d._VtxCurrentIdx += 4;
+ draw_list_3d._ZWritePtr[0] = z;
+ draw_list_3d._ZWritePtr[1] = z;
+ draw_list_3d._ZWritePtr += 2;
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Renderers
+//-----------------------------------------------------------------------------
+
+float GetPointDepth(ImPlot3DPoint p) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+ ImPlot3DPoint p_rot = plot.Rotation * p;
+ return p_rot.z;
+}
+
+struct RendererBase {
+ RendererBase(int prims, int idx_consumed, int vtx_consumed) : Prims(prims),
+ IdxConsumed(idx_consumed),
+ VtxConsumed(vtx_consumed) {}
+ const unsigned int Prims; // Number of primitives to render
+ const unsigned int IdxConsumed; // Number of indices consumed per primitive
+ const unsigned int VtxConsumed; // Number of vertices consumed per primitive
+};
+
+template
+struct RendererMarkersFill : RendererBase {
+ RendererMarkersFill(const _Getter& getter, const ImVec2* marker, int count, float size, ImU32 col) : RendererBase(getter.Count, (count - 2) * 3, count),
+ Getter(getter),
+ Marker(marker),
+ Count(count),
+ Size(size),
+ Col(col) {}
+ void Init(ImDrawList3D& draw_list_3d) const {
+ UV = draw_list_3d._SharedData->TexUvWhitePixel;
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ ImPlot3DPoint p_plot = Getter(prim);
+ if (!cull_box.Contains(p_plot))
+ return false;
+ ImVec2 p = PlotToPixels(p_plot);
+ // 3 vertices per triangle
+ for (int i = 0; i < Count; i++) {
+ draw_list_3d._VtxWritePtr[0].pos.x = p.x + Marker[i].x * Size;
+ draw_list_3d._VtxWritePtr[0].pos.y = p.y + Marker[i].y * Size;
+ draw_list_3d._VtxWritePtr[0].uv = UV;
+ draw_list_3d._VtxWritePtr[0].col = Col;
+ draw_list_3d._VtxWritePtr++;
+ }
+ // 3 indices per triangle
+ for (int i = 2; i < Count; i++) {
+ // Indices
+ draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i - 1);
+ draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i);
+ draw_list_3d._IdxWritePtr += 3;
+ // Z
+ draw_list_3d._ZWritePtr[0] = GetPointDepth(p_plot);
+ draw_list_3d._ZWritePtr++;
+ }
+ // Update vertex count
+ draw_list_3d._VtxCurrentIdx += (ImDrawIdx)Count;
+ return true;
+ }
+ const _Getter& Getter;
+ const ImVec2* Marker;
+ const int Count;
+ const float Size;
+ const ImU32 Col;
+ mutable ImVec2 UV;
+};
+
+template
+struct RendererMarkersLine : RendererBase {
+ RendererMarkersLine(const _Getter& getter, const ImVec2* marker, int count, float size, float weight, ImU32 col) : RendererBase(getter.Count, count / 2 * 6, count / 2 * 4), Getter(getter), Marker(marker), Count(count), HalfWeight(ImMax(1.0f, weight) * 0.5f), Size(size), Col(col) {}
+
+ void Init(ImDrawList3D& draw_list_3d) const {
+ GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ ImPlot3DPoint p_plot = Getter(prim);
+ if (!cull_box.Contains(p_plot))
+ return false;
+ ImVec2 p = PlotToPixels(p_plot);
+ for (int i = 0; i < Count; i = i + 2) {
+ ImVec2 p1(p.x + Marker[i].x * Size, p.y + Marker[i].y * Size);
+ ImVec2 p2(p.x + Marker[i + 1].x * Size, p.y + Marker[i + 1].y * Size);
+ PrimLine(draw_list_3d, p1, p2, HalfWeight, Col, UV0, UV1, GetPointDepth(p_plot));
+ }
+ return true;
+ }
+
+ const _Getter& Getter;
+ const ImVec2* Marker;
+ const int Count;
+ mutable float HalfWeight;
+ const float Size;
+ const ImU32 Col;
+ mutable ImVec2 UV0;
+ mutable ImVec2 UV1;
+};
+
+template
+struct RendererLineStrip : RendererBase {
+ RendererLineStrip(const _Getter& getter, ImU32 col, float weight)
+ : RendererBase(getter.Count - 1, 6, 4),
+ Getter(getter),
+ Col(col),
+ HalfWeight(ImMax(1.0f, weight) * 0.5f) {
+ // Initialize the first point in plot coordinates
+ P1_plot = Getter(0);
+ }
+
+ void Init(ImDrawList3D& draw_list_3d) const {
+ GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ ImPlot3DPoint P2_plot = Getter(prim + 1);
+
+ // Clip the line segment to the culling box using Liang-Barsky algorithm
+ ImPlot3DPoint P1_clipped, P2_clipped;
+ bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);
+
+ if (visible) {
+ // Convert clipped points to pixel coordinates
+ ImVec2 P1_screen = PlotToPixels(P1_clipped);
+ ImVec2 P2_screen = PlotToPixels(P2_clipped);
+ // Render the line segment
+ PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
+ }
+
+ // Update for next segment
+ P1_plot = P2_plot;
+
+ return visible;
+ }
+
+ const _Getter& Getter;
+ const ImU32 Col;
+ mutable float HalfWeight;
+ mutable ImPlot3DPoint P1_plot;
+ mutable ImVec2 UV0;
+ mutable ImVec2 UV1;
+};
+
+template
+struct RendererLineStripSkip : RendererBase {
+ RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight)
+ : RendererBase(getter.Count - 1, 6, 4),
+ Getter(getter),
+ Col(col),
+ HalfWeight(ImMax(1.0f, weight) * 0.5f) {
+ // Initialize the first point in plot coordinates
+ P1_plot = Getter(0);
+ }
+
+ void Init(ImDrawList3D& draw_list_3d) const {
+ GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ // Get the next point in plot coordinates
+ ImPlot3DPoint P2_plot = Getter(prim + 1);
+ bool visible = false;
+
+ // Check for NaNs in P1_plot and P2_plot
+ if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) &&
+ !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) {
+
+ // Clip the line segment to the culling box
+ ImPlot3DPoint P1_clipped, P2_clipped;
+ visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);
+
+ if (visible) {
+ // Convert clipped points to pixel coordinates
+ ImVec2 P1_screen = PlotToPixels(P1_clipped);
+ ImVec2 P2_screen = PlotToPixels(P2_clipped);
+ // Render the line segment
+ PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
+ }
+ }
+
+ // Update P1_plot if P2_plot is valid
+ if (!ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z))
+ P1_plot = P2_plot;
+
+ return visible;
+ }
+
+ const _Getter& Getter;
+ const ImU32 Col;
+ mutable float HalfWeight;
+ mutable ImPlot3DPoint P1_plot;
+ mutable ImVec2 UV0;
+ mutable ImVec2 UV1;
+};
+
+template
+struct RendererLineSegments : RendererBase {
+ RendererLineSegments(const _Getter& getter, ImU32 col, float weight)
+ : RendererBase(getter.Count / 2, 6, 4),
+ Getter(getter),
+ Col(col),
+ HalfWeight(ImMax(1.0f, weight) * 0.5f) {}
+
+ void Init(ImDrawList3D& draw_list_3d) const {
+ GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ // Get the segment's endpoints in plot coordinates
+ ImPlot3DPoint P1_plot = Getter(prim * 2 + 0);
+ ImPlot3DPoint P2_plot = Getter(prim * 2 + 1);
+
+ // Check for NaNs in P1_plot and P2_plot
+ if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) &&
+ !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) {
+
+ // Clip the line segment to the culling box
+ ImPlot3DPoint P1_clipped, P2_clipped;
+ bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);
+
+ if (visible) {
+ // Convert clipped points to pixel coordinates
+ ImVec2 P1_screen = PlotToPixels(P1_clipped);
+ ImVec2 P2_screen = PlotToPixels(P2_clipped);
+ // Render the line segment
+ PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
+ }
+ return visible;
+ }
+
+ return false;
+ }
+
+ const _Getter& Getter;
+ const ImU32 Col;
+ mutable float HalfWeight;
+ mutable ImVec2 UV0;
+ mutable ImVec2 UV1;
+};
+
+template
+struct RendererTriangleFill : RendererBase {
+ RendererTriangleFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 3, 3, 3),
+ Getter(getter),
+ Col(col) {}
+ void Init(ImDrawList3D& draw_list_3d) const {
+ UV = draw_list_3d._SharedData->TexUvWhitePixel;
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ ImPlot3DPoint p_plot[3];
+ p_plot[0] = Getter(3 * prim);
+ p_plot[1] = Getter(3 * prim + 1);
+ p_plot[2] = Getter(3 * prim + 2);
+
+ // Check if the triangle is outside the culling box
+ if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]))
+ return false;
+
+ // Project the triangle vertices to screen space
+ ImVec2 p[3];
+ p[0] = PlotToPixels(p_plot[0]);
+ p[1] = PlotToPixels(p_plot[1]);
+ p[2] = PlotToPixels(p_plot[2]);
+
+ // 3 vertices per triangle
+ draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
+ draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
+ draw_list_3d._VtxWritePtr[0].uv = UV;
+ draw_list_3d._VtxWritePtr[0].col = Col;
+ draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
+ draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
+ draw_list_3d._VtxWritePtr[1].uv = UV;
+ draw_list_3d._VtxWritePtr[1].col = Col;
+ draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
+ draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
+ draw_list_3d._VtxWritePtr[2].uv = UV;
+ draw_list_3d._VtxWritePtr[2].col = Col;
+ draw_list_3d._VtxWritePtr += 3;
+
+ // 3 indices per triangle
+ draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
+ draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+ draw_list_3d._IdxWritePtr += 3;
+ // 1 Z per vertex
+ draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3);
+ draw_list_3d._ZWritePtr++;
+
+ // Update vertex count
+ draw_list_3d._VtxCurrentIdx += 3;
+
+ return true;
+ }
+
+ const _Getter& Getter;
+ mutable ImVec2 UV;
+ const ImU32 Col;
+};
+
+template
+struct RendererQuadFill : RendererBase {
+ RendererQuadFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 4, 6, 4),
+ Getter(getter),
+ Col(col) {}
+ void Init(ImDrawList3D& draw_list_3d) const {
+ UV = draw_list_3d._SharedData->TexUvWhitePixel;
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ ImPlot3DPoint p_plot[4];
+ p_plot[0] = Getter(4 * prim);
+ p_plot[1] = Getter(4 * prim + 1);
+ p_plot[2] = Getter(4 * prim + 2);
+ p_plot[3] = Getter(4 * prim + 3);
+
+ // Check if the quad is outside the culling box
+ if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) &&
+ !cull_box.Contains(p_plot[2]) && !cull_box.Contains(p_plot[3]))
+ return false;
+
+ // Project the quad vertices to screen space
+ ImVec2 p[4];
+ p[0] = PlotToPixels(p_plot[0]);
+ p[1] = PlotToPixels(p_plot[1]);
+ p[2] = PlotToPixels(p_plot[2]);
+ p[3] = PlotToPixels(p_plot[3]);
+
+ // Add vertices for two triangles
+ draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
+ draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
+ draw_list_3d._VtxWritePtr[0].uv = UV;
+ draw_list_3d._VtxWritePtr[0].col = Col;
+
+ draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
+ draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
+ draw_list_3d._VtxWritePtr[1].uv = UV;
+ draw_list_3d._VtxWritePtr[1].col = Col;
+
+ draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
+ draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
+ draw_list_3d._VtxWritePtr[2].uv = UV;
+ draw_list_3d._VtxWritePtr[2].col = Col;
+
+ draw_list_3d._VtxWritePtr[3].pos.x = p[3].x;
+ draw_list_3d._VtxWritePtr[3].pos.y = p[3].y;
+ draw_list_3d._VtxWritePtr[3].uv = UV;
+ draw_list_3d._VtxWritePtr[3].col = Col;
+
+ draw_list_3d._VtxWritePtr += 4;
+
+ // Add indices for two triangles
+ draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
+ draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+
+ draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+ draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);
+
+ draw_list_3d._IdxWritePtr += 6;
+
+ // Add depth values for the two triangles
+ draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3.0f);
+ draw_list_3d._ZWritePtr[1] = GetPointDepth((p_plot[0] + p_plot[2] + p_plot[3]) / 3.0f);
+ draw_list_3d._ZWritePtr += 2;
+
+ // Update vertex count
+ draw_list_3d._VtxCurrentIdx += 4;
+
+ return true;
+ }
+
+ const _Getter& Getter;
+ mutable ImVec2 UV;
+ const ImU32 Col;
+};
+
+template
+struct RendererSurfaceFill : RendererBase {
+ RendererSurfaceFill(const _Getter& getter, int x_count, int y_count, ImU32 col) : RendererBase((x_count - 1) * (y_count - 1), 6, 4),
+ Getter(getter),
+ XCount(x_count),
+ YCount(y_count),
+ Col(col) {}
+ void Init(ImDrawList3D& draw_list_3d) const {
+ UV = draw_list_3d._SharedData->TexUvWhitePixel;
+
+ // Compute min and max values for the colormap (if not solid fill)
+ const ImPlot3DNextItemData& n = GetItemData();
+ if (n.IsAutoFill) {
+ Min = FLT_MAX;
+ Max = -FLT_MAX;
+ for (int i = 0; i < Getter.Count; i++) {
+ float z = Getter(i).z;
+ Min = ImMin(Min, z);
+ Max = ImMax(Max, z);
+ }
+ }
+ }
+
+ IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
+ int x = prim % (XCount - 1);
+ int y = prim / (XCount - 1);
+
+ ImPlot3DPoint p_plot[4];
+ p_plot[0] = Getter(x + y * XCount);
+ p_plot[1] = Getter(x + 1 + y * XCount);
+ p_plot[2] = Getter(x + 1 + (y + 1) * XCount);
+ p_plot[3] = Getter(x + (y + 1) * XCount);
+
+ // Check if the quad is outside the culling box
+ if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) &&
+ !cull_box.Contains(p_plot[2]) && !cull_box.Contains(p_plot[3]))
+ return false;
+
+ // Compute colors
+ ImU32 cols[4] = {Col, Col, Col, Col};
+ const ImPlot3DNextItemData& n = GetItemData();
+ if (n.IsAutoFill) {
+ float alpha = GImPlot3D->NextItemData.FillAlpha;
+ for (int i = 0; i < 4; i++) {
+ ImVec4 col = SampleColormap(ImClamp(ImRemap01(p_plot[i].z, Min, Max), 0.0f, 1.0f));
+ col.w *= alpha;
+ cols[i] = ImGui::ColorConvertFloat4ToU32(col);
+ }
+ }
+
+ // Project the quad vertices to screen space
+ ImVec2 p[4];
+ p[0] = PlotToPixels(p_plot[0]);
+ p[1] = PlotToPixels(p_plot[1]);
+ p[2] = PlotToPixels(p_plot[2]);
+ p[3] = PlotToPixels(p_plot[3]);
+
+ // Add vertices for two triangles
+ draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
+ draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
+ draw_list_3d._VtxWritePtr[0].uv = UV;
+ draw_list_3d._VtxWritePtr[0].col = cols[0];
+
+ draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
+ draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
+ draw_list_3d._VtxWritePtr[1].uv = UV;
+ draw_list_3d._VtxWritePtr[1].col = cols[1];
+
+ draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
+ draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
+ draw_list_3d._VtxWritePtr[2].uv = UV;
+ draw_list_3d._VtxWritePtr[2].col = cols[2];
+
+ draw_list_3d._VtxWritePtr[3].pos.x = p[3].x;
+ draw_list_3d._VtxWritePtr[3].pos.y = p[3].y;
+ draw_list_3d._VtxWritePtr[3].uv = UV;
+ draw_list_3d._VtxWritePtr[3].col = cols[3];
+
+ draw_list_3d._VtxWritePtr += 4;
+
+ // Add indices for two triangles
+ draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
+ draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+
+ draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
+ draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
+ draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);
+
+ draw_list_3d._IdxWritePtr += 6;
+
+ // Add depth values for the two triangles
+ draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3.0f);
+ draw_list_3d._ZWritePtr[1] = GetPointDepth((p_plot[0] + p_plot[2] + p_plot[3]) / 3.0f);
+ draw_list_3d._ZWritePtr += 2;
+
+ // Update vertex count
+ draw_list_3d._VtxCurrentIdx += 4;
+
+ return true;
+ }
+
+ const _Getter& Getter;
+ mutable ImVec2 UV;
+ mutable float Min; // Minimum value for the colormap
+ mutable float Max; // Minimum value for the colormap
+ const int XCount;
+ const int YCount;
+ const ImU32 Col;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Indexers
+//-----------------------------------------------------------------------------
+
+template
+IMPLOT3D_INLINE T IndexData(const T* data, int idx, int count, int offset, int stride) {
+ const int s = ((offset == 0) << 0) | ((stride == sizeof(T)) << 1);
+ switch (s) {
+ case 3: return data[idx];
+ case 2: return data[(offset + idx) % count];
+ case 1: return *(const T*)(const void*)((const unsigned char*)data + (size_t)((idx)) * stride);
+ case 0: return *(const T*)(const void*)((const unsigned char*)data + (size_t)((offset + idx) % count) * stride);
+ default: return T(0);
+ }
+}
+
+template
+struct IndexerIdx {
+ IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : Data(data),
+ Count(count),
+ Offset(offset),
+ Stride(stride) {}
+ template IMPLOT3D_INLINE double operator()(I idx) const {
+ return (double)IndexData(Data, idx, Count, Offset, Stride);
+ }
+ const T* Data;
+ int Count;
+ int Offset;
+ int Stride;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Getters
+//-----------------------------------------------------------------------------
+
+template
+struct GetterXYZ {
+ GetterXYZ(_IndexerX x, _IndexerY y, _IndexerZ z, int count) : IndexerX(x), IndexerY(y), IndexerZ(z), Count(count) {}
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ return ImPlot3DPoint(IndexerX(idx), IndexerY(idx), IndexerZ(idx));
+ }
+ const _IndexerX IndexerX;
+ const _IndexerY IndexerY;
+ const _IndexerZ IndexerZ;
+ const int Count;
+};
+
+template
+struct GetterLoop {
+ GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) {}
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ idx = idx % (Count - 1);
+ return Getter(idx);
+ }
+ const _Getter Getter;
+ const int Count;
+};
+
+template
+struct GetterTriangleLines {
+ GetterTriangleLines(_Getter getter) : Getter(getter), Count(getter.Count * 2) {}
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ idx = ((idx % 6 + 1) / 2) % 3 + idx / 6 * 3;
+ return Getter(idx);
+ }
+ const _Getter Getter;
+ const int Count;
+};
+
+template
+struct GetterQuadLines {
+ GetterQuadLines(_Getter getter) : Getter(getter), Count(getter.Count * 2) {}
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ idx = ((idx % 8 + 1) / 2) % 4 + idx / 8 * 4;
+ return Getter(idx);
+ }
+ const _Getter Getter;
+ const int Count;
+};
+
+template
+struct GetterSurfaceLines {
+ GetterSurfaceLines(_Getter getter, int x_count, int y_count)
+ : Getter(getter), XCount(x_count), YCount(y_count) {
+ int horizontal_segments = (XCount - 1) * YCount;
+ int vertical_segments = (YCount - 1) * XCount;
+ int segments = horizontal_segments + vertical_segments;
+ Count = segments * 2; // Each segment has 2 endpoints
+ }
+
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ // idx is an endpoint index
+ int endpoint_i = (int)(idx % 2);
+ int segment_i = (int)(idx / 2);
+
+ int horizontal_segments = (XCount - 1) * YCount;
+
+ int px, py;
+ if (segment_i < horizontal_segments) {
+ // Horizontal segment
+ int row = segment_i / (XCount - 1);
+ int col = segment_i % (XCount - 1);
+ // Endpoint 0 is (col, row), endpoint 1 is (col+1, row)
+ px = endpoint_i == 0 ? col : col + 1;
+ py = row;
+ } else {
+ // Vertical segment
+ int seg_v = segment_i - horizontal_segments;
+ int col = seg_v / (YCount - 1);
+ int row = seg_v % (YCount - 1);
+ // Endpoint 0 is (col, row), endpoint 1 is (col, row+1)
+ px = col;
+ py = row + endpoint_i;
+ }
+
+ return Getter(py * XCount + px);
+ }
+
+ const _Getter Getter;
+ int Count;
+ const int XCount;
+ const int YCount;
+};
+
+struct Getter3DPoints {
+ Getter3DPoints(const ImPlot3DPoint* points, int count) : Points(points), Count(count) {}
+ template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
+ return Points[idx];
+ }
+ const ImPlot3DPoint* Points;
+ const int Count;
+};
+
+struct GetterMeshTriangles {
+ GetterMeshTriangles(const ImPlot3DPoint* vtx, const unsigned int* idx, int idx_count)
+ : Vtx(vtx), Idx(idx), IdxCount(idx_count), TriCount(idx_count / 3), Count(idx_count) {}
+
+ template
+ IMPLOT3D_INLINE ImPlot3DPoint operator()(I i) const {
+ unsigned int vi = Idx[i];
+ return Vtx[vi];
+ }
+
+ const ImPlot3DPoint* Vtx;
+ const unsigned int* Idx;
+ int IdxCount;
+ int TriCount;
+ int Count;
+};
+
+//-----------------------------------------------------------------------------
+// [SECTION] RenderPrimitives
+//-----------------------------------------------------------------------------
+
+/// Renders primitive shapes
+template class _Renderer, class _Getter, typename... Args>
+void RenderPrimitives(const _Getter& getter, Args... args) {
+ _Renderer<_Getter> renderer(getter, args...);
+ ImPlot3DPlot& plot = *GetCurrentPlot();
+ ImDrawList3D& draw_list_3d = plot.DrawList;
+ ImPlot3DBox cull_box;
+ if (ImHasFlag(plot.Flags, ImPlot3DFlags_NoClip)) {
+ cull_box.Min = ImPlot3DPoint(-HUGE_VAL, -HUGE_VAL, -HUGE_VAL);
+ cull_box.Max = ImPlot3DPoint(HUGE_VAL, HUGE_VAL, HUGE_VAL);
+ } else {
+ cull_box.Min = plot.RangeMin();
+ cull_box.Max = plot.RangeMax();
+ }
+
+ // Find how many can be reserved up to end of current draw command's limit
+ unsigned int prims_to_render = ImMin(renderer.Prims, (ImDrawList3D::MaxIdx() - draw_list_3d._VtxCurrentIdx) / renderer.VtxConsumed);
+
+ // Reserve vertices and indices to render the primitives
+ draw_list_3d.PrimReserve(prims_to_render * renderer.IdxConsumed, prims_to_render * renderer.VtxConsumed);
+
+ // Initialize renderer
+ renderer.Init(draw_list_3d);
+
+ // Render primitives
+ int num_culled = 0;
+ for (unsigned int i = 0; i < prims_to_render; i++)
+ if (!renderer.Render(draw_list_3d, cull_box, i))
+ num_culled++;
+ // Unreserve unused vertices and indices
+ draw_list_3d.PrimUnreserve(num_culled * renderer.IdxConsumed, num_culled * renderer.VtxConsumed);
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] Markers
+//-----------------------------------------------------------------------------
+
+static const ImVec2 MARKER_FILL_CIRCLE[10] = {ImVec2(1.0f, 0.0f), ImVec2(0.809017f, 0.58778524f), ImVec2(0.30901697f, 0.95105654f), ImVec2(-0.30901703f, 0.9510565f), ImVec2(-0.80901706f, 0.5877852f), ImVec2(-1.0f, 0.0f), ImVec2(-0.80901694f, -0.58778536f), ImVec2(-0.3090171f, -0.9510565f), ImVec2(0.30901712f, -0.9510565f), ImVec2(0.80901694f, -0.5877853f)};
+static const ImVec2 MARKER_FILL_SQUARE[4] = {ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2)};
+static const ImVec2 MARKER_FILL_DIAMOND[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)};
+static const ImVec2 MARKER_FILL_UP[3] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f)};
+static const ImVec2 MARKER_FILL_DOWN[3] = {ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f)};
+static const ImVec2 MARKER_FILL_LEFT[3] = {ImVec2(-1, 0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)};
+static const ImVec2 MARKER_FILL_RIGHT[3] = {ImVec2(1, 0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)};
+static const ImVec2 MARKER_LINE_CIRCLE[20] = {
+ ImVec2(1.0f, 0.0f),
+ ImVec2(0.809017f, 0.58778524f),
+ ImVec2(0.809017f, 0.58778524f),
+ ImVec2(0.30901697f, 0.95105654f),
+ ImVec2(0.30901697f, 0.95105654f),
+ ImVec2(-0.30901703f, 0.9510565f),
+ ImVec2(-0.30901703f, 0.9510565f),
+ ImVec2(-0.80901706f, 0.5877852f),
+ ImVec2(-0.80901706f, 0.5877852f),
+ ImVec2(-1.0f, 0.0f),
+ ImVec2(-1.0f, 0.0f),
+ ImVec2(-0.80901694f, -0.58778536f),
+ ImVec2(-0.80901694f, -0.58778536f),
+ ImVec2(-0.3090171f, -0.9510565f),
+ ImVec2(-0.3090171f, -0.9510565f),
+ ImVec2(0.30901712f, -0.9510565f),
+ ImVec2(0.30901712f, -0.9510565f),
+ ImVec2(0.80901694f, -0.5877853f),
+ ImVec2(0.80901694f, -0.5877853f),
+ ImVec2(1.0f, 0.0f)};
+static const ImVec2 MARKER_LINE_SQUARE[8] = {ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, SQRT_1_2)};
+static const ImVec2 MARKER_LINE_DIAMOND[8] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(-1, 0), ImVec2(0, 1), ImVec2(0, 1), ImVec2(1, 0)};
+static const ImVec2 MARKER_LINE_UP[6] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, 0.5f)};
+static const ImVec2 MARKER_LINE_DOWN[6] = {ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f), ImVec2(-SQRT_3_2, -0.5f), ImVec2(SQRT_3_2, -0.5f)};
+static const ImVec2 MARKER_LINE_LEFT[6] = {ImVec2(-1, 0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2), ImVec2(0.5, -SQRT_3_2), ImVec2(-1, 0)};
+static const ImVec2 MARKER_LINE_RIGHT[6] = {ImVec2(1, 0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(1, 0)};
+static const ImVec2 MARKER_LINE_ASTERISK[6] = {ImVec2(-SQRT_3_2, -0.5f), ImVec2(SQRT_3_2, 0.5f), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, -0.5f), ImVec2(0, -1), ImVec2(0, 1)};
+static const ImVec2 MARKER_LINE_PLUS[4] = {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, 1)};
+static const ImVec2 MARKER_LINE_CROSS[4] = {ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2)};
+
+template
+void RenderMarkers(const _Getter& getter, ImPlot3DMarker marker, float size, bool rend_fill, ImU32 col_fill, bool rend_line, ImU32 col_line, float weight) {
+ if (rend_fill) {
+ switch (marker) {
+ case ImPlot3DMarker_Circle: RenderPrimitives(getter, MARKER_FILL_CIRCLE, 10, size, col_fill); break;
+ case ImPlot3DMarker_Square: RenderPrimitives(getter, MARKER_FILL_SQUARE, 4, size, col_fill); break;
+ case ImPlot3DMarker_Diamond: RenderPrimitives(getter, MARKER_FILL_DIAMOND, 4, size, col_fill); break;
+ case ImPlot3DMarker_Up: RenderPrimitives(getter, MARKER_FILL_UP, 3, size, col_fill); break;
+ case ImPlot3DMarker_Down: RenderPrimitives(getter, MARKER_FILL_DOWN, 3, size, col_fill); break;
+ case ImPlot3DMarker_Left: RenderPrimitives(getter, MARKER_FILL_LEFT, 3, size, col_fill); break;
+ case ImPlot3DMarker_Right: RenderPrimitives(getter, MARKER_FILL_RIGHT, 3, size, col_fill); break;
+ }
+ }
+ if (rend_line) {
+ switch (marker) {
+ case ImPlot3DMarker_Circle: RenderPrimitives(getter, MARKER_LINE_CIRCLE, 20, size, weight, col_line); break;
+ case ImPlot3DMarker_Square: RenderPrimitives(getter, MARKER_LINE_SQUARE, 8, size, weight, col_line); break;
+ case ImPlot3DMarker_Diamond: RenderPrimitives(getter, MARKER_LINE_DIAMOND, 8, size, weight, col_line); break;
+ case ImPlot3DMarker_Up: RenderPrimitives(getter, MARKER_LINE_UP, 6, size, weight, col_line); break;
+ case ImPlot3DMarker_Down: RenderPrimitives(getter, MARKER_LINE_DOWN, 6, size, weight, col_line); break;
+ case ImPlot3DMarker_Left: RenderPrimitives(getter, MARKER_LINE_LEFT, 6, size, weight, col_line); break;
+ case ImPlot3DMarker_Right: RenderPrimitives(getter, MARKER_LINE_RIGHT, 6, size, weight, col_line); break;
+ case ImPlot3DMarker_Asterisk: RenderPrimitives(getter, MARKER_LINE_ASTERISK, 6, size, weight, col_line); break;
+ case ImPlot3DMarker_Plus: RenderPrimitives(getter, MARKER_LINE_PLUS, 4, size, weight, col_line); break;
+ case ImPlot3DMarker_Cross: RenderPrimitives(getter, MARKER_LINE_CROSS, 4, size, weight, col_line); break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotScatter
+//-----------------------------------------------------------------------------
+
+template
+void PlotScatterEx(const char* label_id, const Getter& getter, ImPlot3DScatterFlags flags) {
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_MarkerOutline)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+ ImPlot3DMarker marker = n.Marker == ImPlot3DMarker_None ? ImPlot3DMarker_Circle : n.Marker;
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ if (marker != ImPlot3DMarker_None)
+ RenderMarkers(getter, marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ EndItem();
+ }
+}
+
+template
+void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, int offset, int stride) {
+ if (count < 1)
+ return;
+ GetterXYZ, IndexerIdx, IndexerIdx> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), IndexerIdx(zs, count, offset, stride), count);
+ return PlotScatterEx(label_id, getter, flags);
+}
+
+#define INSTANTIATE_MACRO(T) \
+ template IMPLOT3D_API void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, int offset, int stride);
+CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
+#undef INSTANTIATE_MACRO
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotLine
+//-----------------------------------------------------------------------------
+
+template
+void PlotLineEx(const char* label_id, const _Getter& getter, ImPlot3DLineFlags flags) {
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Line)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+ if (getter.Count >= 2 && n.RenderLine) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
+ if (ImHasFlag(flags, ImPlot3DLineFlags_Segments)) {
+ RenderPrimitives(getter, col_line, n.LineWeight);
+ } else if (ImHasFlag(flags, ImPlot3DLineFlags_Loop)) {
+ if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN))
+ RenderPrimitives(GetterLoop<_Getter>(getter), col_line, n.LineWeight);
+ else
+ RenderPrimitives(GetterLoop<_Getter>(getter), col_line, n.LineWeight);
+ } else {
+ if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN))
+ RenderPrimitives(getter, col_line, n.LineWeight);
+ else
+ RenderPrimitives(getter, col_line, n.LineWeight);
+ }
+ }
+
+ // Render markers
+ if (n.Marker != ImPlot3DMarker_None) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ }
+ EndItem();
+ }
+}
+
+IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, int offset, int stride) {
+ if (count < 2)
+ return;
+ GetterXYZ, IndexerIdx, IndexerIdx> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), IndexerIdx(zs, count, offset, stride), count);
+ return PlotLineEx(label_id, getter, flags);
+}
+
+#define INSTANTIATE_MACRO(T) \
+ template IMPLOT3D_API void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, int offset, int stride);
+CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
+#undef INSTANTIATE_MACRO
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotTriangle
+//-----------------------------------------------------------------------------
+
+template
+void PlotTriangleEx(const char* label_id, const _Getter& getter, ImPlot3DTriangleFlags flags) {
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+
+ // Render fill
+ if (getter.Count >= 3 && n.RenderFill) {
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
+ RenderPrimitives(getter, col_fill);
+ }
+
+ // Render lines
+ if (getter.Count >= 2 && n.RenderLine) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
+ RenderPrimitives(GetterTriangleLines<_Getter>(getter), col_line, n.LineWeight);
+ }
+
+ // Render markers
+ if (n.Marker != ImPlot3DMarker_None) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ }
+
+ EndItem();
+ }
+}
+
+IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags, int offset, int stride) {
+ if (count < 3)
+ return;
+ GetterXYZ, IndexerIdx, IndexerIdx> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), IndexerIdx(zs, count, offset, stride), count);
+ return PlotTriangleEx(label_id, getter, flags);
+}
+
+#define INSTANTIATE_MACRO(T) \
+ template IMPLOT3D_API void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags, int offset, int stride);
+CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
+#undef INSTANTIATE_MACRO
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotQuad
+//-----------------------------------------------------------------------------
+
+template
+void PlotQuadEx(const char* label_id, const _Getter& getter, ImPlot3DQuadFlags flags) {
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+
+ // Render fill
+ if (getter.Count >= 4 && n.RenderFill) {
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
+ RenderPrimitives(getter, col_fill);
+ }
+
+ // Render lines
+ if (getter.Count >= 2 && n.RenderLine) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
+ RenderPrimitives(GetterQuadLines<_Getter>(getter), col_line, n.LineWeight);
+ }
+
+ // Render markers
+ if (n.Marker != ImPlot3DMarker_None) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ }
+
+ EndItem();
+ }
+}
+
+IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags, int offset, int stride) {
+ if (count < 3)
+ return;
+ GetterXYZ, IndexerIdx, IndexerIdx> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), IndexerIdx(zs, count, offset, stride), count);
+ return PlotQuadEx(label_id, getter, flags);
+}
+
+#define INSTANTIATE_MACRO(T) \
+ template IMPLOT3D_API void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags, int offset, int stride);
+CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
+#undef INSTANTIATE_MACRO
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotSurface
+//-----------------------------------------------------------------------------
+
+template
+void PlotSurfaceEx(const char* label_id, const _Getter& getter, int x_count, int y_count, ImPlot3DSurfaceFlags flags) {
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+
+ // Render fill
+ if (getter.Count >= 4 && n.RenderFill) {
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
+ RenderPrimitives(getter, x_count, y_count, col_fill);
+ }
+
+ // Render lines
+ if (getter.Count >= 2 && n.RenderLine) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
+ RenderPrimitives(GetterSurfaceLines<_Getter>(getter, x_count, y_count), col_line, n.LineWeight);
+ }
+
+ // Render markers
+ if (n.Marker != ImPlot3DMarker_None) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ }
+
+ EndItem();
+ }
+}
+
+IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, ImPlot3DSurfaceFlags flags, int offset, int stride) {
+ int count = x_count * y_count;
+ if (count < 4)
+ return;
+ GetterXYZ, IndexerIdx, IndexerIdx> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), IndexerIdx(zs, count, offset, stride), count);
+ return PlotSurfaceEx(label_id, getter, x_count, y_count, flags);
+}
+
+#define INSTANTIATE_MACRO(T) \
+ template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, ImPlot3DSurfaceFlags flags, int offset, int stride);
+CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
+#undef INSTANTIATE_MACRO
+
+void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags) {
+ Getter3DPoints getter(vtx, vtx_count); // Get vertices
+ GetterMeshTriangles getter_triangles(vtx, idx, idx_count); // Get triangle vertices
+ if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
+ const ImPlot3DNextItemData& n = GetItemData();
+
+ // Render fill
+ if (getter.Count >= 3 && n.RenderFill) {
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
+ RenderPrimitives(getter_triangles, col_fill);
+ }
+
+ // Render lines
+ if (getter.Count >= 2 && n.RenderLine && !n.IsAutoLine) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
+ RenderPrimitives(GetterTriangleLines(getter_triangles), col_line, n.LineWeight);
+ }
+
+ // Render markers
+ if (n.Marker != ImPlot3DMarker_None) {
+ const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
+ const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
+ RenderMarkers(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
+ }
+
+ EndItem();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// [SECTION] PlotText
+//-----------------------------------------------------------------------------
+
+void PlotText(const char* text, float x, float y, float z, float angle, const ImVec2& pix_offset) {
+ ImPlot3DContext& gp = *GImPlot3D;
+ IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotText() needs to be called between BeginPlot() and EndPlot()!");
+ SetupLock();
+ ImPlot3DPlot& plot = *gp.CurrentPlot;
+
+ ImPlot3DBox cull_box;
+ if (ImHasFlag(plot.Flags, ImPlot3DFlags_NoClip)) {
+ cull_box.Min = ImPlot3DPoint(-HUGE_VAL, -HUGE_VAL, -HUGE_VAL);
+ cull_box.Max = ImPlot3DPoint(HUGE_VAL, HUGE_VAL, HUGE_VAL);
+ } else {
+ cull_box.Min = plot.RangeMin();
+ cull_box.Max = plot.RangeMax();
+ }
+ if (!cull_box.Contains(ImPlot3DPoint(x, y, z)))
+ return;
+
+ ImVec2 p = PlotToPixels(ImPlot3DPoint(x, y, z));
+ p.x += pix_offset.x;
+ p.y += pix_offset.y;
+ AddTextRotated(GetPlotDrawList(), p, angle, GetStyleColorU32(ImPlot3DCol_InlayText), text);
+}
+
+} // namespace ImPlot3D
+
+#endif // #ifndef IMGUI_DISABLE
diff --git a/lib/third_party/imgui/implot3d/source/implot3d_meshes.cpp b/lib/third_party/imgui/implot3d/source/implot3d_meshes.cpp
new file mode 100644
index 000000000..13bcbfe51
--- /dev/null
+++ b/lib/third_party/imgui/implot3d/source/implot3d_meshes.cpp
@@ -0,0 +1,821 @@
+//--------------------------------------------------
+// ImPlot3D v0.1
+// implot3d_meshes.cpp
+// Date: 2024-12-07
+// Author: Breno Cunha Queiroz (brenocq.com)
+//
+// Acknowledgments:
+// ImPlot3D is heavily inspired by ImPlot
+// (https://github.com/epezent/implot) by Evan Pezent,
+// and follows a similar code style and structure to
+// maintain consistency with ImPlot's API.
+//--------------------------------------------------
+
+// Table of Contents:
+// [SECTION] Includes
+// [SECTION] Cube
+// [SECTION] Sphere
+// [SECTION] Duck
+
+//-----------------------------------------------------------------------------
+// [SECTION] Includes
+//-----------------------------------------------------------------------------
+
+#include "implot3d.h"
+
+//-----------------------------------------------------------------------------
+// [SECTION] Cube
+//-----------------------------------------------------------------------------
+
+namespace ImPlot3D {
+
+// clang-format off
+ImPlot3DPoint cube_vtx[CUBE_VTX_COUNT] = {
+ {-1.0f, -1.0f, -1.0f}, // 0: Bottom-back-left
+ { 1.0f, -1.0f, -1.0f}, // 1: Bottom-back-right
+ { 1.0f, 1.0f, -1.0f}, // 2: Top-back-right
+ {-1.0f, 1.0f, -1.0f}, // 3: Top-back-left
+ {-1.0f, -1.0f, 1.0f}, // 4: Bottom-front-left
+ { 1.0f, -1.0f, 1.0f}, // 5: Bottom-front-right
+ { 1.0f, 1.0f, 1.0f}, // 6: Top-front-right
+ {-1.0f, 1.0f, 1.0f}, // 7: Top-front-left
+};
+
+unsigned int cube_idx[CUBE_IDX_COUNT] = {
+ // Back face
+ 0, 1, 2,
+ 0, 2, 3,
+ // Front face
+ 4, 5, 6,
+ 4, 6, 7,
+ // Left face
+ 0, 3, 7,
+ 0, 7, 4,
+ // Right face
+ 1, 5, 6,
+ 1, 6, 2,
+ // Top face
+ 3, 2, 6,
+ 3, 6, 7,
+ // Bottom face
+ 0, 1, 5,
+ 0, 5, 4,
+};
+// clang-format on
+
+//-----------------------------------------------------------------------------
+// [SECTION] Sphere
+//-----------------------------------------------------------------------------
+
+ImPlot3DPoint sphere_vtx[SPHERE_VTX_COUNT] = {
+ {-0.525731f, 0.850651f, 0.000000f},
+ {0.525731f, 0.850651f, 0.000000f},
+ {-0.525731f, -0.850651f, 0.000000f},
+ {0.525731f, -0.850651f, 0.000000f},
+ {0.000000f, -0.525731f, 0.850651f},
+ {0.000000f, 0.525731f, 0.850651f},
+ {0.000000f, -0.525731f, -0.850651f},
+ {0.000000f, 0.525731f, -0.850651f},
+ {0.850651f, 0.000000f, -0.525731f},
+ {0.850651f, 0.000000f, 0.525731f},
+ {-0.850651f, 0.000000f, -0.525731f},
+ {-0.850651f, 0.000000f, 0.525731f},
+ {-0.809017f, 0.500000f, 0.309017f},
+ {-0.500000f, 0.309017f, 0.809017f},
+ {-0.309017f, 0.809017f, 0.500000f},
+ {0.309017f, 0.809017f, 0.500000f},
+ {0.000000f, 1.000000f, 0.000000f},
+ {0.309017f, 0.809017f, -0.500000f},
+ {-0.309017f, 0.809017f, -0.500000f},
+ {-0.500000f, 0.309017f, -0.809017f},
+ {-0.809017f, 0.500000f, -0.309017f},
+ {-1.000000f, 0.000000f, 0.000000f},
+ {0.500000f, 0.309017f, 0.809017f},
+ {0.809017f, 0.500000f, 0.309017f},
+ {-0.500000f, -0.309017f, 0.809017f},
+ {0.000000f, 0.000000f, 1.000000f},
+ {-0.809017f, -0.500000f, -0.309017f},
+ {-0.809017f, -0.500000f, 0.309017f},
+ {0.000000f, 0.000000f, -1.000000f},
+ {-0.500000f, -0.309017f, -0.809017f},
+ {0.809017f, 0.500000f, -0.309017f},
+ {0.500000f, 0.309017f, -0.809017f},
+ {0.809017f, -0.500000f, 0.309017f},
+ {0.500000f, -0.309017f, 0.809017f},
+ {0.309017f, -0.809017f, 0.500000f},
+ {-0.309017f, -0.809017f, 0.500000f},
+ {0.000000f, -1.000000f, 0.000000f},
+ {-0.309017f, -0.809017f, -0.500000f},
+ {0.309017f, -0.809017f, -0.500000f},
+ {0.500000f, -0.309017f, -0.809017f},
+ {0.809017f, -0.500000f, -0.309017f},
+ {1.000000f, 0.000000f, 0.000000f},
+ {-0.693780f, 0.702046f, 0.160622f},
+ {-0.587785f, 0.688191f, 0.425325f},
+ {-0.433889f, 0.862668f, 0.259892f},
+ {-0.862668f, 0.259892f, 0.433889f},
+ {-0.702046f, 0.160622f, 0.693780f},
+ {-0.688191f, 0.425325f, 0.587785f},
+ {-0.425325f, 0.587785f, 0.688191f},
+ {-0.259892f, 0.433889f, 0.862668f},
+ {-0.160622f, 0.693780f, 0.702046f},
+ {-0.162460f, 0.951057f, 0.262866f},
+ {-0.273267f, 0.961938f, 0.000000f},
+ {0.160622f, 0.693780f, 0.702046f},
+ {0.000000f, 0.850651f, 0.525731f},
+ {0.162460f, 0.951057f, 0.262866f},
+ {0.433889f, 0.862668f, 0.259892f},
+ {0.273267f, 0.961938f, 0.000000f},
+ {-0.162460f, 0.951057f, -0.262866f},
+ {-0.433889f, 0.862668f, -0.259892f},
+ {0.433889f, 0.862668f, -0.259892f},
+ {0.162460f, 0.951057f, -0.262866f},
+ {0.000000f, 0.850651f, -0.525731f},
+ {0.160622f, 0.693780f, -0.702046f},
+ {-0.160622f, 0.693780f, -0.702046f},
+ {-0.587785f, 0.688191f, -0.425325f},
+ {-0.693780f, 0.702046f, -0.160622f},
+ {-0.259892f, 0.433889f, -0.862668f},
+ {-0.425325f, 0.587785f, -0.688191f},
+ {-0.688191f, 0.425325f, -0.587785f},
+ {-0.702046f, 0.160622f, -0.693780f},
+ {-0.862668f, 0.259892f, -0.433889f},
+ {-0.850651f, 0.525731f, 0.000000f},
+ {-0.961938f, 0.000000f, -0.273267f},
+ {-0.951057f, 0.262866f, -0.162460f},
+ {-0.951057f, 0.262866f, 0.162460f},
+ {-0.961938f, 0.000000f, 0.273267f},
+ {0.587785f, 0.688191f, 0.425325f},
+ {0.693780f, 0.702046f, 0.160622f},
+ {0.259892f, 0.433889f, 0.862668f},
+ {0.425325f, 0.587785f, 0.688191f},
+ {0.688191f, 0.425325f, 0.587785f},
+ {0.702046f, 0.160622f, 0.693780f},
+ {0.862668f, 0.259892f, 0.433889f},
+ {-0.262866f, 0.162460f, 0.951057f},
+ {0.000000f, 0.273267f, 0.961938f},
+ {-0.702046f, -0.160622f, 0.693780f},
+ {-0.525731f, 0.000000f, 0.850651f},
+ {-0.262866f, -0.162460f, 0.951057f},
+ {-0.259892f, -0.433889f, 0.862668f},
+ {0.000000f, -0.273267f, 0.961938f},
+ {-0.951057f, -0.262866f, 0.162460f},
+ {-0.862668f, -0.259892f, 0.433889f},
+ {-0.862668f, -0.259892f, -0.433889f},
+ {-0.951057f, -0.262866f, -0.162460f},
+ {-0.850651f, -0.525731f, 0.000000f},
+ {-0.693780f, -0.702046f, -0.160622f},
+ {-0.693780f, -0.702046f, 0.160622f},
+ {-0.525731f, 0.000000f, -0.850651f},
+ {-0.702046f, -0.160622f, -0.693780f},
+ {0.000000f, 0.273267f, -0.961938f},
+ {-0.262866f, 0.162460f, -0.951057f},
+ {-0.262866f, -0.162460f, -0.951057f},
+ {0.000000f, -0.273267f, -0.961938f},
+ {-0.259892f, -0.433889f, -0.862668f},
+ {0.425325f, 0.587785f, -0.688191f},
+ {0.259892f, 0.433889f, -0.862668f},
+ {0.693780f, 0.702046f, -0.160622f},
+ {0.587785f, 0.688191f, -0.425325f},
+ {0.688191f, 0.425325f, -0.587785f},
+ {0.862668f, 0.259892f, -0.433889f},
+ {0.702046f, 0.160622f, -0.693780f},
+ {0.693780f, -0.702046f, 0.160622f},
+ {0.587785f, -0.688191f, 0.425325f},
+ {0.433889f, -0.862668f, 0.259892f},
+ {0.862668f, -0.259892f, 0.433889f},
+ {0.702046f, -0.160622f, 0.693780f},
+ {0.688191f, -0.425325f, 0.587785f},
+ {0.425325f, -0.587785f, 0.688191f},
+ {0.259892f, -0.433889f, 0.862668f},
+ {0.160622f, -0.693780f, 0.702046f},
+ {0.162460f, -0.951057f, 0.262866f},
+ {0.273267f, -0.961938f, 0.000000f},
+ {-0.160622f, -0.693780f, 0.702046f},
+ {0.000000f, -0.850651f, 0.525731f},
+ {-0.162460f, -0.951057f, 0.262866f},
+ {-0.433889f, -0.862668f, 0.259892f},
+ {-0.273267f, -0.961938f, 0.000000f},
+ {0.162460f, -0.951057f, -0.262866f},
+ {0.433889f, -0.862668f, -0.259892f},
+ {-0.433889f, -0.862668f, -0.259892f},
+ {-0.162460f, -0.951057f, -0.262866f},
+ {0.000000f, -0.850651f, -0.525731f},
+ {-0.160622f, -0.693780f, -0.702046f},
+ {0.160622f, -0.693780f, -0.702046f},
+ {0.587785f, -0.688191f, -0.425325f},
+ {0.693780f, -0.702046f, -0.160622f},
+ {0.259892f, -0.433889f, -0.862668f},
+ {0.425325f, -0.587785f, -0.688191f},
+ {0.688191f, -0.425325f, -0.587785f},
+ {0.702046f, -0.160622f, -0.693780f},
+ {0.862668f, -0.259892f, -0.433889f},
+ {0.850651f, -0.525731f, 0.000000f},
+ {0.961938f, 0.000000f, -0.273267f},
+ {0.951057f, -0.262866f, -0.162460f},
+ {0.951057f, -0.262866f, 0.162460f},
+ {0.961938f, 0.000000f, 0.273267f},
+ {0.262866f, -0.162460f, 0.951057f},
+ {0.525731f, 0.000000f, 0.850651f},
+ {0.262866f, 0.162460f, 0.951057f},
+ {-0.587785f, -0.688191f, 0.425325f},
+ {-0.425325f, -0.587785f, 0.688191f},
+ {-0.688191f, -0.425325f, 0.587785f},
+ {-0.425325f, -0.587785f, -0.688191f},
+ {-0.587785f, -0.688191f, -0.425325f},
+ {-0.688191f, -0.425325f, -0.587785f},
+ {0.525731f, 0.000000f, -0.850651f},
+ {0.262866f, -0.162460f, -0.951057f},
+ {0.262866f, 0.162460f, -0.951057f},
+ {0.951057f, 0.262866f, 0.162460f},
+ {0.951057f, 0.262866f, -0.162460f},
+ {0.850651f, 0.525731f, 0.000000f},
+};
+
+unsigned int sphere_idx[SPHERE_IDX_COUNT] = {
+ 0, 42, 44,
+ 42, 12, 43,
+ 44, 43, 14,
+ 42, 43, 44,
+ 12, 45, 47,
+ 45, 11, 46,
+ 47, 46, 13,
+ 45, 46, 47,
+ 14, 48, 50,
+ 48, 13, 49,
+ 50, 49, 5,
+ 48, 49, 50,
+ 12, 47, 43,
+ 47, 13, 48,
+ 43, 48, 14,
+ 47, 48, 43,
+ 0, 44, 52,
+ 44, 14, 51,
+ 52, 51, 16,
+ 44, 51, 52,
+ 14, 50, 54,
+ 50, 5, 53,
+ 54, 53, 15,
+ 50, 53, 54,
+ 16, 55, 57,
+ 55, 15, 56,
+ 57, 56, 1,
+ 55, 56, 57,
+ 14, 54, 51,
+ 54, 15, 55,
+ 51, 55, 16,
+ 54, 55, 51,
+ 0, 52, 59,
+ 52, 16, 58,
+ 59, 58, 18,
+ 52, 58, 59,
+ 16, 57, 61,
+ 57, 1, 60,
+ 61, 60, 17,
+ 57, 60, 61,
+ 18, 62, 64,
+ 62, 17, 63,
+ 64, 63, 7,
+ 62, 63, 64,
+ 16, 61, 58,
+ 61, 17, 62,
+ 58, 62, 18,
+ 61, 62, 58,
+ 0, 59, 66,
+ 59, 18, 65,
+ 66, 65, 20,
+ 59, 65, 66,
+ 18, 64, 68,
+ 64, 7, 67,
+ 68, 67, 19,
+ 64, 67, 68,
+ 20, 69, 71,
+ 69, 19, 70,
+ 71, 70, 10,
+ 69, 70, 71,
+ 18, 68, 65,
+ 68, 19, 69,
+ 65, 69, 20,
+ 68, 69, 65,
+ 0, 66, 42,
+ 66, 20, 72,
+ 42, 72, 12,
+ 66, 72, 42,
+ 20, 71, 74,
+ 71, 10, 73,
+ 74, 73, 21,
+ 71, 73, 74,
+ 12, 75, 45,
+ 75, 21, 76,
+ 45, 76, 11,
+ 75, 76, 45,
+ 20, 74, 72,
+ 74, 21, 75,
+ 72, 75, 12,
+ 74, 75, 72,
+ 1, 56, 78,
+ 56, 15, 77,
+ 78, 77, 23,
+ 56, 77, 78,
+ 15, 53, 80,
+ 53, 5, 79,
+ 80, 79, 22,
+ 53, 79, 80,
+ 23, 81, 83,
+ 81, 22, 82,
+ 83, 82, 9,
+ 81, 82, 83,
+ 15, 80, 77,
+ 80, 22, 81,
+ 77, 81, 23,
+ 80, 81, 77,
+ 5, 49, 85,
+ 49, 13, 84,
+ 85, 84, 25,
+ 49, 84, 85,
+ 13, 46, 87,
+ 46, 11, 86,
+ 87, 86, 24,
+ 46, 86, 87,
+ 25, 88, 90,
+ 88, 24, 89,
+ 90, 89, 4,
+ 88, 89, 90,
+ 13, 87, 84,
+ 87, 24, 88,
+ 84, 88, 25,
+ 87, 88, 84,
+ 11, 76, 92,
+ 76, 21, 91,
+ 92, 91, 27,
+ 76, 91, 92,
+ 21, 73, 94,
+ 73, 10, 93,
+ 94, 93, 26,
+ 73, 93, 94,
+ 27, 95, 97,
+ 95, 26, 96,
+ 97, 96, 2,
+ 95, 96, 97,
+ 21, 94, 91,
+ 94, 26, 95,
+ 91, 95, 27,
+ 94, 95, 91,
+ 10, 70, 99,
+ 70, 19, 98,
+ 99, 98, 29,
+ 70, 98, 99,
+ 19, 67, 101,
+ 67, 7, 100,
+ 101, 100, 28,
+ 67, 100, 101,
+ 29, 102, 104,
+ 102, 28, 103,
+ 104, 103, 6,
+ 102, 103, 104,
+ 19, 101, 98,
+ 101, 28, 102,
+ 98, 102, 29,
+ 101, 102, 98,
+ 7, 63, 106,
+ 63, 17, 105,
+ 106, 105, 31,
+ 63, 105, 106,
+ 17, 60, 108,
+ 60, 1, 107,
+ 108, 107, 30,
+ 60, 107, 108,
+ 31, 109, 111,
+ 109, 30, 110,
+ 111, 110, 8,
+ 109, 110, 111,
+ 17, 108, 105,
+ 108, 30, 109,
+ 105, 109, 31,
+ 108, 109, 105,
+ 3, 112, 114,
+ 112, 32, 113,
+ 114, 113, 34,
+ 112, 113, 114,
+ 32, 115, 117,
+ 115, 9, 116,
+ 117, 116, 33,
+ 115, 116, 117,
+ 34, 118, 120,
+ 118, 33, 119,
+ 120, 119, 4,
+ 118, 119, 120,
+ 32, 117, 113,
+ 117, 33, 118,
+ 113, 118, 34,
+ 117, 118, 113,
+ 3, 114, 122,
+ 114, 34, 121,
+ 122, 121, 36,
+ 114, 121, 122,
+ 34, 120, 124,
+ 120, 4, 123,
+ 124, 123, 35,
+ 120, 123, 124,
+ 36, 125, 127,
+ 125, 35, 126,
+ 127, 126, 2,
+ 125, 126, 127,
+ 34, 124, 121,
+ 124, 35, 125,
+ 121, 125, 36,
+ 124, 125, 121,
+ 3, 122, 129,
+ 122, 36, 128,
+ 129, 128, 38,
+ 122, 128, 129,
+ 36, 127, 131,
+ 127, 2, 130,
+ 131, 130, 37,
+ 127, 130, 131,
+ 38, 132, 134,
+ 132, 37, 133,
+ 134, 133, 6,
+ 132, 133, 134,
+ 36, 131, 128,
+ 131, 37, 132,
+ 128, 132, 38,
+ 131, 132, 128,
+ 3, 129, 136,
+ 129, 38, 135,
+ 136, 135, 40,
+ 129, 135, 136,
+ 38, 134, 138,
+ 134, 6, 137,
+ 138, 137, 39,
+ 134, 137, 138,
+ 40, 139, 141,
+ 139, 39, 140,
+ 141, 140, 8,
+ 139, 140, 141,
+ 38, 138, 135,
+ 138, 39, 139,
+ 135, 139, 40,
+ 138, 139, 135,
+ 3, 136, 112,
+ 136, 40, 142,
+ 112, 142, 32,
+ 136, 142, 112,
+ 40, 141, 144,
+ 141, 8, 143,
+ 144, 143, 41,
+ 141, 143, 144,
+ 32, 145, 115,
+ 145, 41, 146,
+ 115, 146, 9,
+ 145, 146, 115,
+ 40, 144, 142,
+ 144, 41, 145,
+ 142, 145, 32,
+ 144, 145, 142,
+ 4, 119, 90,
+ 119, 33, 147,
+ 90, 147, 25,
+ 119, 147, 90,
+ 33, 116, 148,
+ 116, 9, 82,
+ 148, 82, 22,
+ 116, 82, 148,
+ 25, 149, 85,
+ 149, 22, 79,
+ 85, 79, 5,
+ 149, 79, 85,
+ 33, 148, 147,
+ 148, 22, 149,
+ 147, 149, 25,
+ 148, 149, 147,
+ 2, 126, 97,
+ 126, 35, 150,
+ 97, 150, 27,
+ 126, 150, 97,
+ 35, 123, 151,
+ 123, 4, 89,
+ 151, 89, 24,
+ 123, 89, 151,
+ 27, 152, 92,
+ 152, 24, 86,
+ 92, 86, 11,
+ 152, 86, 92,
+ 35, 151, 150,
+ 151, 24, 152,
+ 150, 152, 27,
+ 151, 152, 150,
+ 6, 133, 104,
+ 133, 37, 153,
+ 104, 153, 29,
+ 133, 153, 104,
+ 37, 130, 154,
+ 130, 2, 96,
+ 154, 96, 26,
+ 130, 96, 154,
+ 29, 155, 99,
+ 155, 26, 93,
+ 99, 93, 10,
+ 155, 93, 99,
+ 37, 154, 153,
+ 154, 26, 155,
+ 153, 155, 29,
+ 154, 155, 153,
+ 8, 140, 111,
+ 140, 39, 156,
+ 111, 156, 31,
+ 140, 156, 111,
+ 39, 137, 157,
+ 137, 6, 103,
+ 157, 103, 28,
+ 137, 103, 157,
+ 31, 158, 106,
+ 158, 28, 100,
+ 106, 100, 7,
+ 158, 100, 106,
+ 39, 157, 156,
+ 157, 28, 158,
+ 156, 158, 31,
+ 157, 158, 156,
+ 9, 146, 83,
+ 146, 41, 159,
+ 83, 159, 23,
+ 146, 159, 83,
+ 41, 143, 160,
+ 143, 8, 110,
+ 160, 110, 30,
+ 143, 110, 160,
+ 23, 161, 78,
+ 161, 30, 107,
+ 78, 107, 1,
+ 161, 107, 78,
+ 41, 160, 159,
+ 160, 30, 161,
+ 159, 161, 23,
+ 160, 161, 159};
+
+//-----------------------------------------------------------------------------
+// [SECTION] Duck
+//-----------------------------------------------------------------------------
+// Rubber Duck by Poly by Google [CC-BY] via Poly Pizza
+
+ImPlot3DPoint duck_vtx[DUCK_VTX_COUNT] = {
+ {0.263370f, 0.782383f, -0.488235f},
+ {0.358677f, -0.624396f, -0.546179f},
+ {0.292714f, 0.782558f, -0.125229f},
+ {0.349165f, -0.579430f, -0.151545f},
+ {0.000000f, 0.985995f, 0.055503f},
+ {0.000000f, -0.771795f, -0.060755f},
+ {0.000000f, 0.884807f, -0.562217f},
+ {0.000000f, -0.793724f, -0.609042f},
+ {0.000000f, -0.299071f, -0.806689f},
+ {0.416629f, -0.234865f, -0.718575f},
+ {0.386268f, -0.264259f, -0.087117f},
+ {0.000000f, -0.154521f, 0.030419f},
+ {0.170659f, -0.255441f, 0.007066f},
+ {0.180454f, -0.704124f, -0.061920f},
+ {0.155948f, 0.918253f, -0.001556f},
+ {0.155050f, -0.228032f, 0.751753f},
+ {0.151757f, -0.513087f, 0.744284f},
+ {0.000000f, -0.182899f, 0.779477f},
+ {0.000000f, -0.578330f, 0.764610f},
+ {0.000000f, 0.685169f, -0.739686f},
+ {0.371476f, 0.629238f, -0.627667f},
+ {0.331315f, 0.641641f, -0.175468f},
+ {0.206943f, 0.597766f, -0.089039f},
+ {0.000000f, 0.614709f, -0.054922f},
+ {0.000000f, -0.737692f, 0.299441f},
+ {0.283809f, -0.102660f, 0.299441f},
+ {0.000000f, 0.014558f, 0.299441f},
+ {0.000000f, -0.736425f, 0.474106f},
+ {0.278490f, -0.087547f, 0.474106f},
+ {0.000000f, 0.025780f, 0.474106f},
+ {0.164839f, 0.864781f, -0.528560f},
+ {0.230536f, 0.696343f, -0.704629f},
+ {0.220279f, -0.294500f, -0.788924f},
+ {0.216836f, -0.741049f, -0.579700f},
+ {0.238591f, -0.660227f, 0.299441f},
+ {0.244763f, -0.660211f, 0.474106f},
+ {0.000000f, -0.363444f, 0.812572f},
+ {0.163341f, -0.354552f, 0.773703f},
+ {0.374463f, -0.372350f, 0.474106f},
+ {0.379897f, -0.380938f, 0.299441f},
+ {0.289253f, -0.469584f, -0.048850f},
+ {0.399568f, -0.463659f, -0.141744f},
+ {0.416824f, -0.506064f, -0.619739f},
+ {0.226948f, -0.580280f, -0.719311f},
+ {0.000000f, -0.596101f, -0.755098f},
+ {0.199617f, 0.050031f, -0.023613f},
+ {0.000000f, 0.056331f, -0.812572f},
+ {0.221169f, 0.056088f, -0.800430f},
+ {0.414907f, 0.057214f, -0.741319f},
+ {0.383278f, 0.079881f, -0.106452f},
+ {0.000000f, 0.066068f, 0.011448f},
+ {0.485071f, -0.225034f, -0.615697f},
+ {0.474322f, -0.235931f, -0.130497f},
+ {0.451926f, -0.378909f, -0.182230f},
+ {0.464485f, -0.423127f, -0.533935f},
+ {0.376105f, 0.621725f, -0.201967f},
+ {0.458732f, 0.078611f, -0.144688f},
+ {0.414064f, 0.587520f, -0.543056f},
+ {0.482586f, 0.065612f, -0.653211f},
+ {0.000000f, -0.672915f, 0.096026f},
+ {0.146952f, -0.607627f, 0.096026f},
+ {0.261607f, -0.401487f, 0.096026f},
+ {0.193018f, -0.190607f, 0.096026f},
+ {0.000000f, -0.095407f, 0.096026f},
+ {0.000000f, -0.044418f, 0.665235f},
+ {0.241792f, -0.132120f, 0.640095f},
+ {0.312765f, -0.375473f, 0.640095f},
+ {0.214627f, -0.620202f, 0.640095f},
+ {0.000000f, -0.694664f, 0.658020f},
+ {0.000000f, -0.682358f, 0.030419f},
+ {0.138512f, -0.610831f, 0.030419f},
+ {0.234026f, -0.438399f, 0.030419f},
+ {0.000000f, 0.914951f, 0.077360f},
+ {0.160964f, 0.841809f, 0.024144f},
+ {0.193354f, 0.741696f, -0.037907f},
+ {0.000000f, 0.800313f, 0.001882f},
+ {0.000000f, 0.405613f, -0.811128f},
+ {0.229117f, 0.399656f, -0.791482f},
+ {0.409787f, 0.383452f, -0.704163f},
+ {0.458770f, 0.376363f, -0.605461f},
+ {0.385168f, 0.580561f, -0.185704f},
+ {0.344420f, 0.600331f, -0.166386f},
+ {0.437609f, 0.339612f, -0.230592f},
+ {0.386172f, 0.365055f, -0.180691f},
+ {0.203357f, 0.342793f, -0.082669f},
+ {0.000000f, 0.367574f, -0.043515f},
+ {0.368402f, 0.509624f, -0.189577f},
+ {0.408311f, 0.494489f, -0.224777f},
+ {0.317982f, -0.276765f, -0.036125f},
+ {0.000000f, -0.705078f, 0.171934f},
+ {0.194647f, -0.634647f, 0.171934f},
+ {0.332827f, -0.390756f, 0.171934f},
+ {0.252044f, -0.153800f, 0.171934f},
+ {0.000000f, -0.052325f, 0.171934f},
+ {0.000000f, -0.845971f, -0.319676f},
+ {0.204859f, -0.774310f, -0.326206f},
+ {0.382866f, -0.635409f, -0.351147f},
+ {0.437191f, -0.532057f, -0.362802f},
+ {0.277419f, 0.820517f, -0.362410f},
+ {0.165994f, 0.946464f, -0.306175f},
+ {0.366520f, 0.694046f, -0.383403f},
+ {0.000000f, 1.000000f, -0.305615f},
+ {0.554754f, 0.081200f, -0.400087f},
+ {0.548457f, -0.240710f, -0.371820f},
+ {0.496187f, -0.443697f, -0.357878f},
+ {0.410754f, 0.655281f, -0.382353f},
+ {0.501407f, 0.340701f, -0.406753f},
+ {-0.263370f, 0.782383f, -0.488235f},
+ {-0.358677f, -0.624396f, -0.546179f},
+ {-0.292714f, 0.782558f, -0.125229f},
+ {-0.349165f, -0.579430f, -0.151545f},
+ {-0.416629f, -0.234865f, -0.718575f},
+ {-0.386268f, -0.264259f, -0.087117f},
+ {-0.170659f, -0.255441f, 0.007066f},
+ {-0.180454f, -0.704124f, -0.061920f},
+ {-0.155948f, 0.918253f, -0.001556f},
+ {-0.155050f, -0.228032f, 0.751753f},
+ {-0.151757f, -0.513087f, 0.744284f},
+ {-0.371476f, 0.629238f, -0.627667f},
+ {-0.331315f, 0.641641f, -0.175468f},
+ {-0.206943f, 0.597766f, -0.089039f},
+ {-0.283809f, -0.102660f, 0.299441f},
+ {-0.278490f, -0.087547f, 0.474106f},
+ {-0.164839f, 0.864781f, -0.528560f},
+ {-0.230536f, 0.696343f, -0.704629f},
+ {-0.220279f, -0.294500f, -0.788924f},
+ {-0.216836f, -0.741049f, -0.579700f},
+ {-0.238591f, -0.660227f, 0.299441f},
+ {-0.244763f, -0.660211f, 0.474106f},
+ {-0.163341f, -0.354552f, 0.773703f},
+ {-0.374463f, -0.372350f, 0.474106f},
+ {-0.379897f, -0.380938f, 0.299441f},
+ {-0.289253f, -0.469584f, -0.048850f},
+ {-0.399568f, -0.463659f, -0.141744f},
+ {-0.416824f, -0.506064f, -0.619739f},
+ {-0.226948f, -0.580280f, -0.719311f},
+ {-0.199617f, 0.050031f, -0.023613f},
+ {-0.221169f, 0.056088f, -0.800430f},
+ {-0.414907f, 0.057214f, -0.741319f},
+ {-0.383278f, 0.079881f, -0.106452f},
+ {-0.485071f, -0.225034f, -0.615697f},
+ {-0.474322f, -0.235931f, -0.130497f},
+ {-0.451926f, -0.378909f, -0.182230f},
+ {-0.464485f, -0.423127f, -0.533935f},
+ {-0.376105f, 0.621725f, -0.201967f},
+ {-0.458732f, 0.078611f, -0.144688f},
+ {-0.414064f, 0.587520f, -0.543056f},
+ {-0.482586f, 0.065612f, -0.653211f},
+ {-0.146952f, -0.607627f, 0.096026f},
+ {-0.261606f, -0.401487f, 0.096026f},
+ {-0.193018f, -0.190607f, 0.096026f},
+ {-0.241792f, -0.132120f, 0.640095f},
+ {-0.312765f, -0.375473f, 0.640095f},
+ {-0.214627f, -0.620202f, 0.640095f},
+ {-0.138512f, -0.610831f, 0.030419f},
+ {-0.234026f, -0.438399f, 0.030419f},
+ {-0.160964f, 0.841809f, 0.024144f},
+ {-0.193354f, 0.741696f, -0.037907f},
+ {-0.229117f, 0.399656f, -0.791482f},
+ {-0.409787f, 0.383452f, -0.704163f},
+ {-0.458770f, 0.376363f, -0.605461f},
+ {-0.385168f, 0.580561f, -0.185704f},
+ {-0.344420f, 0.600331f, -0.166386f},
+ {-0.437609f, 0.339612f, -0.230592f},
+ {-0.386172f, 0.365055f, -0.180691f},
+ {-0.203357f, 0.342793f, -0.082669f},
+ {-0.368402f, 0.509624f, -0.189577f},
+ {-0.408311f, 0.494489f, -0.224777f},
+ {-0.317982f, -0.276765f, -0.036125f},
+ {-0.194647f, -0.634647f, 0.171934f},
+ {-0.332827f, -0.390756f, 0.171934f},
+ {-0.252044f, -0.153800f, 0.171934f},
+ {-0.204859f, -0.774310f, -0.326206f},
+ {-0.382866f, -0.635409f, -0.351147f},
+ {-0.437191f, -0.532057f, -0.362802f},
+ {-0.277419f, 0.820517f, -0.362410f},
+ {-0.165994f, 0.946464f, -0.306175f},
+ {-0.366520f, 0.694046f, -0.383403f},
+ {-0.554754f, 0.081200f, -0.400087f},
+ {-0.548457f, -0.240710f, -0.371820f},
+ {-0.496187f, -0.443697f, -0.357878f},
+ {-0.410754f, 0.655281f, -0.382353f},
+ {-0.501407f, 0.340701f, -0.406753f},
+ {-0.147251f, -0.668523f, 0.330326f},
+ {-0.047291f, -0.930817f, 0.472716f},
+ {-0.224222f, -0.665336f, 0.415767f},
+ {-0.075450f, -0.918607f, 0.533818f},
+ {-0.075797f, -0.674119f, 0.541030f},
+ {-0.096675f, -0.818748f, 0.395991f},
+ {-0.159031f, -0.798414f, 0.462927f},
+ {-0.223648f, -0.656683f, 0.331961f},
+ {-0.072544f, -0.935279f, 0.500884f},
+ {0.000000f, -0.961081f, 0.579077f},
+ {0.000000f, -0.726138f, 0.328549f},
+ {0.000000f, -0.969221f, 0.509585f},
+ {-0.176269f, -0.669386f, 0.471817f},
+ {-0.051675f, -0.862571f, 0.538304f},
+ {0.000000f, -0.680106f, 0.554423f},
+ {0.000000f, -0.974061f, 0.546160f},
+ {-0.152228f, -0.819158f, 0.418117f},
+ {0.000000f, -0.867770f, 0.561794f},
+ {0.000000f, -0.827887f, 0.404080f},
+ {-0.123466f, -0.788030f, 0.489190f},
+ {0.147251f, -0.668523f, 0.330326f},
+ {0.047291f, -0.930817f, 0.472716f},
+ {0.224222f, -0.665336f, 0.415767f},
+ {0.075450f, -0.918607f, 0.533818f},
+ {0.075797f, -0.674119f, 0.541030f},
+ {0.096675f, -0.818748f, 0.395991f},
+ {0.159031f, -0.798414f, 0.462927f},
+ {0.223648f, -0.656683f, 0.331961f},
+ {0.072544f, -0.935279f, 0.500884f},
+ {0.176269f, -0.669386f, 0.471817f},
+ {0.051675f, -0.862571f, 0.538304f},
+ {0.152228f, -0.819158f, 0.418117f},
+ {0.123466f, -0.788030f, 0.489190f},
+ {0.000000f, -0.994773f, 0.345730f},
+ {0.000000f, -0.647443f, 0.199203f},
+ {0.000000f, -0.986165f, 0.291566f},
+ {0.000000f, -0.703971f, 0.341994f},
+ {0.000000f, -1.000000f, 0.315210f},
+ {0.000000f, -0.787658f, 0.226787f},
+ {0.000000f, -0.961085f, 0.352303f},
+ {-0.133409f, -0.653443f, 0.222877f},
+ {-0.060090f, -0.929363f, 0.267589f},
+ {-0.224222f, -0.658853f, 0.342353f},
+ {-0.094843f, -0.932646f, 0.343060f},
+ {-0.151330f, -0.675024f, 0.346911f},
+ {0.000000f, -0.932532f, 0.323403f},
+ {-0.120579f, -0.791706f, 0.239478f},
+ {-0.187682f, -0.791724f, 0.341367f},
+ {-0.213355f, -0.651997f, 0.277443f},
+ {-0.092940f, -0.939719f, 0.301090f},
+ {-0.176269f, -0.666860f, 0.352303f},
+ {-0.130777f, -0.793601f, 0.328226f},
+ {-0.075297f, -0.913539f, 0.352303f},
+ {-0.182747f, -0.797038f, 0.284545f},
+ {-0.150873f, -0.786911f, 0.352303f},
+ {-0.055104f, -0.898766f, 0.325273f},
+ {0.133409f, -0.653443f, 0.222877f},
+ {0.060090f, -0.929363f, 0.267589f},
+ {0.224222f, -0.658853f, 0.342353f},
+ {0.094843f, -0.932646f, 0.343060f},
+ {0.151330f, -0.675024f, 0.346911f},
+ {0.120579f, -0.791706f, 0.239478f},
+ {0.187682f, -0.791724f, 0.341367f},
+ {0.213355f, -0.651997f, 0.277443f},
+ {0.092940f, -0.939719f, 0.301090f},
+ {0.176269f, -0.666860f, 0.352303f},
+ {0.130777f, -0.793601f, 0.328226f},
+ {0.075297f, -0.913539f, 0.352303f},
+ {0.182747f, -0.797038f, 0.284545f},
+ {0.150873f, -0.786911f, 0.352303f},
+ {0.055104f, -0.898766f, 0.325273f}};
+
+unsigned int duck_idx[DUCK_IDX_COUNT] = {
+ 2, 73, 74, 2, 74, 21, 30, 0, 20, 30, 20, 31, 43, 42, 1, 43, 1, 33, 41, 40, 13, 41, 13, 3, 40, 71, 70, 40, 70, 13, 48, 9, 32, 48, 32, 47, 45, 88, 10, 45, 10, 49, 39, 38, 35, 39, 35, 34, 66, 37, 16, 66, 16, 67, 6, 30, 31, 6, 31, 19, 46, 47, 32, 46, 32, 8, 44, 43, 33, 44, 33, 7, 70, 69, 5, 70, 5, 13, 35, 27, 24, 35, 24, 34, 16, 18, 68, 16, 68, 67, 36, 18, 16, 36, 16, 37, 64, 17, 15, 64, 15, 65, 26, 29, 28, 26, 28, 25, 50, 11, 12, 50, 12, 45, 72, 75, 74, 72, 74, 73, 17, 36, 37, 17, 37, 15, 65, 15, 37, 65, 37, 66, 25, 28, 38, 25, 38, 39, 40, 88, 12, 40, 12, 71, 10, 88, 40, 10, 40, 41, 32, 9, 42, 32, 42, 43, 8, 32, 43, 8, 43, 44, 85, 84, 22, 85, 22, 23, 20, 78, 77, 20, 77, 31, 19, 31, 77, 19, 77, 76, 48, 58, 51, 48, 51, 9, 10, 52, 56, 10, 56, 49, 41, 53, 52, 41, 52, 10, 9, 51, 54, 9, 54, 42, 81, 80, 55, 81, 55, 21, 20, 57, 79, 20, 79, 78, 90, 89, 59, 90, 59, 60, 61, 91, 90, 61, 90, 60, 62, 92, 91, 62, 91, 61, 63, 93, 92, 63, 92, 62, 29, 64, 65, 29, 65, 28, 28, 65, 66, 28, 66, 38, 38, 66, 67, 38, 67, 35, 68, 27, 35, 68, 35, 67, 60, 59, 69, 60, 69, 70, 71, 61, 60, 71, 60, 70, 12, 62, 61, 12, 61, 71, 11, 63, 62, 11, 62, 12, 14, 4, 72, 14, 72, 73, 2, 14, 73, 22, 81, 21, 22, 21, 74, 75, 23, 22, 75, 22, 74, 83, 82, 87, 83, 87, 86, 76, 77, 47, 76, 47, 46, 78, 48, 47, 78, 47, 77, 79, 58, 48, 79, 48, 78, 49, 56, 82, 49, 82, 83, 84, 45, 49, 84, 49, 83, 85, 50, 45, 85, 45, 84, 87, 80, 81, 87, 81, 86, 22, 84, 83, 22, 83, 86, 45, 12, 88, 34, 24, 89, 34, 89, 90, 91, 39, 34, 91, 34, 90, 92, 25, 39, 92, 39, 91, 93, 26, 25, 93, 25, 92, 0, 98, 100, 0, 100, 20, 1, 96, 95, 1, 95, 33, 30, 99, 98, 30, 98, 0, 97, 96, 1, 97, 1, 42, 96, 3, 13, 96, 13, 95, 41, 3, 96, 41, 96, 97, 98, 2, 21, 98, 21, 100, 99, 14, 2, 99, 2, 98, 102, 103, 51, 102, 51, 58, 56, 52, 103, 56, 103, 102, 101, 4, 14, 101, 14, 99, 6, 101, 99, 6, 99, 30, 95, 94, 7, 95, 7, 33, 13, 5, 94, 13, 94, 95, 52, 53, 104, 52, 104, 103, 103, 104, 54, 103, 54, 51, 105, 106, 79, 105, 79, 57, 97, 104, 53, 97, 53, 41, 42, 54, 104, 42, 104, 97, 21, 55, 105, 21, 105, 100, 100, 105, 57, 100, 57, 20, 106, 105, 87, 106, 87, 82, 106, 102, 58, 106, 58, 79, 82, 56, 102, 82, 102, 106, 22, 86, 81, 80, 87, 105, 80, 105, 55, 109, 119, 157, 109, 157, 156, 123, 124, 118, 123, 118, 107, 135, 126, 108, 135, 108, 134, 133, 110, 114, 133, 114, 132, 132, 114, 154, 132, 154, 155, 138, 137, 125, 138, 125, 111, 136, 139, 112, 136, 112, 168, 131, 127, 128, 131, 128, 130, 152, 153, 117, 152, 117, 129, 6, 19, 124, 6, 124, 123, 46, 8, 125, 46, 125, 137, 44, 7, 126, 44, 126, 135, 154, 114, 5, 154, 5, 69, 128, 127, 24, 128, 24, 27, 117, 153, 68, 117, 68, 18, 36, 129, 117, 36, 117, 18, 64, 151, 116, 64, 116, 17, 26, 121, 122, 26, 122, 29, 50, 136, 113, 50, 113, 11, 72, 156, 157, 72, 157, 75, 17, 116, 129, 17, 129, 36, 151, 152, 129, 151, 129, 116, 121, 131, 130, 121, 130, 122, 132, 155, 113, 132, 113, 168, 112, 133, 132, 112, 132, 168, 125, 135, 134, 125, 134, 111, 8, 44, 135, 8, 135, 125, 85, 23, 120, 85, 120, 165, 118, 124, 158, 118, 158, 159, 19, 76, 158, 19, 158, 124, 138, 111, 140, 138, 140, 147, 112, 139, 145, 112, 145, 141, 133, 112, 141, 133, 141, 142, 111, 134, 143, 111, 143, 140, 162, 119, 144, 162, 144, 161, 118, 159, 160, 118, 160, 146, 169, 148, 59, 169, 59, 89, 149, 148, 169, 149, 169, 170, 150, 149, 170, 150, 170, 171, 63, 150, 171, 63, 171, 93, 29, 122, 151, 29, 151, 64, 122, 130, 152, 122, 152, 151, 130, 128, 153, 130, 153, 152, 68, 153, 128, 68, 128, 27, 148, 154, 69, 148, 69, 59, 155, 154, 148, 155, 148, 149, 113, 155, 149, 113, 149, 150, 11, 113, 150, 11, 150, 63, 115, 156, 72, 115, 72, 4, 109, 156, 115, 120, 157, 119, 120, 119, 162, 75, 157, 120, 75, 120, 23, 164, 166, 167, 164, 167, 163, 76, 46, 137, 76, 137, 158, 159, 158, 137, 159, 137, 138, 160, 159, 138, 160, 138, 147, 139, 164, 163, 139, 163, 145, 165, 164, 139, 165, 139, 136, 85, 165, 136, 85, 136, 50, 167, 166, 162, 167, 162, 161, 120, 166, 164, 120, 164, 165, 136, 168, 113, 127, 169, 89, 127, 89, 24, 170, 169, 127, 170, 127, 131, 171, 170, 131, 171, 131, 121, 93, 171, 121, 93, 121, 26, 107, 118, 177, 107, 177, 175, 108, 126, 172, 108, 172, 173, 123, 107, 175, 123, 175, 176, 174, 134, 108, 174, 108, 173, 173, 172, 114, 173, 114, 110, 133, 174, 173, 133, 173, 110, 175, 177, 119, 175, 119, 109, 176, 175, 109, 176, 109, 115, 178, 147, 140, 178, 140, 179, 145, 178, 179, 145, 179, 141, 101, 176, 115, 101, 115, 4, 6, 123, 176, 6, 176, 101, 172, 126, 7, 172, 7, 94, 114, 172, 94, 114, 94, 5, 141, 179, 180, 141, 180, 142, 179, 140, 143, 179, 143, 180, 181, 146, 160, 181, 160, 182, 174, 133, 142, 174, 142, 180, 134, 174, 180, 134, 180, 143, 119, 177, 181, 119, 181, 144, 177, 118, 146, 177, 146, 181, 182, 163, 167, 182, 167, 181, 182, 160, 147, 182, 147, 178, 163, 182, 178, 163, 178, 145, 120, 162, 166, 161, 144, 181, 161, 181, 167, 189, 185, 190, 189, 190, 199, 190, 183, 188, 190, 188, 199, 197, 187, 196, 197, 196, 200, 188, 183, 193, 188, 193, 201, 192, 186, 191, 192, 191, 198, 191, 184, 194, 191, 194, 198, 188, 184, 191, 188, 191, 199, 191, 186, 189, 191, 189, 199, 194, 184, 188, 194, 188, 201, 196, 187, 195, 196, 195, 202, 195, 185, 189, 195, 189, 202, 189, 186, 196, 189, 196, 202, 196, 186, 192, 196, 192, 200, 209, 214, 210, 209, 210, 205, 210, 214, 208, 210, 208, 203, 197, 200, 213, 197, 213, 207, 208, 201, 193, 208, 193, 203, 192, 198, 211, 192, 211, 206, 211, 198, 194, 211, 194, 204, 208, 214, 211, 208, 211, 204, 211, 214, 209, 211, 209, 206, 194, 201, 208, 194, 208, 204, 213, 215, 212, 213, 212, 207, 212, 215, 209, 212, 209, 205, 209, 215, 213, 209, 213, 206, 213, 200, 192, 213, 192, 206, 230, 225, 231, 230, 231, 236, 231, 223, 229, 231, 229, 236, 219, 227, 234, 229, 223, 217, 229, 217, 221, 216, 226, 232, 216, 232, 220, 232, 224, 218, 232, 218, 220, 229, 224, 232, 229, 232, 236, 232, 226, 230, 232, 230, 236, 234, 238, 228, 234, 228, 219, 218, 224, 229, 218, 229, 221, 234, 227, 233, 234, 233, 237, 233, 225, 230, 233, 230, 237, 230, 226, 235, 230, 235, 237, 235, 238, 234, 235, 234, 237, 228, 238, 235, 228, 235, 222, 235, 226, 216, 235, 216, 222, 245, 251, 246, 245, 246, 241, 246, 251, 244, 246, 244, 239, 219, 249, 243, 244, 221, 217, 244, 217, 239, 216, 220, 247, 216, 247, 242, 247, 220, 218, 247, 218, 240, 244, 251, 247, 244, 247, 240, 247, 251, 245, 247, 245, 242, 249, 219, 228, 249, 228, 253, 218, 221, 244, 218, 244, 240, 249, 252, 248, 249, 248, 243, 248, 252, 245, 248, 245, 241, 245, 252, 250, 245, 250, 242, 250, 252, 249, 250, 249, 253, 228, 222, 250, 228, 250, 253, 250, 222, 216, 250, 216, 242};
+
+} // namespace ImPlot3D
diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp
index c7737eda9..7daaf39e0 100644
--- a/main/gui/source/window/window.cpp
+++ b/main/gui/source/window/window.cpp
@@ -30,6 +30,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -955,9 +957,10 @@ namespace hex {
}
// Initialize ImGui and all other ImGui extensions
- GImGui = ImGui::CreateContext(fonts);
- GImPlot = ImPlot::CreateContext();
- GImNodes = ImNodes::CreateContext();
+ GImGui = ImGui::CreateContext(fonts);
+ GImPlot = ImPlot::CreateContext();
+ ImPlot3D::GImPlot3D = ImPlot3D::CreateContext();
+ GImNodes = ImNodes::CreateContext();
ImGuiIO &io = ImGui::GetIO();
ImGuiStyle &style = ImGui::GetStyle();
@@ -1054,6 +1057,7 @@ namespace hex {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
+ ImPlot3D::DestroyContext();
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
diff --git a/plugins/builtin/source/content/ui_items.cpp b/plugins/builtin/source/content/ui_items.cpp
index 107dc7f3d..b20c6bd9c 100644
--- a/plugins/builtin/source/content/ui_items.cpp
+++ b/plugins/builtin/source/content/ui_items.cpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include
#include
@@ -54,6 +55,7 @@ namespace hex::plugin::builtin {
static void drawDebugPopup() {
static bool showImGuiDemo = false;
static bool showImPlotDemo = false;
+ static bool showImPlot3DDemo = false;
ImGui::SetNextWindowSize(scaled({ 300, 150 }), ImGuiCond_Always);
if (ImGui::BeginPopup("DebugMenu")) {
@@ -76,6 +78,7 @@ namespace hex::plugin::builtin {
auto ctx = ImGui::GetCurrentContext();
ImGui::Checkbox("Show ImGui Demo", &showImGuiDemo);
ImGui::Checkbox("Show ImPlot Demo", &showImPlotDemo);
+ ImGui::Checkbox("Show ImPlot3D Demo", &showImPlot3DDemo);
if (ImGui::Button("Trigger Breakpoint in Item") || ctx->DebugItemPickerActive)
ImGui::DebugStartItemPicker();
@@ -125,6 +128,8 @@ namespace hex::plugin::builtin {
ImGui::ShowDemoWindow(&showImGuiDemo);
if (showImPlotDemo)
ImPlot::ShowDemoWindow(&showImPlotDemo);
+ if (showImPlot3DDemo)
+ ImPlot3D::ShowDemoWindow(&showImPlot3DDemo);
}
#endif