From 99835a9fa6eaf60fda16b859a29cb6cb998f6186 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 05:03:26 +0100 Subject: [PATCH 1/9] feat: add BoxAspect to plot struct --- implot3d.cpp | 10 ++++++++++ implot3d.h | 2 ++ implot3d_internal.h | 8 +++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 19d1c90..2a80172 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -1480,6 +1480,16 @@ void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { legend.PreviousFlags = flags; } +void SetupBoxAspect(float x, float y, float z) { + ImPlot3DContext& gp = *GImPlot3D; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "SetupBoxAspect() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); + ImPlot3DPlot& plot = *gp.CurrentPlot; + plot.BoxAspect = ImPlot3DPoint(x, y, z); + float max = ImMax(x, ImMax(y, z)); + plot.BoxAspect /= max; +} + //----------------------------------------------------------------------------- // [SECTION] Plot Utils //----------------------------------------------------------------------------- diff --git a/implot3d.h b/implot3d.h index 5b9e35e..fcf3bcc 100644 --- a/implot3d.h +++ b/implot3d.h @@ -372,6 +372,8 @@ IMPLOT3D_API void SetupAxesLimits(double x_min, double x_max, double y_min, doub IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0); +IMPLOT3D_API void SetupBoxAspect(float x, float y, float z); + //----------------------------------------------------------------------------- // [SECTION] Plot Items //----------------------------------------------------------------------------- diff --git a/implot3d_internal.h b/implot3d_internal.h index dc48f7e..99c1d8c 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -521,9 +521,10 @@ struct ImPlot3DPlot { 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]; + // Plot box state + ImPlot3DQuat Rotation; // Current rotation quaternion + ImPlot3DAxis Axes[3]; // X, Y, Z axes + ImPlot3DPoint BoxAspect; // Aspect ratio of the plot box // Animation float AnimationTime; // Remaining animation time ImPlot3DQuat RotationAnimationEnd; // End rotation for animation @@ -551,6 +552,7 @@ struct ImPlot3DPlot { Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f); for (int i = 0; i < 3; i++) Axes[i] = ImPlot3DAxis(); + BoxAspect = ImPlot3DPoint(1.0f, 1.0f, 1.0f); AnimationTime = 0.0f; RotationAnimationEnd = Rotation; SetupLocked = false; From cfac4c7ba262e46c274c198fecb0b7823db21c87 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 06:13:24 +0100 Subject: [PATCH 2/9] feat: setup box scale chore: make sure obsolete functions are not used feat: apply box aspect ratio feat: box aspect ratio/scale demo --- implot3d.cpp | 47 ++++++++++++++++++++++++++++++--------------- implot3d.h | 10 ++++++++-- implot3d_demo.cpp | 28 +++++++++++++++++++++++++++ implot3d_internal.h | 7 +++++-- 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 2a80172..4345ae3 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -48,6 +48,11 @@ #define IMGUI_DEFINE_MATH_OPERATORS #endif +// We define this to avoid accidentally using the deprecated API +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#endif + #include "implot3d.h" #include "implot3d_internal.h" @@ -1465,6 +1470,25 @@ void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, dou GImPlot3D->CurrentPlot->FitThisFrame = false; } +void SetupBoxAspect(float x, float y, float z) { + ImPlot3DContext& gp = *GImPlot3D; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "SetupBoxAspect() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); + IM_ASSERT_USER_ERROR(x > 0.0f && y > 0.0f && z > 0.0f, "SetupBoxAspect() requires all aspect ratios to be greater than 0!"); + ImPlot3DPlot& plot = *gp.CurrentPlot; + plot.BoxAspect = ImPlot3DPoint(x, y, z); + float max = ImMax(x, ImMax(y, z)); + plot.BoxAspect /= max; +} + +void SetupBoxScale(float scale) { + ImPlot3DContext& gp = *GImPlot3D; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "SetupBoxScale() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); + IM_ASSERT_USER_ERROR(scale > 0.0f, "SetupBoxScale() requires the scale to greater than 0!"); + gp.CurrentPlot->BoxScale = ImMax(0.1f, scale); // Prevent negative or zero scaling +} + void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { ImPlot3DContext& gp = *GImPlot3D; IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, @@ -1480,16 +1504,6 @@ void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { legend.PreviousFlags = flags; } -void SetupBoxAspect(float x, float y, float z) { - ImPlot3DContext& gp = *GImPlot3D; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, - "SetupBoxAspect() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); - ImPlot3DPlot& plot = *gp.CurrentPlot; - plot.BoxAspect = ImPlot3DPoint(x, y, z); - float max = ImMax(x, ImMax(y, z)); - plot.BoxAspect /= max; -} - //----------------------------------------------------------------------------- // [SECTION] Plot Utils //----------------------------------------------------------------------------- @@ -1637,7 +1651,7 @@ ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) { ImPlot3DPoint ndc_point; for (int i = 0; i < 3; i++) - ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]); + ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]) * plot.BoxAspect[i]; return ndc_point; } @@ -1649,7 +1663,7 @@ ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) { ImPlot3DPoint plot_point; for (int i = 0; i < 3; i++) - plot_point[i] = plot.Axes[i].NDCToPlot(point[i]); + plot_point[i] = plot.Axes[i].NDCToPlot(point[i]) / plot.BoxAspect[i]; return plot_point; } @@ -1659,9 +1673,8 @@ ImVec2 NDCToPixels(const ImPlot3DPoint& point) { 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); + ImPlot3DPoint point_pix = plot.GetBoxZoom() * (plot.Rotation * point); point_pix.y *= -1.0f; // Invert y-axis point_pix.x += center.x; point_pix.y += center.y; @@ -1676,7 +1689,7 @@ ImPlot3DRay PixelsToNDCRay(const ImVec2& pix) { SetupLock(); // Calculate zoom factor and plot center - float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f; + float zoom = plot.GetBoxZoom(); ImVec2 center = plot.PlotRect.GetCenter(); // Undo screen transformations to get back to NDC space @@ -3068,6 +3081,10 @@ void ImPlot3DPlot::SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max) Axes[2].SetRange(min.z, max.z); } +float ImPlot3DPlot::GetBoxZoom() const { + return ImMin(PlotRect.GetWidth(), PlotRect.GetHeight()) / 1.8f * BoxScale; +} + //----------------------------------------------------------------------------- // [SECTION] ImPlot3DStyle //----------------------------------------------------------------------------- diff --git a/implot3d.h b/implot3d.h index fcf3bcc..d1f7156 100644 --- a/implot3d.h +++ b/implot3d.h @@ -370,10 +370,16 @@ IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char // 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); - +// Sets the aspect ratio between the X, Y, and Z axes for the 3D plot box (values must be positive). +// An aspect ratio of 1:1:1 represents equal ratio across all axes (default plot cube). IMPLOT3D_API void SetupBoxAspect(float x, float y, float z); +// Scales the entire 3D plot box uniformly along all axes. A scale of 1.0 is the default. +// Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it. +IMPLOT3D_API void SetupBoxScale(float scale); + +IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0); + //----------------------------------------------------------------------------- // [SECTION] Plot Items //----------------------------------------------------------------------------- diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index baecedc..7c2787e 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -15,11 +15,17 @@ // [SECTION] User Namespace // [SECTION] Helpers // [SECTION] Plots +// [SECTION] Axes // [SECTION] Custom // [SECTION] Demo Window // [SECTION] Style Editor // [SECTION] User Namespace Implementation +// We define this to avoid accidentally using the deprecated API +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#endif + #include "implot3d.h" #include "implot3d_internal.h" @@ -535,6 +541,24 @@ void DemoNaNValues() { } } +//----------------------------------------------------------------------------- +// [SECTION] Axes +//----------------------------------------------------------------------------- + +void DemoBoxAspectScale() { + static float aspect[3] = {1.0f, 1.0f, 1.0f}; + static float scale = 1.0f; + + ImGui::SliderFloat3("Box Aspect Ratio", aspect, 0.1f, 5.0f, "%.1f"); + ImGui::SliderFloat("Box Scale", &scale, 0.1f, 5.0f, "%.1f"); + + if (ImPlot3D::BeginPlot("##BoxAspectRatio")) { + ImPlot3D::SetupBoxAspect(aspect[0], aspect[1], aspect[2]); + ImPlot3D::SetupBoxScale(scale); + ImPlot3D::EndPlot(); + } +} + //----------------------------------------------------------------------------- // [SECTION] Custom //----------------------------------------------------------------------------- @@ -721,6 +745,10 @@ void ShowDemoWindow(bool* p_open) { DemoHeader("NaN Values", DemoNaNValues); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Axes")) { + DemoHeader("Box Aspect & Scale", DemoBoxAspectScale); + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Custom")) { DemoHeader("Custom Styles", DemoCustomStyles); DemoHeader("Custom Rendering", DemoCustomRendering); diff --git a/implot3d_internal.h b/implot3d_internal.h index 99c1d8c..c4ea5ec 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -521,10 +521,11 @@ struct ImPlot3DPlot { 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 - // Plot box state + // Rotation & axes ImPlot3DQuat Rotation; // Current rotation quaternion ImPlot3DAxis Axes[3]; // X, Y, Z axes - ImPlot3DPoint BoxAspect; // Aspect ratio of the plot box + ImPlot3DPoint BoxAspect; // Aspect ratio of the X, Y, Z axes of the plot box + float BoxScale; // Scales the plot box uniformly along all axes // Animation float AnimationTime; // Remaining animation time ImPlot3DQuat RotationAnimationEnd; // End rotation for animation @@ -553,6 +554,7 @@ struct ImPlot3DPlot { for (int i = 0; i < 3; i++) Axes[i] = ImPlot3DAxis(); BoxAspect = ImPlot3DPoint(1.0f, 1.0f, 1.0f); + BoxScale = 1.0f; AnimationTime = 0.0f; RotationAnimationEnd = Rotation; SetupLocked = false; @@ -578,6 +580,7 @@ struct ImPlot3DPlot { ImPlot3DPoint RangeMax() const; ImPlot3DPoint RangeCenter() const; void SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max); + float GetBoxZoom() const; }; struct ImPlot3DContext { From 412862157d913b87eabe12aa6069da56b3515956 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 06:21:04 +0100 Subject: [PATCH 3/9] fix: ensure tick mark size is independent of box scale --- implot3d.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 4345ae3..1d41382 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -713,8 +713,8 @@ void RenderTickMarks(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPl tick_dir.Normalize(); // Tick lengths in NDC units - const float major_size_ndc = 0.06f; - const float minor_size_ndc = 0.03f; + const float major_size_ndc = 0.06f / plot.BoxScale; + const float minor_size_ndc = 0.03f / plot.BoxScale; for (int t = 0; t < axis.Ticker.TickCount(); ++t) { const ImPlot3DTick& tick = axis.Ticker.Ticks[t]; From 83c6b39f30b90ed29049cbb13dbaa65fb9bda0a5 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 06:57:13 +0100 Subject: [PATCH 4/9] feat: dynamic number of ticks --- implot3d.cpp | 20 ++++++++++---------- implot3d_demo.cpp | 4 ++-- implot3d_internal.h | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 1d41382..6f3ad34 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -1073,11 +1073,11 @@ double NiceNum(double x, bool round) { return nf * ImPow(10.0, expv); } -void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data) { +void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data) { if (range.Min == range.Max) return; - const int nMinor = 5; - const int nMajor = 3; + const int nMinor = ImMin(ImMax(1, (int)IM_ROUND(pixels / 30.0f)), 5); + const int nMajor = ImMax(2, (int)IM_ROUND(pixels / 80.0f)); 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); @@ -1085,10 +1085,9 @@ void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3 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); + const int idx0 = ticker.TickCount(); // Ticker may have user custom ticks for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) { - // is this zero? combat zero formatting issues + // Is this zero? combat zero formatting issues if (major - interval < 0 && major + interval > 0) major = 0; if (range.Contains((float)major)) { @@ -1096,12 +1095,12 @@ void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3 first_major_idx = ticker.TickCount(); first_major_set = true; } - total_size += ticker.AddTick(major, true, true, formatter, formatter_data).LabelSize; + ticker.AddTick(major, true, true, formatter, formatter_data); } for (int i = 1; i < nMinor; ++i) { double minor = major + i * interval / nMinor; if (range.Contains((float)minor)) { - total_size += ticker.AddTick(minor, false, true, formatter, formatter_data).LabelSize; + ticker.AddTick(minor, false, true, formatter, formatter_data); } } } @@ -1486,7 +1485,7 @@ void SetupBoxScale(float scale) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, "SetupBoxScale() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); IM_ASSERT_USER_ERROR(scale > 0.0f, "SetupBoxScale() requires the scale to greater than 0!"); - gp.CurrentPlot->BoxScale = ImMax(0.1f, scale); // Prevent negative or zero scaling + gp.CurrentPlot->BoxScale = scale; } void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { @@ -2083,7 +2082,8 @@ void SetupLock() { 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); + float pixels = plot.GetBoxZoom() * plot.BoxAspect[i]; + axis.Locator(axis.Ticker, axis.Range, pixels, axis.Formatter, axis.FormatterData); } // Render title diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 7c2787e..41fb69d 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -549,8 +549,8 @@ void DemoBoxAspectScale() { static float aspect[3] = {1.0f, 1.0f, 1.0f}; static float scale = 1.0f; - ImGui::SliderFloat3("Box Aspect Ratio", aspect, 0.1f, 5.0f, "%.1f"); - ImGui::SliderFloat("Box Scale", &scale, 0.1f, 5.0f, "%.1f"); + ImGui::SliderFloat3("Box Aspect Ratio", aspect, 0.1f, 5.0f, "%.2f"); + ImGui::SliderFloat("Box Scale", &scale, 0.1f, 3.0f, "%.2f"); if (ImPlot3D::BeginPlot("##BoxAspectRatio")) { ImPlot3D::SetupBoxAspect(aspect[0], aspect[1], aspect[2]); diff --git a/implot3d_internal.h b/implot3d_internal.h index c4ea5ec..5912ff4 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -109,7 +109,7 @@ struct ImPlot3DTicker; // [SECTION] Callbacks //------------------------------------------------------------------------------ -typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data); +typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data); //----------------------------------------------------------------------------- // [SECTION] Structs @@ -686,7 +686,7 @@ 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); +void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float pixels, ImPlot3DFormatter formatter, void* formatter_data); } // namespace ImPlot3D From c28a2336ed6a8c1b658c60cf99669da2f85e1aae Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 07:45:58 +0100 Subject: [PATCH 5/9] refactor: merge box aspect & scale --- implot3d.cpp | 46 +++++++++++++++++++++++++-------------------- implot3d.h | 9 ++------- implot3d_demo.cpp | 15 ++++++--------- implot3d_internal.h | 12 +++++------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 6f3ad34..49b9603 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -713,8 +713,8 @@ void RenderTickMarks(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPl tick_dir.Normalize(); // Tick lengths in NDC units - const float major_size_ndc = 0.06f / plot.BoxScale; - const float minor_size_ndc = 0.03f / plot.BoxScale; + 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]; @@ -1235,6 +1235,22 @@ void ShowPlotContextMenu(ImPlot3DPlot& plot) { } ImGui::Separator(); + + if ((ImGui::BeginMenu("Box"))) { + ImGui::PushItemWidth(75); + float temp_scale[3] = {plot.BoxScale[0], plot.BoxScale[1], plot.BoxScale[2]}; + if (ImGui::DragFloat("Scale X", &temp_scale[0], 0.01f, 0.1f, 3.0f)) + plot.BoxScale[0] = ImMax(temp_scale[0], 0.01f); + if (ImGui::DragFloat("Scale Y", &temp_scale[1], 0.01f, 0.1f, 3.0f)) + plot.BoxScale[1] = ImMax(temp_scale[1], 0.01f); + if (ImGui::DragFloat("Scale Z", &temp_scale[2], 0.01f, 0.1f, 3.0f)) + plot.BoxScale[2] = ImMax(temp_scale[2], 0.01f); + ImGui::PopItemWidth(); + ImGui::EndMenu(); + } + + ImGui::Separator(); + if ((ImGui::BeginMenu("Legend"))) { if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend))) ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend); @@ -1469,23 +1485,13 @@ void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, dou GImPlot3D->CurrentPlot->FitThisFrame = false; } -void SetupBoxAspect(float x, float y, float z) { - ImPlot3DContext& gp = *GImPlot3D; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, - "SetupBoxAspect() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); - IM_ASSERT_USER_ERROR(x > 0.0f && y > 0.0f && z > 0.0f, "SetupBoxAspect() requires all aspect ratios to be greater than 0!"); - ImPlot3DPlot& plot = *gp.CurrentPlot; - plot.BoxAspect = ImPlot3DPoint(x, y, z); - float max = ImMax(x, ImMax(y, z)); - plot.BoxAspect /= max; -} - -void SetupBoxScale(float scale) { +void SetupBoxScale(float x, float y, float z) { ImPlot3DContext& gp = *GImPlot3D; IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, "SetupBoxScale() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!"); - IM_ASSERT_USER_ERROR(scale > 0.0f, "SetupBoxScale() requires the scale to greater than 0!"); - gp.CurrentPlot->BoxScale = scale; + IM_ASSERT_USER_ERROR(x > 0.0f && y > 0.0f && z > 0.0f, "SetupBoxScale() requires all aspect ratios to be greater than 0!"); + ImPlot3DPlot& plot = *gp.CurrentPlot; + plot.BoxScale = ImPlot3DPoint(x, y, z); } void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { @@ -1650,7 +1656,7 @@ ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) { ImPlot3DPoint ndc_point; for (int i = 0; i < 3; i++) - ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]) * plot.BoxAspect[i]; + ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]) * plot.BoxScale[i]; return ndc_point; } @@ -1662,7 +1668,7 @@ ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) { ImPlot3DPoint plot_point; for (int i = 0; i < 3; i++) - plot_point[i] = plot.Axes[i].NDCToPlot(point[i]) / plot.BoxAspect[i]; + plot_point[i] = plot.Axes[i].NDCToPlot(point[i]) / plot.BoxScale[i]; return plot_point; } @@ -2082,7 +2088,7 @@ void SetupLock() { for (int i = 0; i < 3; i++) { ImPlot3DAxis& axis = plot.Axes[i]; axis.Ticker.Reset(); - float pixels = plot.GetBoxZoom() * plot.BoxAspect[i]; + float pixels = plot.GetBoxZoom() * plot.BoxScale[i]; axis.Locator(axis.Ticker, axis.Range, pixels, axis.Formatter, axis.FormatterData); } @@ -3082,7 +3088,7 @@ void ImPlot3DPlot::SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max) } float ImPlot3DPlot::GetBoxZoom() const { - return ImMin(PlotRect.GetWidth(), PlotRect.GetHeight()) / 1.8f * BoxScale; + return ImMin(PlotRect.GetWidth(), PlotRect.GetHeight()) / 1.8f; } //----------------------------------------------------------------------------- diff --git a/implot3d.h b/implot3d.h index d1f7156..b0b3917 100644 --- a/implot3d.h +++ b/implot3d.h @@ -370,13 +370,8 @@ IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char // 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); -// Sets the aspect ratio between the X, Y, and Z axes for the 3D plot box (values must be positive). -// An aspect ratio of 1:1:1 represents equal ratio across all axes (default plot cube). -IMPLOT3D_API void SetupBoxAspect(float x, float y, float z); - -// Scales the entire 3D plot box uniformly along all axes. A scale of 1.0 is the default. -// Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it. -IMPLOT3D_API void SetupBoxScale(float scale); +// Sets the plot box X/Y/Z scale. A scale of 1.0 is the default. Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it. +IMPLOT3D_API void SetupBoxScale(float x, float y, float z); IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0); diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 41fb69d..7b9ee6e 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -545,16 +545,13 @@ void DemoNaNValues() { // [SECTION] Axes //----------------------------------------------------------------------------- -void DemoBoxAspectScale() { - static float aspect[3] = {1.0f, 1.0f, 1.0f}; - static float scale = 1.0f; +void DemoBoxScale() { + static float scale[3] = {1.0f, 1.0f, 1.0f}; - ImGui::SliderFloat3("Box Aspect Ratio", aspect, 0.1f, 5.0f, "%.2f"); - ImGui::SliderFloat("Box Scale", &scale, 0.1f, 3.0f, "%.2f"); + ImGui::SliderFloat3("Box Scale", scale, 0.1f, 5.0f, "%.2f"); - if (ImPlot3D::BeginPlot("##BoxAspectRatio")) { - ImPlot3D::SetupBoxAspect(aspect[0], aspect[1], aspect[2]); - ImPlot3D::SetupBoxScale(scale); + if (ImPlot3D::BeginPlot("##BoxScale")) { + ImPlot3D::SetupBoxScale(scale[0], scale[1], scale[2]); ImPlot3D::EndPlot(); } } @@ -746,7 +743,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Axes")) { - DemoHeader("Box Aspect & Scale", DemoBoxAspectScale); + DemoHeader("Box Scale", DemoBoxScale); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Custom")) { diff --git a/implot3d_internal.h b/implot3d_internal.h index 5912ff4..30bc9e1 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -521,11 +521,10 @@ struct ImPlot3DPlot { 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; // Current rotation quaternion - ImPlot3DAxis Axes[3]; // X, Y, Z axes - ImPlot3DPoint BoxAspect; // Aspect ratio of the X, Y, Z axes of the plot box - float BoxScale; // Scales the plot box uniformly along all axes + // Rotation & axes & box + ImPlot3DQuat Rotation; // Current rotation quaternion + ImPlot3DAxis Axes[3]; // X, Y, Z axes + ImPlot3DPoint BoxScale; // Scale factor for plot box X, Y, Z axes // Animation float AnimationTime; // Remaining animation time ImPlot3DQuat RotationAnimationEnd; // End rotation for animation @@ -553,8 +552,7 @@ struct ImPlot3DPlot { Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f); for (int i = 0; i < 3; i++) Axes[i] = ImPlot3DAxis(); - BoxAspect = ImPlot3DPoint(1.0f, 1.0f, 1.0f); - BoxScale = 1.0f; + BoxScale = ImPlot3DPoint(1.0f, 1.0f, 1.0f); AnimationTime = 0.0f; RotationAnimationEnd = Rotation; SetupLocked = false; From 204ee5f5637742eaead89d3cc20454fc4328b2eb Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 07:53:57 +0100 Subject: [PATCH 6/9] feat: add line plot to box scale demo --- implot3d_demo.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 7b9ee6e..239815d 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -546,12 +546,21 @@ void DemoNaNValues() { //----------------------------------------------------------------------------- void DemoBoxScale() { - static float scale[3] = {1.0f, 1.0f, 1.0f}; + constexpr int N = 100; + float xs[N], ys[N], zs[N]; + for (int i = 0; i < N; ++i) { + float t = i / (float)(N - 1); + xs[i] = sinf(t * 2.0f * IM_PI); + ys[i] = cosf(t * 4.0f * IM_PI); + zs[i] = t * 2.0f - 1.0f; + } - ImGui::SliderFloat3("Box Scale", scale, 0.1f, 5.0f, "%.2f"); + static float scale[3] = {1.0f, 1.0f, 1.0f}; + ImGui::SliderFloat3("Box Scale", scale, 0.1f, 2.0f, "%.2f"); if (ImPlot3D::BeginPlot("##BoxScale")) { ImPlot3D::SetupBoxScale(scale[0], scale[1], scale[2]); + ImPlot3D::PlotLine("3D Curve", xs, ys, zs, N); ImPlot3D::EndPlot(); } } From a2201e59ae3792baba138ffd9e6d04c5258ba7c9 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Fri, 3 Jan 2025 08:58:49 +0100 Subject: [PATCH 7/9] fix: scaled box translation --- implot3d.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 49b9603..45ce4ab 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -1581,22 +1581,22 @@ ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask) { 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, plot.Axes); // Calculate intersection point with the planes - ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5f : -0.5f); + ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5f * plot.BoxScale[plane] : -0.5f * plot.BoxScale[plane]); if (P.IsNaN()) return P; + // Helper lambda to check if point P is within the plot box + auto InRange = [&](const ImPlot3DPoint& P) { + return P.x >= -0.5f * plot.BoxScale.x && P.x <= 0.5f * plot.BoxScale.x && + P.y >= -0.5f * plot.BoxScale.y && P.y <= 0.5f * plot.BoxScale.y && + P.z >= -0.5f * plot.BoxScale.z && P.z <= 0.5f * plot.BoxScale.z; + }; + // Handle mask (if one of the intersections is out of range, set it to NAN) if (mask) { switch (plane) { @@ -1848,11 +1848,11 @@ void HandleInput(ImPlot3DPlot& plot) { 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; + float zoom = plot.GetBoxZoom(); ImPlot3DPoint delta_NDC = plot.Rotation.Inverse() * (delta_pixels / zoom); // Convert delta to plot space - ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin()); + ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin()) / plot.BoxScale; // Adjust delta for inverted axes for (int i = 0; i < 3; i++) { From 8b266f232241c91a176bacb0838e5078b55384a5 Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 4 Jan 2025 14:04:57 +0100 Subject: [PATCH 8/9] fix: scaled box zoom --- implot3d.cpp | 28 ++++++++++++++-------------- implot3d_internal.h | 2 -- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 45ce4ab..f71128c 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -1655,8 +1655,13 @@ ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) { SetupLock(); ImPlot3DPoint ndc_point; - for (int i = 0; i < 3; i++) - ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]) * plot.BoxScale[i]; + for (int i = 0; i < 3; i++) { + ImPlot3DAxis& axis = plot.Axes[i]; + float ndc_range = 0.5f * plot.BoxScale[i]; + float t = (point[i] - axis.Range.Min) / (axis.Range.Max - axis.Range.Min); + t *= plot.BoxScale[i]; + ndc_point[i] = ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_Invert) ? (ndc_range - t) : (t - ndc_range); + } return ndc_point; } @@ -1667,8 +1672,13 @@ ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) { SetupLock(); ImPlot3DPoint plot_point; - for (int i = 0; i < 3; i++) - plot_point[i] = plot.Axes[i].NDCToPlot(point[i]) / plot.BoxScale[i]; + for (int i = 0; i < 3; i++) { + ImPlot3DAxis& axis = plot.Axes[i]; + float ndc_range = 0.5f * plot.BoxScale[i]; + float t = ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_Invert) ? (ndc_range - point[i]) : (point[i] + ndc_range); + t /= plot.BoxScale[i]; + plot_point[i] = axis.Range.Min + t * (axis.Range.Max - axis.Range.Min); + } return plot_point; } @@ -3045,16 +3055,6 @@ void ImPlot3DAxis::ApplyFit() { FitExtents.Max = -HUGE_VAL; } -float ImPlot3DAxis::PlotToNDC(float value) const { - float t = (value - Range.Min) / (Range.Max - Range.Min); - return ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_Invert) ? (0.5f - t) : (t - 0.5f); -} - -float ImPlot3DAxis::NDCToPlot(float value) const { - float t = ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_Invert) ? (0.5f - value) : (value + 0.5f); - return Range.Min + t * (Range.Max - Range.Min); -} - //----------------------------------------------------------------------------- // [SECTION] ImPlot3DPlot //----------------------------------------------------------------------------- diff --git a/implot3d_internal.h b/implot3d_internal.h index 30bc9e1..251f272 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -505,8 +505,6 @@ struct ImPlot3DAxis { 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 From 280146ef42642da5ca865ce57f2e2e587abf378e Mon Sep 17 00:00:00 2001 From: Breno Cunha Queiroz Date: Sat, 4 Jan 2025 14:12:50 +0100 Subject: [PATCH 9/9] fix: scaled box axis labels --- implot3d.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index f71128c..020b79b 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -829,7 +829,6 @@ void RenderTickLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImP } 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()) @@ -846,12 +845,13 @@ void RenderAxisLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImP continue; // Position at the end of the axis - ImPlot3DPoint label_pos = (corners[idx0] + corners[idx1]) * 0.5f; + ImPlot3DPoint label_pos = (PlotToNDC(corners[idx0]) + PlotToNDC(corners[idx1])) * 0.5f; + ImPlot3DPoint center_dir = label_pos.Normalized(); // Add offset - label_pos += (label_pos - range_center) * 0.4f; + label_pos += center_dir * 0.3f; // Convert to pixel coordinates - ImVec2 label_pos_pix = PlotToPixels(label_pos); + ImVec2 label_pos_pix = NDCToPixels(label_pos); // Adjust label position and angle ImU32 col_ax_txt = GetStyleColorU32(ImPlot3DCol_AxisText);