From ae10a40f9e8c45beba8899f4a5659a8231586c39 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:20:01 +0200 Subject: [PATCH 01/15] Added a "simplified" PlotSurface that only takes in one array. - The minor and the major parameters for the rest of the plot needs to be specified. These parameters include stride, offset and the boundary to use - By default using `ImPlot3D::PlotSurface("Plot", z_values, num_x, num_y)` should work perfectly to produce a z-plot with just z_values - Most of the parameters can be left as default but does allow for more control if desired --- implot3d.h | 7 ++++ implot3d_demo.cpp | 95 +++++++++++++++++++++++++++++++++++++++++++ implot3d_internal.h | 2 + implot3d_items.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 202 insertions(+), 1 deletion(-) diff --git a/implot3d.h b/implot3d.h index d0e9f08..0d8cc17 100644 --- a/implot3d.h +++ b/implot3d.h @@ -37,6 +37,8 @@ #include "imgui.h" #ifndef IMGUI_DISABLE +#include + //----------------------------------------------------------------------------- // [SECTION] Macros and Defines //----------------------------------------------------------------------------- @@ -50,6 +52,7 @@ #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 +#define IMPLOT3D_DEFAULT_MAJOR_STRIDE INT_MAX //----------------------------------------------------------------------------- // [SECTION] Forward declarations and basic types @@ -453,6 +456,10 @@ IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const // to a predefined range IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min = 0.0, double scale_max = 0.0, ImPlot3DSurfaceFlags flags = 0, int offset = 0, int stride = sizeof(T)); +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min = 0.0, double scale_max = 0.0, + const ImVec2& minor_bounds = ImVec2(-1, 1), const ImVec2& major_bounds = ImVec2(-1, 1), ImPlot3DSurfaceFlags flags = 0, + ImAxis3D values_axis = ImAxis3D_Z, ImAxis3D major_axis = ImAxis3D_Y, int minor_offset = 0, int major_offset = 0, + int minor_stride = sizeof(T), int major_stride = IMPLOT3D_DEFAULT_MAJOR_STRIDE); IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0); diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 4e47fe3..c9b4f97 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -397,6 +397,100 @@ void DemoSurfacePlots() { ImPlot3D::PopColormap(); } +void DemoSimplifiedSurfacePlotsOffsetStride() { + constexpr int N = 10; + constexpr int M = 20; + static float values[N * M]; + static float t = 0.0f; + // t += ImGui::GetIO().DeltaTime; + + // Define the range for X and Y + constexpr float min_val = -1.0f; + constexpr float max_val = 1.0f; + constexpr float x_step = (max_val - min_val) / (N - 1); + constexpr float y_step = (max_val - min_val) / (M - 1); + + // Populate the values array + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + int idx = i * M + j; + float x = min_val + j * x_step; + float y = min_val + i * y_step; + values[idx] = ImSin(2 * t + ImSqrt((x * x + y * y))); // value = sin(2t + sqrt(x^2 + y^2)) + } + } + + // Choose range + static bool custom_range = false; + static float range_min = -1.0f; + static float range_max = 1.0f; + + // Determine the + static ImAxis3D values_axis = ImAxis3D_Z; + static ImAxis3D major_axis = ImAxis3D_Y; + if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis\0")) { + // The major and value axis cannot be the same + if (major_axis == values_axis) { + major_axis = (major_axis + 1) % ImAxis3D_COUNT; + } + } + if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis\0")) { + if (major_axis == values_axis) { + values_axis = (values_axis + 1) % ImAxis3D_COUNT; + } + } + // Add offset and stride + static int minor_offset = 0; + static int major_offset = 0; + static int minor_stride = 1; + static int major_stride = 1; + + ImGui::SliderInt("Minor Offset", &minor_offset, -20, 20); + ImGui::SliderInt("Major Offset", &major_offset, -20, 20); + ImGui::SliderInt("Minor Stride", &minor_stride, -7, 7); + ImGui::SliderInt("Major Stride", &major_stride, -7, 7); + + // Select flags + static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers; + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoMarkers); + + // Begin the plot + ImPlot3D::PushColormap("Jet"); + if (ImPlot3D::BeginPlot("Surface Plots", ImVec2(-1, 400), ImPlot3DFlags_NoClip)) { + ImPlot3D::SetupAxes("x", "y", "z"); + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1.5, 1.5); + + // Set fill style + ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.8f); + + // Set line style + ImPlot3D::SetNextLineStyle(ImPlot3D::GetColormapColor(1)); + + // Set marker style + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(2)); + + // Update the number of minor and major items depending on the stride that was selected + const int updated_num_minor = minor_stride == 0 ? M : (M / abs(minor_stride) + (M % minor_stride == 0 ? 0 : 1)); + const int updated_num_major = major_stride == 0 ? N : (N / abs(major_stride) + (N % major_stride == 0 ? 0 : 1)); + + // Add an offset to the array if either the major or the minor stride is negative + const int array_offset = (major_stride < 0 ? (M * (N - 1)) : 0) + (minor_stride < 0 ? M : 1) - 1; + + // Plot the surface + // The surface is flipped around the minor axis by specifying ImVec2(1, -1) for the minor bounds + ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, ImVec2(1, -1), ImVec2(-1, 1), + flags, values_axis, major_axis, minor_offset, major_offset, minor_stride * sizeof(float), + major_stride * M * sizeof(float)); + + // 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"); @@ -873,6 +967,7 @@ void ShowAllDemos() { DemoHeader("Triangle Plots", DemoTrianglePlots); DemoHeader("Quad Plots", DemoQuadPlots); DemoHeader("Surface Plots", DemoSurfacePlots); + DemoHeader("Simplified Surface Plots with Offset and Stride", DemoSimplifiedSurfacePlotsOffsetStride); DemoHeader("Mesh Plots", DemoMeshPlots); DemoHeader("Realtime Plots", DemoRealtimePlots); DemoHeader("Image Plots", DemoImagePlots); diff --git a/implot3d_internal.h b/implot3d_internal.h index d1a22be..0ecd939 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -57,6 +57,8 @@ template static inline bool ImHasFlag(TSet set, // 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 (x1 - x0) ? ((x - x0) / (x1 - x0)) : 0; } +// Returns always positive modulo (assumes r != 0) +static inline int ImPosMod(int l, int r) { return (l % r + r) % r; } // Returns true if val is NAN static inline bool ImNan(float val) { return isnan(val); } // Returns true if val is NAN or INFINITY diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 703044a..bddaee5 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -917,6 +917,50 @@ template struct IndexerIdx { int Stride; }; +template struct IndexerIdxMajorMinor { + IndexerIdxMajorMinor(const T* data, int num_major, int num_minor, int major_offset, int minor_offset, int major_stride, int minor_stride) + : Data(data), + // If the stride does not have to be applied then use the major offset index as the number of major columns to shift by instead of applying + // the shift for the indexes It is a bit simpler to only have to append a constant to the index instead of having to add a constant to first + // determine the rows and then column offsets + MajorOffset((major_stride == int(sizeof(T)) * num_minor && minor_stride > 0) ? ImPosMod(num_minor * major_offset, num_minor * num_major) + : ImPosMod(major_offset, num_major)), + MinorOffset(ImPosMod(minor_offset, num_minor)), MajorStride(major_stride), MinorStride(minor_stride), + Type(((MinorOffset == 0) << 0) | ((MajorOffset == 0) << 1) | ((MinorStride == int(sizeof(T)) && MajorStride > 0) << 2) | + ((MajorStride == int(sizeof(T)) * num_minor && MinorStride > 0) << 3)) {} + + template IMPLOT3D_INLINE double operator()(I idx, int count, int major, int minor, int num_major, int num_minor) const { + return (double)GetData(idx, count, major, minor, num_major, num_minor); + } + + template IMPLOT3D_INLINE T GetData(I idx, int count, int major, int minor, int num_major, int num_minor) const { + // clang-format off + // Get the data based based on the type + switch (Type) { + case 15: return Data[idx]; // No offset or stride + case 14: return Data[(((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count]; // Minor offset + case 13: return Data[(MajorOffset + idx) % count]; // Major offset + case 12: return Data[(MajorOffset + ((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count]; // Major+minor offset + case 11: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((idx)) * MinorStride); // Minor stride + case 10: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count) * MinorStride); // Minor stride and minor offset + case 9: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + idx) % count) * MinorStride); // Minor stride and major offset + case 8: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + ((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count) * MinorStride); // Minor stride and major + minor offset + case 7: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + minor * sizeof(T)); // Major stride + case 6: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + ((minor + MinorOffset) % num_minor) * sizeof(T)); // Major stride and minor offset + case 5: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + minor * sizeof(T)); // Major stride and major offset + case 4: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + ((minor + MinorOffset) % num_minor) * sizeof(T)); // Major stride and major+minor offset + case 3: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + (size_t)((minor)) * MinorStride); // Major+minor stride + case 2: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + (size_t)((minor + MinorOffset) % num_minor) * MinorStride); // Major+minor stride and minor offset + case 1: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + (size_t)((minor)) * MinorStride); // Major+minor stride and major offset + case 0: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + (size_t)((minor + MinorOffset) % num_minor) * MinorStride); // Major+minor stride and major+minor offset + default: return T(0); + } + // clang-format on + } + const T* const Data; + const int MajorOffset, MinorOffset, MajorStride, MinorStride, Type; +}; + //----------------------------------------------------------------------------- // [SECTION] Getters //----------------------------------------------------------------------------- @@ -932,6 +976,36 @@ template struct Get const int Count; }; +template struct GetterMinorMajor { + GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, + ImAxis3D values_axis, ImAxis3D major_axis) + : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef(major_bounds.y - major_bounds.x), + MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), Type(values_axis * 3 + major_axis) {} + template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { + const int major = idx / NumMinor; + const int minor = idx % NumMinor; + const float major_value = (major / float(NumMajor - 1.0f)) * MajorRef + MajorOffset; + const float minor_value = (minor / float(NumMinor - 1.0f)) * MinorRef + MinorOffset; + const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); + switch (Type) { + case 7: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major + case 6: return ImPlot3DPoint(major_value, minor_value, value); // Z-Values + X-Major + case 5: return ImPlot3DPoint(minor_value, value, major_value); // Y-Values + Z-Major + case 3: return ImPlot3DPoint(major_value, value, minor_value); // Y-Values + X-Major + case 2: return ImPlot3DPoint(value, minor_value, major_value); // X-Values + Z-Major + case 1: return ImPlot3DPoint(value, major_value, minor_value); // X-Values + Y-Major + case 8: // Z-Values + Z-Major. Not valid. Maybe assert here? + case 4: // Y-Values + Y-Major. Not valid. Maybe assert here? + case 0: // X-Values + X-Major. Not Valid. Maybe assert here? + default: return ImPlot3DPoint(0, 0, 0); + } + } + const _Indexer& Indexer; + const int NumMajor, NumMinor, Count; + const float MajorRef, MajorOffset, MinorRef, MinorOffset; + const int Type; +}; + template struct GetterLoop { GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { @@ -1364,9 +1438,32 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, co return PlotSurfaceEx(label_id, getter, x_count, y_count, scale_min, scale_max, flags); } +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, double scale_max, + const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, ImAxis3D values_axis, + ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, int major_stride) { + IM_ASSERT_USER_ERROR(values_axis != major_axis, "The values axis and major axis needs to be two different values"); + // IM_ASSERT_USER_ERROR(values_axis == ImAxis3D_Z, "Only support Z-Axis at the moment. Need to change PlotSurfaceEx for this to work with anything + // other than z-axis"); + int count = major_count * minor_count; + if (count < 4) + return; + GetterMinorMajor> getter( + IndexerIdxMajorMinor(values, major_count, minor_count, major_offset, minor_offset, + (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), minor_stride), + major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis); + + // TODO: I am pretty sure that the way that PlotSurfaceEx works is that it takes in the minor and the major count and thus it first iterates over + // the major then minor values. I need to confirm this first though + return PlotSurfaceEx(label_id, getter, minor_count, major_count, scale_min, scale_max, 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, \ - double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); + double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); \ + template IMPLOT3D_API void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, \ + double scale_max, const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, \ + ImAxis3D values_axis, ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, \ + int major_stride); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO From 103ec1061e9e7e131e5f406ae9a53fe81fbddc21 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Mon, 14 Jul 2025 23:21:56 +0200 Subject: [PATCH 02/15] fix: Get rid of unused variables --- implot3d_demo.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index c9b4f97..654ce4d 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -420,11 +420,6 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { } } - // Choose range - static bool custom_range = false; - static float range_min = -1.0f; - static float range_max = 1.0f; - // Determine the static ImAxis3D values_axis = ImAxis3D_Z; static ImAxis3D major_axis = ImAxis3D_Y; From 9e933566c964bcbc3b7f7cdcec093e50341768f0 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 15 Jul 2025 00:45:31 +0200 Subject: [PATCH 03/15] feat: Use the `GetSurfaceValue` to get the Values for the axis depending on what the data axis was set to - Don't know if we are keeping this. Might remove it depending on feeback --- implot3d_items.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index bddaee5..1b80066 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -791,9 +791,9 @@ template struct RendererSurfaceFill : RendererBase { 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); + float val = Getter.GetSurfaceValue(Getter(i)); + Min = ImMin(Min, val); + Max = ImMax(Max, val); } } } @@ -824,7 +824,7 @@ template struct RendererSurfaceFill : RendererBase { max = (float)ScaleMax; } for (int i = 0; i < 4; i++) { - ImVec4 col = SampleColormap(ImClamp(ImRemap01(p_plot[i].z, min, max), 0.0f, 1.0f)); + ImVec4 col = SampleColormap(ImClamp(ImRemap01(Getter.GetSurfaceValue(p_plot[i]), min, max), 0.0f, 1.0f)); col.w *= alpha; cols[i] = ImGui::ColorConvertFloat4ToU32(col); } @@ -970,6 +970,9 @@ template struct Get template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { return ImPlot3DPoint((float)IndexerX(idx), (float)IndexerY(idx), (float)IndexerZ(idx)); } + + IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { return point.z; } + const _IndexerX IndexerX; const _IndexerY IndexerY; const _IndexerZ IndexerZ; @@ -980,7 +983,8 @@ template struct GetterMinorMajor { GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, ImAxis3D values_axis, ImAxis3D major_axis) : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef(major_bounds.y - major_bounds.x), - MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), Type(values_axis * 3 + major_axis) {} + MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), ValueAxis(values_axis), + Type(values_axis * 3 + major_axis) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; @@ -1000,9 +1004,19 @@ template struct GetterMinorMajor { default: return ImPlot3DPoint(0, 0, 0); } } + + IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { + switch (ValueAxis) { + case 0: return point.x; // X-Values + case 1: return point.y; // Y-Values + case 2: // Z-Values + default: return point.z; + } + } const _Indexer& Indexer; const int NumMajor, NumMinor, Count; const float MajorRef, MajorOffset, MinorRef, MinorOffset; + const ImAxis3D ValueAxis; const int Type; }; From 39039695dfcb57291d4d2a53d2b3d47a2f28b4e4 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:59:08 +0200 Subject: [PATCH 04/15] feat: Add the `surface_axis` to set which axis should be used for the surface plot and allows for even more control Default to `ImAxis3D_COUNT` which the plot will then use with `values_axis` but can change the value if desired --- implot3d.h | 2 +- implot3d_demo.cpp | 8 +++++--- implot3d_items.cpp | 16 ++++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/implot3d.h b/implot3d.h index 0d8cc17..823d621 100644 --- a/implot3d.h +++ b/implot3d.h @@ -459,7 +459,7 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, co IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min = 0.0, double scale_max = 0.0, const ImVec2& minor_bounds = ImVec2(-1, 1), const ImVec2& major_bounds = ImVec2(-1, 1), ImPlot3DSurfaceFlags flags = 0, ImAxis3D values_axis = ImAxis3D_Z, ImAxis3D major_axis = ImAxis3D_Y, int minor_offset = 0, int major_offset = 0, - int minor_stride = sizeof(T), int major_stride = IMPLOT3D_DEFAULT_MAJOR_STRIDE); + int minor_stride = sizeof(T), int major_stride = IMPLOT3D_DEFAULT_MAJOR_STRIDE, ImAxis3D surface_axis = ImAxis3D_COUNT); IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0); diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 654ce4d..e17bfd1 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -423,17 +423,19 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { // Determine the static ImAxis3D values_axis = ImAxis3D_Z; static ImAxis3D major_axis = ImAxis3D_Y; - if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis\0")) { + static ImAxis3D surface_axis = ImAxis3D_COUNT; + if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis")) { // The major and value axis cannot be the same if (major_axis == values_axis) { major_axis = (major_axis + 1) % ImAxis3D_COUNT; } } - if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis\0")) { + if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis")) { if (major_axis == values_axis) { values_axis = (values_axis + 1) % ImAxis3D_COUNT; } } + ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values-Axis"); // Add offset and stride static int minor_offset = 0; static int major_offset = 0; @@ -477,7 +479,7 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { // The surface is flipped around the minor axis by specifying ImVec2(1, -1) for the minor bounds ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, ImVec2(1, -1), ImVec2(-1, 1), flags, values_axis, major_axis, minor_offset, major_offset, minor_stride * sizeof(float), - major_stride * M * sizeof(float)); + major_stride * M * sizeof(float), surface_axis); // End the plot ImPlot3D::PopStyleVar(); diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 1b80066..e9fc98d 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -981,10 +981,10 @@ template struct Get template struct GetterMinorMajor { GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, - ImAxis3D values_axis, ImAxis3D major_axis) + ImAxis3D values_axis, ImAxis3D major_axis, ImAxis3D surface_axis) : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef(major_bounds.y - major_bounds.x), - MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), ValueAxis(values_axis), - Type(values_axis * 3 + major_axis) {} + MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), + SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), Type(values_axis * 3 + major_axis) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; @@ -1006,7 +1006,7 @@ template struct GetterMinorMajor { } IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { - switch (ValueAxis) { + switch (SurfaceAxis) { case 0: return point.x; // X-Values case 1: return point.y; // Y-Values case 2: // Z-Values @@ -1016,7 +1016,7 @@ template struct GetterMinorMajor { const _Indexer& Indexer; const int NumMajor, NumMinor, Count; const float MajorRef, MajorOffset, MinorRef, MinorOffset; - const ImAxis3D ValueAxis; + const ImAxis3D SurfaceAxis; const int Type; }; @@ -1454,7 +1454,7 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, co IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, double scale_max, const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, ImAxis3D values_axis, - ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, int major_stride) { + ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, int major_stride, ImAxis3D surface_axis) { IM_ASSERT_USER_ERROR(values_axis != major_axis, "The values axis and major axis needs to be two different values"); // IM_ASSERT_USER_ERROR(values_axis == ImAxis3D_Z, "Only support Z-Axis at the moment. Need to change PlotSurfaceEx for this to work with anything // other than z-axis"); @@ -1464,7 +1464,7 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_c GetterMinorMajor> getter( IndexerIdxMajorMinor(values, major_count, minor_count, major_offset, minor_offset, (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), minor_stride), - major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis); + major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis, surface_axis); // TODO: I am pretty sure that the way that PlotSurfaceEx works is that it takes in the minor and the major count and thus it first iterates over // the major then minor values. I need to confirm this first though @@ -1477,7 +1477,7 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_c template IMPLOT3D_API void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, \ double scale_max, const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, \ ImAxis3D values_axis, ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, \ - int major_stride); + int major_stride, ImAxis3D surface_axis); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO From 15f563eba7d4aee9d62381993f48bfbd74a625bb Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:14:32 +0200 Subject: [PATCH 05/15] refactor: Fix up some comments and value displayed in the combo box --- implot3d_demo.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index e17bfd1..1541a2f 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -420,12 +420,12 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { } } - // Determine the + // Set which axis information to use for this plot + // The major and value axis flags cannot be set to the same value static ImAxis3D values_axis = ImAxis3D_Z; static ImAxis3D major_axis = ImAxis3D_Y; static ImAxis3D surface_axis = ImAxis3D_COUNT; if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis")) { - // The major and value axis cannot be the same if (major_axis == values_axis) { major_axis = (major_axis + 1) % ImAxis3D_COUNT; } @@ -435,19 +435,19 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { values_axis = (values_axis + 1) % ImAxis3D_COUNT; } } - ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values-Axis"); + ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values Axis"); + // Add offset and stride static int minor_offset = 0; static int major_offset = 0; static int minor_stride = 1; static int major_stride = 1; - ImGui::SliderInt("Minor Offset", &minor_offset, -20, 20); ImGui::SliderInt("Major Offset", &major_offset, -20, 20); ImGui::SliderInt("Minor Stride", &minor_stride, -7, 7); ImGui::SliderInt("Major Stride", &major_stride, -7, 7); - // Select flags + // Select flags for the surface plot static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers; CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); From dc073f1ef25cb1ccf10ab69d7e14d47dba07233e Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:36:09 +0200 Subject: [PATCH 06/15] refactor: Split the new "simplified" demo into two parts 1. A simplified example where the x and y values do not have to be specified but only the z_values. 2. Simillar to ImPlot add a section `Tools` where we can show an example where the axis, stride and offset of the surface plot is changed --- implot3d_demo.cpp | 150 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 1541a2f..d106df6 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -17,6 +17,7 @@ // [SECTION] Plots // [SECTION] Axes // [SECTION] Custom +// [SECTION] Tools // [SECTION] Demo Window // [SECTION] Style Editor // [SECTION] User Namespace Implementation @@ -397,18 +398,18 @@ void DemoSurfacePlots() { ImPlot3D::PopColormap(); } -void DemoSimplifiedSurfacePlotsOffsetStride() { +void DemoSimplifiedSurfacePlots() { constexpr int N = 10; constexpr int M = 20; static float values[N * M]; static float t = 0.0f; - // t += ImGui::GetIO().DeltaTime; + t += ImGui::GetIO().DeltaTime; // Define the range for X and Y constexpr float min_val = -1.0f; constexpr float max_val = 1.0f; - constexpr float x_step = (max_val - min_val) / (N - 1); - constexpr float y_step = (max_val - min_val) / (M - 1); + constexpr float x_step = (max_val - min_val) / (M - 1); + constexpr float y_step = (max_val - min_val) / (N - 1); // Populate the values array for (int i = 0; i < N; i++) { @@ -420,33 +421,6 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { } } - // Set which axis information to use for this plot - // The major and value axis flags cannot be set to the same value - static ImAxis3D values_axis = ImAxis3D_Z; - static ImAxis3D major_axis = ImAxis3D_Y; - static ImAxis3D surface_axis = ImAxis3D_COUNT; - if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis")) { - if (major_axis == values_axis) { - major_axis = (major_axis + 1) % ImAxis3D_COUNT; - } - } - if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis")) { - if (major_axis == values_axis) { - values_axis = (values_axis + 1) % ImAxis3D_COUNT; - } - } - ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values Axis"); - - // Add offset and stride - static int minor_offset = 0; - static int major_offset = 0; - static int minor_stride = 1; - static int major_stride = 1; - ImGui::SliderInt("Minor Offset", &minor_offset, -20, 20); - ImGui::SliderInt("Major Offset", &major_offset, -20, 20); - ImGui::SliderInt("Minor Stride", &minor_stride, -7, 7); - ImGui::SliderInt("Major Stride", &major_stride, -7, 7); - // Select flags for the surface plot static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers; CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); @@ -457,7 +431,7 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { ImPlot3D::PushColormap("Jet"); if (ImPlot3D::BeginPlot("Surface Plots", ImVec2(-1, 400), ImPlot3DFlags_NoClip)) { ImPlot3D::SetupAxes("x", "y", "z"); - ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1.5, 1.5); + ImPlot3D::SetupAxesLimits(-10, -5, 5, 10, -1.5, 1.5); // Set fill style ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.8f); @@ -468,18 +442,9 @@ void DemoSimplifiedSurfacePlotsOffsetStride() { // Set marker style ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(2)); - // Update the number of minor and major items depending on the stride that was selected - const int updated_num_minor = minor_stride == 0 ? M : (M / abs(minor_stride) + (M % minor_stride == 0 ? 0 : 1)); - const int updated_num_major = major_stride == 0 ? N : (N / abs(major_stride) + (N % major_stride == 0 ? 0 : 1)); - - // Add an offset to the array if either the major or the minor stride is negative - const int array_offset = (major_stride < 0 ? (M * (N - 1)) : 0) + (minor_stride < 0 ? M : 1) - 1; - // Plot the surface - // The surface is flipped around the minor axis by specifying ImVec2(1, -1) for the minor bounds - ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, ImVec2(1, -1), ImVec2(-1, 1), - flags, values_axis, major_axis, minor_offset, major_offset, minor_stride * sizeof(float), - major_stride * M * sizeof(float), surface_axis); + // Plot the X-Axis range from [-10, -5] and Y-Axis range from [5, 10] + ImPlot3D::PlotSurface("Wave Surface", values, M, N, 0.0, 0.0, ImVec2(-10, -5), ImVec2(5, 10), flags); // End the plot ImPlot3D::PopStyleVar(); @@ -828,6 +793,99 @@ void DemoAxisConstraints() { } } +//----------------------------------------------------------------------------- +// [SECTION] Tools +//----------------------------------------------------------------------------- + +void DemoSurfaceAxisOffsetStride() { + constexpr int N = 10; + constexpr int M = 20; + static float values[N * M]; + + // Define the range for X and Y + constexpr float min_val = -1.0f; + constexpr float max_val = 1.0f; + constexpr float x_step = (max_val - min_val) / (N - 1); + constexpr float y_step = (max_val - min_val) / (M - 1); + + // Populate the values array + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + int idx = i * M + j; + float x = min_val + j * x_step; + float y = min_val + i * y_step; + values[idx] = ImSin(ImSqrt((x * x + y * y))); // value = sin(sqrt(x^2 + y^2)) + } + } + + // Set which axis information to use for this plot + // The major and value axis flags cannot be set to the same value + static ImAxis3D values_axis = ImAxis3D_Z; + static ImAxis3D major_axis = ImAxis3D_Y; + static ImAxis3D surface_axis = ImAxis3D_COUNT; + if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis")) { + if (major_axis == values_axis) { + major_axis = (major_axis + 1) % ImAxis3D_COUNT; + } + } + if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis")) { + if (major_axis == values_axis) { + values_axis = (values_axis + 1) % ImAxis3D_COUNT; + } + } + ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values Axis"); + + // Add offset and stride + static int minor_offset = 0; + static int major_offset = 0; + static int minor_stride = 1; + static int major_stride = 1; + ImGui::SliderInt("Minor Offset", &minor_offset, -20, 20); + ImGui::SliderInt("Major Offset", &major_offset, -20, 20); + ImGui::SliderInt("Minor Stride", &minor_stride, -7, 7); + ImGui::SliderInt("Major Stride", &major_stride, -7, 7); + + // Select flags for the surface plot + static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers; + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoMarkers); + + // Begin the plot + ImPlot3D::PushColormap("Jet"); + if (ImPlot3D::BeginPlot("Surface Plots", ImVec2(-1, 400), ImPlot3DFlags_NoClip)) { + ImPlot3D::SetupAxes("x", "y", "z"); + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1, 1); + + // Set fill style + ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.8f); + + // Set line style + ImPlot3D::SetNextLineStyle(ImPlot3D::GetColormapColor(1)); + + // Set marker style + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(2)); + + // Update the number of minor and major items depending on the stride that was selected + const int updated_num_minor = minor_stride == 0 ? M : (M / abs(minor_stride) + (M % minor_stride == 0 ? 0 : 1)); + const int updated_num_major = major_stride == 0 ? N : (N / abs(major_stride) + (N % major_stride == 0 ? 0 : 1)); + + // Add an offset to the array if either the major or the minor stride is negative + const int array_offset = (major_stride < 0 ? (M * (N - 1)) : 0) + (minor_stride < 0 ? M : 1) - 1; + + // Plot the surface + // The surface is flipped around the minor axis by specifying ImVec2(1, -1) for the minor bounds + ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, ImVec2(1, -1), ImVec2(-1, 1), + flags, values_axis, major_axis, minor_offset, major_offset, minor_stride * sizeof(float), + major_stride * M * sizeof(float), surface_axis); + + // End the plot + ImPlot3D::PopStyleVar(); + ImPlot3D::EndPlot(); + } + ImPlot3D::PopColormap(); +} + //----------------------------------------------------------------------------- // [SECTION] Custom //----------------------------------------------------------------------------- @@ -964,7 +1022,7 @@ void ShowAllDemos() { DemoHeader("Triangle Plots", DemoTrianglePlots); DemoHeader("Quad Plots", DemoQuadPlots); DemoHeader("Surface Plots", DemoSurfacePlots); - DemoHeader("Simplified Surface Plots with Offset and Stride", DemoSimplifiedSurfacePlotsOffsetStride); + DemoHeader("Simplified Surface Plots", DemoSimplifiedSurfacePlots); DemoHeader("Mesh Plots", DemoMeshPlots); DemoHeader("Realtime Plots", DemoRealtimePlots); DemoHeader("Image Plots", DemoImagePlots); @@ -979,6 +1037,10 @@ void ShowAllDemos() { DemoHeader("Axis Constraints", DemoAxisConstraints); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Tools")) { + DemoHeader("Surface Axis, Offset and Stride Plot", DemoSurfaceAxisOffsetStride); + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Custom")) { DemoHeader("Custom Styles", DemoCustomStyles); DemoHeader("Custom Rendering", DemoCustomRendering); From 71a400a963bd0d14a8eda3e199441da6be270764 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Tue, 15 Jul 2025 22:02:18 +0200 Subject: [PATCH 07/15] refactor: Fix up comments + remove comments that are not applicable - The plot works with plots that use x-axis or y-axis information - As far as I can tell PlotSurfaceEx does not care what the values axis and minor/major values are as long as they are provided. --- implot3d_items.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index e9fc98d..1a3c01d 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -1456,18 +1456,15 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_c const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, ImAxis3D values_axis, ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, int major_stride, ImAxis3D surface_axis) { IM_ASSERT_USER_ERROR(values_axis != major_axis, "The values axis and major axis needs to be two different values"); - // IM_ASSERT_USER_ERROR(values_axis == ImAxis3D_Z, "Only support Z-Axis at the moment. Need to change PlotSurfaceEx for this to work with anything - // other than z-axis"); int count = major_count * minor_count; if (count < 4) return; - GetterMinorMajor> getter( - IndexerIdxMajorMinor(values, major_count, minor_count, major_offset, minor_offset, - (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), minor_stride), - major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis, surface_axis); - - // TODO: I am pretty sure that the way that PlotSurfaceEx works is that it takes in the minor and the major count and thus it first iterates over - // the major then minor values. I need to confirm this first though + // Create the getter and the indexer that will be passed to PlotSurfaceEx. The getter and the indexer will produce the correct information based + // on the indexes passed in + IndexerIdxMajorMinor indexer(values, major_count, minor_count, major_offset, minor_offset, + (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), minor_stride); + GetterMinorMajor> getter(indexer, major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis, + surface_axis); return PlotSurfaceEx(label_id, getter, minor_count, major_count, scale_min, scale_max, flags); } From 35f4047c1aa43059756294fb419bb571265d1c4d Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:43:02 +0200 Subject: [PATCH 08/15] fix: Get rid of unneed case statements - If the value axis and the major axis is the same then do not have a case for it. - There are fewer case statements in the switch --- implot3d_items.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 1a3c01d..63bf55e 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -984,7 +984,8 @@ template struct GetterMinorMajor { ImAxis3D values_axis, ImAxis3D major_axis, ImAxis3D surface_axis) : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef(major_bounds.y - major_bounds.x), MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), - SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), Type(values_axis * 3 + major_axis) {} + SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), + Type((values_axis == major_axis) ? 6 : (values_axis * 2 + (major_axis - (major_axis > values_axis ? 1 : 0)))) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; @@ -992,25 +993,21 @@ template struct GetterMinorMajor { const float minor_value = (minor / float(NumMinor - 1.0f)) * MinorRef + MinorOffset; const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); switch (Type) { - case 7: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major - case 6: return ImPlot3DPoint(major_value, minor_value, value); // Z-Values + X-Major - case 5: return ImPlot3DPoint(minor_value, value, major_value); // Y-Values + Z-Major - case 3: return ImPlot3DPoint(major_value, value, minor_value); // Y-Values + X-Major - case 2: return ImPlot3DPoint(value, minor_value, major_value); // X-Values + Z-Major - case 1: return ImPlot3DPoint(value, major_value, minor_value); // X-Values + Y-Major - case 8: // Z-Values + Z-Major. Not valid. Maybe assert here? - case 4: // Y-Values + Y-Major. Not valid. Maybe assert here? - case 0: // X-Values + X-Major. Not Valid. Maybe assert here? + case 5: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major + case 4: return ImPlot3DPoint(major_value, minor_value, value); // Z-Values + X-Major + case 3: return ImPlot3DPoint(minor_value, value, major_value); // Y-Values + Z-Major + case 2: return ImPlot3DPoint(major_value, value, minor_value); // Y-Values + X-Major + case 1: return ImPlot3DPoint(value, minor_value, major_value); // X-Values + Z-Major + case 0: return ImPlot3DPoint(value, major_value, minor_value); // X-Values + Y-Major default: return ImPlot3DPoint(0, 0, 0); } } IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { switch (SurfaceAxis) { - case 0: return point.x; // X-Values - case 1: return point.y; // Y-Values - case 2: // Z-Values - default: return point.z; + case 0: return point.x; // X-Values + case 1: return point.y; // Y-Values + default: return point.z; // Z-Values(2) } } const _Indexer& Indexer; From dff4d4f350d7ba5910eac9ff3498b03fa5d15202 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:57:22 +0200 Subject: [PATCH 09/15] refactor: Rename the `x_count` and `y_count` to `minor_count` and `major_count` since that matches how they are used in the new `PlotSurface` function. - Up to this point they have been used as x and y count but with the new function the major axis can be set --- implot3d_items.cpp | 49 +++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 63bf55e..ee60b7f 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -778,8 +778,9 @@ template struct RendererQuadImage : RendererBase { }; template struct RendererSurfaceFill : RendererBase { - RendererSurfaceFill(const _Getter& getter, int x_count, int y_count, ImU32 col, double scale_min, double scale_max) - : RendererBase((x_count - 1) * (y_count - 1), 6, 4), Getter(getter), XCount(x_count), YCount(y_count), Col(col), ScaleMin(scale_min), + RendererSurfaceFill(const _Getter& getter, int minor_count, int major_count, ImU32 col, double scale_min, double scale_max) + : RendererBase((minor_count - 1) * (major_count - 1), 6, 4), Getter(getter), MinorCount(minor_count), MajorCount(major_count), Col(col), + ScaleMin(scale_min), ScaleMax(scale_max) {} void Init(ImDrawList3D& draw_list_3d) const { @@ -799,14 +800,14 @@ template struct RendererSurfaceFill : RendererBase { } 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); + int minor = prim % (MinorCount - 1); + int major = prim / (MinorCount - 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); + p_plot[0] = Getter(minor + major * MinorCount); + p_plot[1] = Getter(minor + 1 + major * MinorCount); + p_plot[2] = Getter(minor + 1 + (major + 1) * MinorCount); + p_plot[3] = Getter(minor + (major + 1) * MinorCount); // 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])) @@ -886,8 +887,8 @@ template struct RendererSurfaceFill : RendererBase { 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 int MinorCount; + const int MajorCount; const ImU32 Col; const double ScaleMin; const double ScaleMax; @@ -1048,9 +1049,9 @@ template struct GetterQuadLines { }; 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; + GetterSurfaceLines(_Getter getter, int minor_count, int major_count) : Getter(getter), MinorCount(minor_count), MajorCount(major_count) { + int horizontal_segments = (MinorCount - 1) * MajorCount; + int vertical_segments = (MajorCount - 1) * MinorCount; int segments = horizontal_segments + vertical_segments; Count = segments * 2; // Each segment has 2 endpoints } @@ -1060,33 +1061,33 @@ template struct GetterSurfaceLines { int endpoint_i = (int)(idx % 2); int segment_i = (int)(idx / 2); - int horizontal_segments = (XCount - 1) * YCount; + int horizontal_segments = (MinorCount - 1) * MajorCount; int px, py; if (segment_i < horizontal_segments) { // Horizontal segment - int row = segment_i / (XCount - 1); - int col = segment_i % (XCount - 1); + int row = segment_i / (MinorCount - 1); + int col = segment_i % (MinorCount - 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); + int col = seg_v / (MajorCount - 1); + int row = seg_v % (MajorCount - 1); // Endpoint 0 is (col, row), endpoint 1 is (col, row+1) px = col; py = row + endpoint_i; } - return Getter(py * XCount + px); + return Getter(py * MinorCount + px); } const _Getter Getter; int Count; - const int XCount; - const int YCount; + const int MinorCount; + const int MajorCount; }; struct Getter3DPoints { @@ -1411,7 +1412,7 @@ CALL_INSTANTIATE_FOR_NUMERIC_TYPES() // [SECTION] PlotSurface //----------------------------------------------------------------------------- -template void PlotSurfaceEx(const char* label_id, const _Getter& getter, int x_count, int y_count, double scale_min, +template void PlotSurfaceEx(const char* label_id, const _Getter& getter, int minor_count, int major_count, double scale_min, double scale_max, ImPlot3DSurfaceFlags flags) { if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) { const ImPlot3DNextItemData& n = GetItemData(); @@ -1419,13 +1420,13 @@ template void PlotSurfaceEx(const char* label_id, const _Gett // Render fill if (getter.Count >= 4 && n.RenderFill && !ImHasFlag(flags, ImPlot3DSurfaceFlags_NoFill)) { const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]); - RenderPrimitives(getter, x_count, y_count, col_fill, scale_min, scale_max); + RenderPrimitives(getter, minor_count, major_count, col_fill, scale_min, scale_max); } // Render lines if (getter.Count >= 2 && n.RenderLine && !ImHasFlag(flags, ImPlot3DSurfaceFlags_NoLines)) { const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]); - RenderPrimitives(GetterSurfaceLines<_Getter>(getter, x_count, y_count), col_line, n.LineWeight); + RenderPrimitives(GetterSurfaceLines<_Getter>(getter, minor_count, major_count), col_line, n.LineWeight); } // Render markers From cb3b07c1b1134f9905e3b10cbd6b0fd84b383f7c Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:18:12 +0200 Subject: [PATCH 10/15] refactor: Apply small optimisation to determine the `MajorRef` and `MinorRef` to apply to the major and minor when creating the `Getter` instead of determining it in the for loop each time --- implot3d_items.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index ee60b7f..36fc6f1 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -780,8 +780,7 @@ template struct RendererQuadImage : RendererBase { template struct RendererSurfaceFill : RendererBase { RendererSurfaceFill(const _Getter& getter, int minor_count, int major_count, ImU32 col, double scale_min, double scale_max) : RendererBase((minor_count - 1) * (major_count - 1), 6, 4), Getter(getter), MinorCount(minor_count), MajorCount(major_count), Col(col), - ScaleMin(scale_min), - ScaleMax(scale_max) {} + ScaleMin(scale_min), ScaleMax(scale_max) {} void Init(ImDrawList3D& draw_list_3d) const { UV = draw_list_3d._SharedData->TexUvWhitePixel; @@ -983,15 +982,15 @@ template struct Get template struct GetterMinorMajor { GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, ImAxis3D values_axis, ImAxis3D major_axis, ImAxis3D surface_axis) - : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef(major_bounds.y - major_bounds.x), - MajorOffset(major_bounds.x), MinorRef(minor_bounds.y - minor_bounds.x), MinorOffset(minor_bounds.x), + : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef((major_bounds.y - major_bounds.x) / (num_major - 1.0f)), + MajorOffset(major_bounds.x), MinorRef((minor_bounds.y - minor_bounds.x) / (num_minor - 1.0f)), MinorOffset(minor_bounds.x), SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), Type((values_axis == major_axis) ? 6 : (values_axis * 2 + (major_axis - (major_axis > values_axis ? 1 : 0)))) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; - const float major_value = (major / float(NumMajor - 1.0f)) * MajorRef + MajorOffset; - const float minor_value = (minor / float(NumMinor - 1.0f)) * MinorRef + MinorOffset; + const float major_value = major * MajorRef + MajorOffset; + const float minor_value = minor * MinorRef + MinorOffset; const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); switch (Type) { case 5: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major From 62d4bcdf3ff874a561180dc8b9e0cf1970882e30 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Wed, 16 Jul 2025 19:36:03 +0200 Subject: [PATCH 11/15] fix: Minor optimisation. Get rid of having to determine the major and minor values if they are already available. - Call and pass in values to the operator in the `Getter` function if the minor and major values are already known. - Default back to using the operator which does not take in the minor and major stride if it is not available. --- implot3d_items.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 36fc6f1..d0dc14f 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -803,10 +803,10 @@ template struct RendererSurfaceFill : RendererBase { int major = prim / (MinorCount - 1); ImPlot3DPoint p_plot[4]; - p_plot[0] = Getter(minor + major * MinorCount); - p_plot[1] = Getter(minor + 1 + major * MinorCount); - p_plot[2] = Getter(minor + 1 + (major + 1) * MinorCount); - p_plot[3] = Getter(minor + (major + 1) * MinorCount); + p_plot[0] = Getter(minor + major * MinorCount, minor, major); + p_plot[1] = Getter(minor + 1 + major * MinorCount, minor + 1, major); + p_plot[2] = Getter(minor + 1 + (major + 1) * MinorCount, minor + 1, (major + 1)); + p_plot[3] = Getter(minor + (major + 1) * MinorCount, minor, (major + 1)); // 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])) @@ -971,6 +971,8 @@ template struct Get return ImPlot3DPoint((float)IndexerX(idx), (float)IndexerY(idx), (float)IndexerZ(idx)); } + template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx, int minor, int major) const { return (*this)(idx); } + IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { return point.z; } const _IndexerX IndexerX; @@ -989,6 +991,9 @@ template struct GetterMinorMajor { template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; + return (*this)(idx, minor, major); + } + template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx, int minor, int major) const { const float major_value = major * MajorRef + MajorOffset; const float minor_value = minor * MinorRef + MinorOffset; const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); @@ -1080,7 +1085,7 @@ template struct GetterSurfaceLines { py = row + endpoint_i; } - return Getter(py * MinorCount + px); + return Getter(py * MinorCount + px, px, py); } const _Getter Getter; From d4b4b25d6eeaa3ce85a0dca9f9e5a2956891414c Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:44:17 +0200 Subject: [PATCH 12/15] refactor: Rename `MajorRef`, `MajorOffset`, `MinorRef` and `MinorOffset` to `MajorValueRef`, `MajorValueOffset`, `MinorValueRef` and `MinorValueOffset` - This is mainly distinguish it from the major and minor offset we add to the data pointer --- implot3d_items.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/implot3d_items.cpp b/implot3d_items.cpp index d0dc14f..fc775f0 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -984,8 +984,9 @@ template struct Get template struct GetterMinorMajor { GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, ImAxis3D values_axis, ImAxis3D major_axis, ImAxis3D surface_axis) - : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorRef((major_bounds.y - major_bounds.x) / (num_major - 1.0f)), - MajorOffset(major_bounds.x), MinorRef((minor_bounds.y - minor_bounds.x) / (num_minor - 1.0f)), MinorOffset(minor_bounds.x), + : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), + MajorValueRef((major_bounds.y - major_bounds.x) / (num_major - 1.0f)), MajorValueOffset(major_bounds.x), + MinorValueRef((minor_bounds.y - minor_bounds.x) / (num_minor - 1.0f)), MinorValueOffset(minor_bounds.x), SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), Type((values_axis == major_axis) ? 6 : (values_axis * 2 + (major_axis - (major_axis > values_axis ? 1 : 0)))) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { @@ -994,8 +995,8 @@ template struct GetterMinorMajor { return (*this)(idx, minor, major); } template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx, int minor, int major) const { - const float major_value = major * MajorRef + MajorOffset; - const float minor_value = minor * MinorRef + MinorOffset; + const float major_value = major * MajorValueRef + MajorValueOffset; + const float minor_value = minor * MinorValueRef + MinorValueOffset; const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); switch (Type) { case 5: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major @@ -1017,7 +1018,7 @@ template struct GetterMinorMajor { } const _Indexer& Indexer; const int NumMajor, NumMinor, Count; - const float MajorRef, MajorOffset, MinorRef, MinorOffset; + const float MajorValueRef, MajorValueOffset, MinorValueRef, MinorValueOffset; const ImAxis3D SurfaceAxis; const int Type; }; From 77ac908a2ba3df7072956eda7fe813deacb16c14 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:13:05 +0200 Subject: [PATCH 13/15] feat: Update the surface plot API based on initial feedback - Rearange parameters for the `PlotSurface` function call and get rid of some of the parameters not needed. - Added flags to indicate what the plane axis is and use them accordingly - Fix demo to take into account some of the changes - Use data `stride` and `offset` instead of `minor stride` and `minor offset`. For the most part the `minor stride` is the same as `stride` but there is quite a big difference between `minor offset` and `offset`. - Added helper object `SurfacePlotPlaneGetter` which get the plane value from the 3D point based on which plane was set --- implot3d.h | 12 +++-- implot3d_demo.cpp | 53 +++++++++---------- implot3d_items.cpp | 123 +++++++++++++++++++++++++-------------------- 3 files changed, 100 insertions(+), 88 deletions(-) diff --git a/implot3d.h b/implot3d.h index 823d621..42c2ed1 100644 --- a/implot3d.h +++ b/implot3d.h @@ -229,6 +229,13 @@ enum ImPlot3DSurfaceFlags_ { ImPlot3DSurfaceFlags_NoLines = 1 << 10, // No lines will be rendered ImPlot3DSurfaceFlags_NoFill = 1 << 11, // No fill will be rendered ImPlot3DSurfaceFlags_NoMarkers = 1 << 12, // No markers will be rendered + + // The plane to use for the surface plot + ImPlot3DSurfaceFlags_PlaneXY = 1 << 0, + ImPlot3DSurfaceFlags_PlaneXZ = 1 << 13, + ImPlot3DSurfaceFlags_PlaneYZ = 1 << 14, + ImPlot3DSurfaceFlags_SwapAxes = 1 << 15, + ImPlot3DSurfaceFlags_PlaneMask_ = ImPlot3DSurfaceFlags_PlaneXY | ImPlot3DSurfaceFlags_PlaneXZ | ImPlot3DSurfaceFlags_PlaneYZ, }; // Flags for PlotMesh @@ -457,9 +464,8 @@ IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min = 0.0, double scale_max = 0.0, ImPlot3DSurfaceFlags flags = 0, int offset = 0, int stride = sizeof(T)); IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min = 0.0, double scale_max = 0.0, - const ImVec2& minor_bounds = ImVec2(-1, 1), const ImVec2& major_bounds = ImVec2(-1, 1), ImPlot3DSurfaceFlags flags = 0, - ImAxis3D values_axis = ImAxis3D_Z, ImAxis3D major_axis = ImAxis3D_Y, int minor_offset = 0, int major_offset = 0, - int minor_stride = sizeof(T), int major_stride = IMPLOT3D_DEFAULT_MAJOR_STRIDE, ImAxis3D surface_axis = ImAxis3D_COUNT); + ImPlot3DSurfaceFlags flags = 0, const ImVec2& minor_bounds = ImVec2(-1, 1), const ImVec2& major_bounds = ImVec2(-1, 1), + int offset = 0, int stride = sizeof(T), int major_offset = 0, int major_stride = IMPLOT3D_DEFAULT_MAJOR_STRIDE); IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0); diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index d106df6..aa33d37 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -444,7 +444,7 @@ void DemoSimplifiedSurfacePlots() { // Plot the surface // Plot the X-Axis range from [-10, -5] and Y-Axis range from [5, 10] - ImPlot3D::PlotSurface("Wave Surface", values, M, N, 0.0, 0.0, ImVec2(-10, -5), ImVec2(5, 10), flags); + ImPlot3D::PlotSurface("Wave Surface", values, M, N, 0.0, 0.0, flags, ImVec2(-10, -5), ImVec2(5, 10)); // End the plot ImPlot3D::PopStyleVar(); @@ -818,38 +818,32 @@ void DemoSurfaceAxisOffsetStride() { } } - // Set which axis information to use for this plot - // The major and value axis flags cannot be set to the same value - static ImAxis3D values_axis = ImAxis3D_Z; - static ImAxis3D major_axis = ImAxis3D_Y; - static ImAxis3D surface_axis = ImAxis3D_COUNT; - if (ImGui::Combo("Values Axis", &values_axis, "X-Axis\0Y-Axis\0Z-Axis")) { - if (major_axis == values_axis) { - major_axis = (major_axis + 1) % ImAxis3D_COUNT; - } - } - if (ImGui::Combo("Major Axis", &major_axis, "X-Axis\0Y-Axis\0Z-Axis")) { - if (major_axis == values_axis) { - values_axis = (values_axis + 1) % ImAxis3D_COUNT; - } - } - ImGui::Combo("Surface Axis", &surface_axis, "X-Axis\0Y-Axis\0Z-Axis\0Values Axis"); - // Add offset and stride - static int minor_offset = 0; + static int offset = 0; static int major_offset = 0; - static int minor_stride = 1; + static int stride = 1; static int major_stride = 1; - ImGui::SliderInt("Minor Offset", &minor_offset, -20, 20); + static bool apply_major_stride = true; + if(ImGui::Checkbox("Apply Major stride", &apply_major_stride)) + major_stride = 1; + ImGui::SliderInt("Minor Offset", &offset, -200, 200); ImGui::SliderInt("Major Offset", &major_offset, -20, 20); - ImGui::SliderInt("Minor Stride", &minor_stride, -7, 7); - ImGui::SliderInt("Major Stride", &major_stride, -7, 7); + ImGui::SliderInt("Minor Stride", &stride, -10, 10); + if (apply_major_stride) + ImGui::SliderInt("Major Stride", &major_stride, -10, 10); // Select flags for the surface plot - static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers; + static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers | ImPlot3DSurfaceFlags_PlaneXY; CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoMarkers); + if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneXY", &flags, ImPlot3DSurfaceFlags_PlaneXY)) + flags &= ~(ImPlot3DSurfaceFlags_PlaneMask_ & ~ImPlot3DSurfaceFlags_PlaneXY); + if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneXZ", &flags, ImPlot3DSurfaceFlags_PlaneXZ)) + flags &= ~(ImPlot3DSurfaceFlags_PlaneMask_ & ~ImPlot3DSurfaceFlags_PlaneXZ); + if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneYZ", &flags, ImPlot3DSurfaceFlags_PlaneYZ)) + flags &= ~(ImPlot3DSurfaceFlags_PlaneMask_ & ~ImPlot3DSurfaceFlags_PlaneYZ); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_SwapAxes); // Begin the plot ImPlot3D::PushColormap("Jet"); @@ -867,17 +861,16 @@ void DemoSurfaceAxisOffsetStride() { ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(2)); // Update the number of minor and major items depending on the stride that was selected - const int updated_num_minor = minor_stride == 0 ? M : (M / abs(minor_stride) + (M % minor_stride == 0 ? 0 : 1)); + const int updated_num_minor = stride == 0 ? M : (M / abs(stride) + (M % stride == 0 ? 0 : 1)); const int updated_num_major = major_stride == 0 ? N : (N / abs(major_stride) + (N % major_stride == 0 ? 0 : 1)); - // Add an offset to the array if either the major or the minor stride is negative - const int array_offset = (major_stride < 0 ? (M * (N - 1)) : 0) + (minor_stride < 0 ? M : 1) - 1; + const int array_offset = (major_stride < 0 ? (M * (N - 1)) : 0) + (stride < 0 ? M : 1) - 1; // Plot the surface // The surface is flipped around the minor axis by specifying ImVec2(1, -1) for the minor bounds - ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, ImVec2(1, -1), ImVec2(-1, 1), - flags, values_axis, major_axis, minor_offset, major_offset, minor_stride * sizeof(float), - major_stride * M * sizeof(float), surface_axis); + ImPlot3D::PlotSurface("Wave Surface", &values[array_offset], updated_num_minor, updated_num_major, 0.0, 0.0, flags, ImVec2(1, -1), + ImVec2(-1, 1), offset, stride * sizeof(float), major_offset, + apply_major_stride ? (major_stride * M * sizeof(float)) : IMPLOT3D_DEFAULT_MAJOR_STRIDE); // End the plot ImPlot3D::PopStyleVar(); diff --git a/implot3d_items.cpp b/implot3d_items.cpp index fc775f0..67cb019 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -777,10 +777,24 @@ template struct RendererQuadImage : RendererBase { const ImU32 Col; }; +struct SurfacePlotPlaneGetter { + SurfacePlotPlaneGetter(ImPlane3D plane) : Plane(plane) {} + + IMPLOT3D_INLINE float operator()(const ImPlot3DPoint& point) const { + switch (Plane) { + case 0: return point.x; // X-Values + case 1: return point.y; // Y-Values + default: return point.z; // Z-Values(2) + } + } + const ImPlane3D Plane; +}; + template struct RendererSurfaceFill : RendererBase { - RendererSurfaceFill(const _Getter& getter, int minor_count, int major_count, ImU32 col, double scale_min, double scale_max) + RendererSurfaceFill(const _Getter& getter, int minor_count, int major_count, ImU32 col, double scale_min, double scale_max, + const SurfacePlotPlaneGetter& plane_getter) : RendererBase((minor_count - 1) * (major_count - 1), 6, 4), Getter(getter), MinorCount(minor_count), MajorCount(major_count), Col(col), - ScaleMin(scale_min), ScaleMax(scale_max) {} + ScaleMin(scale_min), ScaleMax(scale_max), PlaneGetter(plane_getter) {} void Init(ImDrawList3D& draw_list_3d) const { UV = draw_list_3d._SharedData->TexUvWhitePixel; @@ -791,7 +805,7 @@ template struct RendererSurfaceFill : RendererBase { Min = FLT_MAX; Max = -FLT_MAX; for (int i = 0; i < Getter.Count; i++) { - float val = Getter.GetSurfaceValue(Getter(i)); + float val = PlaneGetter(Getter(i)); Min = ImMin(Min, val); Max = ImMax(Max, val); } @@ -824,7 +838,7 @@ template struct RendererSurfaceFill : RendererBase { max = (float)ScaleMax; } for (int i = 0; i < 4; i++) { - ImVec4 col = SampleColormap(ImClamp(ImRemap01(Getter.GetSurfaceValue(p_plot[i]), min, max), 0.0f, 1.0f)); + ImVec4 col = SampleColormap(ImClamp(ImRemap01(PlaneGetter(p_plot[i]), min, max), 0.0f, 1.0f)); col.w *= alpha; cols[i] = ImGui::ColorConvertFloat4ToU32(col); } @@ -891,6 +905,7 @@ template struct RendererSurfaceFill : RendererBase { const ImU32 Col; const double ScaleMin; const double ScaleMax; + const SurfacePlotPlaneGetter& PlaneGetter; }; //----------------------------------------------------------------------------- @@ -917,17 +932,17 @@ template struct IndexerIdx { int Stride; }; -template struct IndexerIdxMajorMinor { - IndexerIdxMajorMinor(const T* data, int num_major, int num_minor, int major_offset, int minor_offset, int major_stride, int minor_stride) +template struct IndexerIdxMajor { + IndexerIdxMajor(const T* data, int num_major, int num_minor, int major_offset, int offset, int major_stride, int stride) : Data(data), // If the stride does not have to be applied then use the major offset index as the number of major columns to shift by instead of applying // the shift for the indexes It is a bit simpler to only have to append a constant to the index instead of having to add a constant to first // determine the rows and then column offsets - MajorOffset((major_stride == int(sizeof(T)) * num_minor && minor_stride > 0) ? ImPosMod(num_minor * major_offset, num_minor * num_major) - : ImPosMod(major_offset, num_major)), - MinorOffset(ImPosMod(minor_offset, num_minor)), MajorStride(major_stride), MinorStride(minor_stride), - Type(((MinorOffset == 0) << 0) | ((MajorOffset == 0) << 1) | ((MinorStride == int(sizeof(T)) && MajorStride > 0) << 2) | - ((MajorStride == int(sizeof(T)) * num_minor && MinorStride > 0) << 3)) {} + MajorOffset((major_stride == int(sizeof(T)) * num_minor && stride > 0) ? ImPosMod(num_minor * major_offset, num_minor * num_major) + : ImPosMod(major_offset, num_major)), + Offset(ImPosMod(offset, num_minor * num_major)), MajorStride(major_stride), Stride(stride), + Type(((Offset == 0) << 0) | ((MajorOffset == 0) << 1) | ((Stride == int(sizeof(T)) && MajorStride > 0) << 2) | + ((MajorStride == int(sizeof(T)) * num_minor && Stride > 0) << 3)) {} template IMPLOT3D_INLINE double operator()(I idx, int count, int major, int minor, int num_major, int num_minor) const { return (double)GetData(idx, count, major, minor, num_major, num_minor); @@ -938,27 +953,27 @@ template struct IndexerIdxMajorMinor { // Get the data based based on the type switch (Type) { case 15: return Data[idx]; // No offset or stride - case 14: return Data[(((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count]; // Minor offset + case 14: return Data[(Offset + idx) % count]; // Normal offset case 13: return Data[(MajorOffset + idx) % count]; // Major offset - case 12: return Data[(MajorOffset + ((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count]; // Major+minor offset - case 11: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((idx)) * MinorStride); // Minor stride - case 10: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count) * MinorStride); // Minor stride and minor offset - case 9: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + idx) % count) * MinorStride); // Minor stride and major offset - case 8: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + ((minor + MinorOffset) < num_minor ? MinorOffset : (MinorOffset - num_minor)) + idx) % count) * MinorStride); // Minor stride and major + minor offset + case 12: return Data[(MajorOffset + Offset + idx) % count]; // Major+normal offset + case 11: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((idx)) * Stride); // Normal stride + case 10: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((Offset + idx) % count) * Stride); // Normal stride and normal offset + case 9: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + idx) % count) * Stride); // Normal stride and major offset + case 8: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((MajorOffset + Offset + idx) % count) * Stride); // Normal stride and major + normal offset case 7: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + minor * sizeof(T)); // Major stride - case 6: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + ((minor + MinorOffset) % num_minor) * sizeof(T)); // Major stride and minor offset + case 6: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + (minor + Offset) / num_minor) % num_major) * MajorStride + ((minor + Offset) % num_minor) * sizeof(T)); case 5: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + minor * sizeof(T)); // Major stride and major offset - case 4: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + ((minor + MinorOffset) % num_minor) * sizeof(T)); // Major stride and major+minor offset - case 3: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + (size_t)((minor)) * MinorStride); // Major+minor stride - case 2: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + (size_t)((minor + MinorOffset) % num_minor) * MinorStride); // Major+minor stride and minor offset - case 1: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + (size_t)((minor)) * MinorStride); // Major+minor stride and major offset - case 0: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + (size_t)((minor + MinorOffset) % num_minor) * MinorStride); // Major+minor stride and major+minor offset + case 4: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset + (minor + Offset) / num_minor) % num_major) * MajorStride + ((minor + Offset) % num_minor) * sizeof(T)); // Major stride and major+normal offset + case 3: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major)) * MajorStride + (size_t)((minor)) * Stride); // Major+normal stride + case 2: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + (minor + Offset) / num_minor) % num_major) * MajorStride + (size_t)((minor + Offset) % num_minor) * Stride); // Major+normal stride and normal offset + case 1: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset) % num_major) * MajorStride + (size_t)((minor)) * Stride); // Major+normal stride and major offset + case 0: return *(const T*)(const void*)((const unsigned char*)Data + (size_t)((major + MajorOffset + (minor + Offset) / num_minor) % num_major) * MajorStride + (size_t)((minor + Offset) % num_minor) * Stride); // Major+normal stride and major+normal offset default: return T(0); } // clang-format on } const T* const Data; - const int MajorOffset, MinorOffset, MajorStride, MinorStride, Type; + const int MajorOffset, Offset, MajorStride, Stride, Type; }; //----------------------------------------------------------------------------- @@ -973,8 +988,6 @@ template struct Get template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx, int minor, int major) const { return (*this)(idx); } - IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { return point.z; } - const _IndexerX IndexerX; const _IndexerY IndexerY; const _IndexerZ IndexerZ; @@ -983,12 +996,10 @@ template struct Get template struct GetterMinorMajor { GetterMinorMajor(const _Indexer& indexer, int num_major, int num_minor, int count, const ImVec2& major_bounds, const ImVec2& minor_bounds, - ImAxis3D values_axis, ImAxis3D major_axis, ImAxis3D surface_axis) + ImPlane3D plane, bool swap_axis) : Indexer(indexer), NumMajor(num_major), NumMinor(num_minor), Count(count), MajorValueRef((major_bounds.y - major_bounds.x) / (num_major - 1.0f)), MajorValueOffset(major_bounds.x), - MinorValueRef((minor_bounds.y - minor_bounds.x) / (num_minor - 1.0f)), MinorValueOffset(minor_bounds.x), - SurfaceAxis(surface_axis == ImAxis3D_COUNT ? values_axis : surface_axis), - Type((values_axis == major_axis) ? 6 : (values_axis * 2 + (major_axis - (major_axis > values_axis ? 1 : 0)))) {} + MinorValueRef((minor_bounds.y - minor_bounds.x) / (num_minor - 1.0f)), MinorValueOffset(minor_bounds.x), Type(plane * 2 + swap_axis) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { const int major = idx / NumMinor; const int minor = idx % NumMinor; @@ -999,27 +1010,19 @@ template struct GetterMinorMajor { const float minor_value = minor * MinorValueRef + MinorValueOffset; const float value = (float)Indexer(idx, Count, major, minor, NumMajor, NumMinor); switch (Type) { - case 5: return ImPlot3DPoint(minor_value, major_value, value); // Z-Values + Y-Major - case 4: return ImPlot3DPoint(major_value, minor_value, value); // Z-Values + X-Major - case 3: return ImPlot3DPoint(minor_value, value, major_value); // Y-Values + Z-Major - case 2: return ImPlot3DPoint(major_value, value, minor_value); // Y-Values + X-Major - case 1: return ImPlot3DPoint(value, minor_value, major_value); // X-Values + Z-Major - case 0: return ImPlot3DPoint(value, major_value, minor_value); // X-Values + Y-Major + case 5: return ImPlot3DPoint(major_value, minor_value, value); // X-Major + Y-Minor + Z-Values + case 4: return ImPlot3DPoint(minor_value, major_value, value); // X-Minor + Y-Major + Z-Values + case 3: return ImPlot3DPoint(major_value, value, minor_value); // X-Major + Y-Values + Z-Minor + case 2: return ImPlot3DPoint(minor_value, value, major_value); // X-Minor + Y-Values + Z-Major + case 1: return ImPlot3DPoint(value, major_value, minor_value); // X-Values + Y-Major + Z-Minor + case 0: return ImPlot3DPoint(value, minor_value, major_value); // X-Values + Y-Minor + Z-Major default: return ImPlot3DPoint(0, 0, 0); } } - IMPLOT3D_INLINE float GetSurfaceValue(const ImPlot3DPoint& point) const { - switch (SurfaceAxis) { - case 0: return point.x; // X-Values - case 1: return point.y; // Y-Values - default: return point.z; // Z-Values(2) - } - } const _Indexer& Indexer; const int NumMajor, NumMinor, Count; const float MajorValueRef, MajorValueOffset, MinorValueRef, MinorValueOffset; - const ImAxis3D SurfaceAxis; const int Type; }; @@ -1424,8 +1427,15 @@ template void PlotSurfaceEx(const char* label_id, const _Gett // Render fill if (getter.Count >= 4 && n.RenderFill && !ImHasFlag(flags, ImPlot3DSurfaceFlags_NoFill)) { + ImPlane3D plane = ImPlane3D_XY; + if (ImHasFlag(flags, ImPlot3DSurfaceFlags_PlaneXZ)) + plane = ImPlane3D_XZ; + else if (ImHasFlag(flags, ImPlot3DSurfaceFlags_PlaneYZ)) + plane = ImPlane3D_YZ; + + SurfacePlotPlaneGetter plane_getter(plane); const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]); - RenderPrimitives(getter, minor_count, major_count, col_fill, scale_min, scale_max); + RenderPrimitives(getter, minor_count, major_count, col_fill, scale_min, scale_max, plane_getter); } // Render lines @@ -1456,18 +1466,22 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, co } IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, double scale_max, - const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, ImAxis3D values_axis, - ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, int major_stride, ImAxis3D surface_axis) { - IM_ASSERT_USER_ERROR(values_axis != major_axis, "The values axis and major axis needs to be two different values"); + ImPlot3DSurfaceFlags flags, const ImVec2& minor_bounds, const ImVec2& major_bounds, int offset, int stride, + int major_offset, int major_stride) { int count = major_count * minor_count; if (count < 4) return; // Create the getter and the indexer that will be passed to PlotSurfaceEx. The getter and the indexer will produce the correct information based // on the indexes passed in - IndexerIdxMajorMinor indexer(values, major_count, minor_count, major_offset, minor_offset, - (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), minor_stride); - GetterMinorMajor> getter(indexer, major_count, minor_count, count, major_bounds, minor_bounds, values_axis, major_axis, - surface_axis); + IndexerIdxMajor indexer(values, major_count, minor_count, major_offset, offset, + (major_stride == IMPLOT3D_DEFAULT_MAJOR_STRIDE ? (sizeof(T) * minor_count) : major_stride), stride); + ImPlane3D plane = ImPlane3D_XY; + if (ImHasFlag(flags, ImPlot3DSurfaceFlags_PlaneXZ)) + plane = ImPlane3D_XZ; + else if (ImHasFlag(flags, ImPlot3DSurfaceFlags_PlaneYZ)) + plane = ImPlane3D_YZ; + const bool swap_axis = ImHasFlag(flags, ImPlot3DSurfaceFlags_SwapAxes); + GetterMinorMajor> getter(indexer, major_count, minor_count, count, major_bounds, minor_bounds, plane, swap_axis); return PlotSurfaceEx(label_id, getter, minor_count, major_count, scale_min, scale_max, flags); } @@ -1475,9 +1489,8 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_c template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, \ double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); \ template IMPLOT3D_API void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, \ - double scale_max, const ImVec2& minor_bounds, const ImVec2& major_bounds, ImPlot3DSurfaceFlags flags, \ - ImAxis3D values_axis, ImAxis3D major_axis, int minor_offset, int major_offset, int minor_stride, \ - int major_stride, ImAxis3D surface_axis); + double scale_max, ImPlot3DSurfaceFlags flags, const ImVec2& minor_bounds, const ImVec2& major_bounds, \ + int offset, int stride, int major_offset, int major_stride); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO From c4faa99457323ae6f9294cc7dde89937be27f323 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:30:54 +0200 Subject: [PATCH 14/15] refactor: Change x and y count to minor and major count and add some comments - Depending to what the `ImPlot3DSurfaceFlags_Plane(XY/YZ/XZ)` flag is set to, the minor/major count values changes based on the flag set. Rename so that there is no potential confusion - Added some much needed comments for the new flags --- implot3d.h | 10 +++++----- implot3d_items.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/implot3d.h b/implot3d.h index 42c2ed1..1f5790d 100644 --- a/implot3d.h +++ b/implot3d.h @@ -231,10 +231,10 @@ enum ImPlot3DSurfaceFlags_ { ImPlot3DSurfaceFlags_NoMarkers = 1 << 12, // No markers will be rendered // The plane to use for the surface plot - ImPlot3DSurfaceFlags_PlaneXY = 1 << 0, - ImPlot3DSurfaceFlags_PlaneXZ = 1 << 13, - ImPlot3DSurfaceFlags_PlaneYZ = 1 << 14, - ImPlot3DSurfaceFlags_SwapAxes = 1 << 15, + ImPlot3DSurfaceFlags_PlaneXY = 1 << 0, // Use the XY plane and Z values for surface. Default behavior + ImPlot3DSurfaceFlags_PlaneXZ = 1 << 13, // Use the XZ plane and Y values for surface + ImPlot3DSurfaceFlags_PlaneYZ = 1 << 14, // Use the YZ plane and X values for surface + ImPlot3DSurfaceFlags_SwapAxes = 1 << 15, // Swap the minor and major axis. Only has an effect on the PlotSurface function where only the values are passed in ImPlot3DSurfaceFlags_PlaneMask_ = ImPlot3DSurfaceFlags_PlaneXY | ImPlot3DSurfaceFlags_PlaneXZ | ImPlot3DSurfaceFlags_PlaneYZ, }; @@ -461,7 +461,7 @@ IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const // Plot the surface defined by a grid of vertices. The grid is defined by the x and y arrays, and the z array contains the height of each vertex. A // total of x_count * y_count vertices are expected for each array. Leave #scale_min and #scale_max both at 0 for automatic color scaling, or set them // to a predefined range -IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min = 0.0, +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int minor_count, int major_count, double scale_min = 0.0, double scale_max = 0.0, ImPlot3DSurfaceFlags flags = 0, int offset = 0, int stride = sizeof(T)); IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min = 0.0, double scale_max = 0.0, ImPlot3DSurfaceFlags flags = 0, const ImVec2& minor_bounds = ImVec2(-1, 1), const ImVec2& major_bounds = ImVec2(-1, 1), diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 67cb019..9c7fb28 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -1455,14 +1455,14 @@ template void PlotSurfaceEx(const char* label_id, const _Gett } } -IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min, +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int minor_count, int major_count, double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride) { - int count = x_count * y_count; + int count = minor_count * major_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, scale_min, scale_max, flags); + return PlotSurfaceEx(label_id, getter, minor_count, major_count, scale_min, scale_max, flags); } IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, double scale_max, @@ -1486,7 +1486,7 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* values, int minor_c } #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, \ + template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int minor_count, int major_count, \ double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); \ template IMPLOT3D_API void PlotSurface(const char* label_id, const T* values, int minor_count, int major_count, double scale_min, \ double scale_max, ImPlot3DSurfaceFlags flags, const ImVec2& minor_bounds, const ImVec2& major_bounds, \ From 39fab8dca55faf06921243ca99c14ec0f5615c04 Mon Sep 17 00:00:00 2001 From: ACvanWyk <66202856+ACvanWyk@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:43:56 +0200 Subject: [PATCH 15/15] fix: Make the `ImPlot3DSurfaceFlags_PlaneXY` the default - If neither the `ImPlot3DSurfaceFlags_PlaneXZ` and the `ImPlot3DSurfaceFlags_PlaneYZ` flag is specified then use `ImPlot3DSurfaceFlags_PlaneXY`. --- implot3d.h | 2 +- implot3d_demo.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/implot3d.h b/implot3d.h index 1f5790d..0cc502f 100644 --- a/implot3d.h +++ b/implot3d.h @@ -231,7 +231,7 @@ enum ImPlot3DSurfaceFlags_ { ImPlot3DSurfaceFlags_NoMarkers = 1 << 12, // No markers will be rendered // The plane to use for the surface plot - ImPlot3DSurfaceFlags_PlaneXY = 1 << 0, // Use the XY plane and Z values for surface. Default behavior + ImPlot3DSurfaceFlags_PlaneXY = 0, // Use the XY plane and Z values for surface. Default behavior. Use for None flag ImPlot3DSurfaceFlags_PlaneXZ = 1 << 13, // Use the XZ plane and Y values for surface ImPlot3DSurfaceFlags_PlaneYZ = 1 << 14, // Use the YZ plane and X values for surface ImPlot3DSurfaceFlags_SwapAxes = 1 << 15, // Swap the minor and major axis. Only has an effect on the PlotSurface function where only the values are passed in diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index aa33d37..e19bca4 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -833,12 +833,12 @@ void DemoSurfaceAxisOffsetStride() { ImGui::SliderInt("Major Stride", &major_stride, -10, 10); // Select flags for the surface plot + // Default to using ImPlot3DSurfaceFlags_PlaneXY flag. If neither the ImPlot3DSurfaceFlags_PlaneXZ and the ImPlot3DSurfaceFlags_PlaneYZ flag is + // set then use ImPlot3DSurfaceFlags_PlaneXY static ImPlot3DSurfaceFlags flags = ImPlot3DSurfaceFlags_NoMarkers | ImPlot3DSurfaceFlags_PlaneXY; CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoMarkers); - if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneXY", &flags, ImPlot3DSurfaceFlags_PlaneXY)) - flags &= ~(ImPlot3DSurfaceFlags_PlaneMask_ & ~ImPlot3DSurfaceFlags_PlaneXY); if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneXZ", &flags, ImPlot3DSurfaceFlags_PlaneXZ)) flags &= ~(ImPlot3DSurfaceFlags_PlaneMask_ & ~ImPlot3DSurfaceFlags_PlaneXZ); if (ImGui::CheckboxFlags("ImPlot3DSurfaceFlags_PlaneYZ", &flags, ImPlot3DSurfaceFlags_PlaneYZ))