diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 9299f8c..b35ce22 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -46,7 +46,7 @@ target_link_libraries(imgui PUBLIC glfw OpenGL::GL) FetchContent_Declare( implot GIT_REPOSITORY "https://github.com/epezent/implot" - GIT_TAG "3da8bd34299965d3b0ab124df743fe3e076fa222" + GIT_TAG "de4d28e4fbf6382f4e36197a0706bec07ebae104" GIT_PROGRESS TRUE ) FetchContent_MakeAvailable(implot) diff --git a/example/main.cpp b/example/main.cpp index 02d668f..9f8186c 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -1,9 +1,8 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D Example -// main.cpp -// Date: 2024-11-17 -// Author: Breno Cunha Queiroz (brenocq.com) -//-------------------------------------------------- + #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" diff --git a/implot3d.cpp b/implot3d.cpp index f3fe421..fcd4ac4 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -1,15 +1,13 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d.cpp -// Date: 2024-11-16 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] Includes @@ -40,6 +38,7 @@ // [SECTION] ImPlot3DPlot // [SECTION] ImPlot3DStyle // [SECTION] Metrics +// [SECTION] Obsolete API /* API BREAKING CHANGES @@ -49,6 +48,8 @@ Below is a change-log of API breaking changes only. If you are using one of the When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot3d files. You can read releases logs https://github.com/brenocq/implot3d/releases for more details. +- 2025/11/15 (0.3) - Renamed GetPlotPos() -> GetPlotRectPos() and GetPlotSize() -> GetPlotRectSize() for clarity in 3D context. + Old functions are marked as deprecated and will be removed in v1.0. - 2025/10/22 (0.3) - **IMPORTANT** All plot coordinate types migrated from float to double precision to fix sorting issues with large values: - ImPlot3DPoint members (x, y, z): float -> double - ImPlot3DPoint operators: float parameters -> double parameters @@ -78,8 +79,8 @@ implot3d files. You can read releases logs https://github.com/brenocq/implot3d/r #endif // We define this to avoid accidentally using the deprecated API -#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS -#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#ifndef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS +#define IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS #endif #include "implot3d.h" @@ -1696,14 +1697,14 @@ void SetupAxisLimitsConstraints(ImAxis3D idx, double v_min, double v_max) { axis.ConstraintRange.Max = (float)v_max; } -void SetupAxisZoomConstraints(ImAxis3D idx, double z_min, double z_max) { +void SetupAxisZoomConstraints(ImAxis3D idx, double zoom_min, double zoom_max) { ImPlot3DContext& gp = *GImPlot3D; IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); ImPlot3DPlot& plot = *gp.CurrentPlot; ImPlot3DAxis& axis = plot.Axes[idx]; - axis.ConstraintZoom.Min = (float)z_min; - axis.ConstraintZoom.Max = (float)z_max; + axis.ConstraintZoom.Min = (float)zoom_min; + axis.ConstraintZoom.Max = (float)zoom_max; } void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags, ImPlot3DAxisFlags y_flags, @@ -1896,16 +1897,16 @@ ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask) { ImPlot3DPoint PixelsToPlotPlane(double x, double y, ImPlane3D plane, bool mask) { return PixelsToPlotPlane(ImVec2((float)x, (float)y), plane, mask); } -ImVec2 GetPlotPos() { +ImVec2 GetPlotRectPos() { ImPlot3DContext& gp = *GImPlot3D; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotRectPos() needs to be called between BeginPlot() and EndPlot()!"); SetupLock(); return gp.CurrentPlot->PlotRect.Min; } -ImVec2 GetPlotSize() { +ImVec2 GetPlotRectSize() { ImPlot3DContext& gp = *GImPlot3D; - IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotRectSize() needs to be called between BeginPlot() and EndPlot()!"); SetupLock(); return gp.CurrentPlot->PlotRect.GetSize(); } @@ -1972,8 +1973,8 @@ ImPlot3DRay PixelsToNDCRay(const ImVec2& pix) { float y = -(pix.y - center.y) / zoom; // Invert y-axis // Define near and far points in NDC space along the z-axis - ImPlot3DPoint ndc_near = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, -10.0f); - ImPlot3DPoint ndc_far = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, 10.0f); + ImPlot3DPoint ndc_near = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, 10.0f); + ImPlot3DPoint ndc_far = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, -10.0f); // Create the ray in NDC space ImPlot3DRay ndc_ray; @@ -2625,6 +2626,37 @@ void StyleColorsClassic(ImPlot3DStyle* dst) { colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL; } +bool ShowStyleSelector(const char* label) { + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0")) { + switch (style_idx) { + case 0: StyleColorsAuto(); break; + case 1: StyleColorsClassic(); break; + case 2: StyleColorsDark(); break; + case 3: StyleColorsLight(); break; + } + return true; + } + return false; +} + +bool ShowColormapSelector(const char* label) { + ImPlot3DContext& gp = *GImPlot3D; + bool set = false; + if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) { + for (int i = 0; i < gp.ColormapData.Count; ++i) { + const char* name = gp.ColormapData.GetName(i); + if (ImGui::Selectable(name, gp.Style.Colormap == i)) { + gp.Style.Colormap = i; + BustItemCache(); + set = true; + } + } + ImGui::EndCombo(); + } + return set; +} + void PushStyleColor(ImPlot3DCol idx, ImU32 col) { ImPlot3DContext& gp = *GImPlot3D; ImGuiColorMod backup; @@ -3929,4 +3961,20 @@ void ImPlot3D::ShowMetricsWindow(bool* p_popen) { ImGui::End(); } +//----------------------------------------------------------------------------- +// [SECTION] Obsolete API +//----------------------------------------------------------------------------- + +#ifndef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS + +namespace ImPlot3D { + +// OBSOLETED in v0.3 +ImVec2 GetPlotPos() { return GetPlotRectPos(); } +ImVec2 GetPlotSize() { return GetPlotRectSize(); } + +} // namespace ImPlot3D + +#endif // #ifndef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS + #endif // #ifndef IMGUI_DISABLE diff --git a/implot3d.h b/implot3d.h index 507405e..20109c1 100644 --- a/implot3d.h +++ b/implot3d.h @@ -1,15 +1,13 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d.h -// Date: 2024-11-16 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] Macros and Defines @@ -32,6 +30,7 @@ // [SECTION] ImPlot3DQuat // [SECTION] ImPlot3DStyle // [SECTION] Meshes +// [SECTION] Obsolete API #pragma once #include "imgui.h" @@ -81,10 +80,11 @@ typedef int ImPlot3DItemFlags; // -> ImPlot3DItemFlags_ // Flags: Item f typedef int ImPlot3DScatterFlags; // -> ImPlot3DScatterFlags_ // Flags: Scatter plot flags typedef int ImPlot3DLineFlags; // -> ImPlot3DLineFlags_ // Flags: Line plot flags typedef int ImPlot3DTriangleFlags; // -> ImPlot3DTriangleFlags_ // Flags: Triangle plot flags -typedef int ImPlot3DQuadFlags; // -> ImPlot3DQuadFlags_ // Flags: QuadFplot flags +typedef int ImPlot3DQuadFlags; // -> ImPlot3DQuadFlags_ // Flags: Quad plot flags typedef int ImPlot3DSurfaceFlags; // -> ImPlot3DSurfaceFlags_ // Flags: Surface plot flags typedef int ImPlot3DMeshFlags; // -> ImPlot3DMeshFlags_ // Flags: Mesh plot flags typedef int ImPlot3DImageFlags; // -> ImPlot3DImageFlags_ // Flags: Image plot flags +typedef int ImPlot3DDummyFlags; // -> ImPlot3DDummyFlags_ // Flags: Dummy flags typedef int ImPlot3DLegendFlags; // -> ImPlot3DLegendFlags_ // Flags: Legend flags typedef int ImPlot3DAxisFlags; // -> ImPlot3DAxisFlags_ // Flags: Axis flags @@ -251,6 +251,11 @@ enum ImPlot3DImageFlags_ { ImPlot3DImageFlags_NoFit = ImPlot3DItemFlags_NoFit, }; +// Flags for PlotDummy +enum ImPlot3DDummyFlags_ { + ImPlot3DDummyFlags_None = 0 // Default +}; + // Flags for legends enum ImPlot3DLegendFlags_ { ImPlot3DLegendFlags_None = 0, // Default @@ -290,17 +295,17 @@ enum ImPlot3DAxisFlags_ { // Axis indices enum ImAxis3D_ { - ImAxis3D_X = 0, - ImAxis3D_Y, - ImAxis3D_Z, + ImAxis3D_X = 0, // X-axis + ImAxis3D_Y, // Y-axis + ImAxis3D_Z, // Z-axis ImAxis3D_COUNT, }; // Plane indices enum ImPlane3D_ { - ImPlane3D_YZ = 0, - ImPlane3D_XZ, - ImPlane3D_XY, + ImPlane3D_YZ = 0, // YZ plane (perpendicular to X-axis) + ImPlane3D_XZ, // XZ plane (perpendicular to Y-axis) + ImPlane3D_XY, // XY plane (perpendicular to Z-axis) ImPlane3D_COUNT, }; @@ -328,7 +333,9 @@ enum ImPlot3DColormap_ { // [SECTION] Callbacks //----------------------------------------------------------------------------- -// Callback signature for axis tick label formatter +// Callback signature for axis tick label formatter. +// Given a numeric #value, format it into #buff with maximum #size characters. +// Optionally use #user_data for context. Return the number of characters written (excluding null terminator) typedef int (*ImPlot3DFormatter)(double value, char* buff, int size, void* user_data); namespace ImPlot3D { @@ -336,9 +343,14 @@ namespace ImPlot3D { //----------------------------------------------------------------------------- // [SECTION] Context //----------------------------------------------------------------------------- + +// Creates a new ImPlot3D context. Call this after ImGui::CreateContext IMPLOT3D_API ImPlot3DContext* CreateContext(); +// Destroys an ImPlot3D context. Call this before ImGui::DestroyContext. nullptr = destroy current context IMPLOT3D_API void DestroyContext(ImPlot3DContext* ctx = nullptr); +// Returns the current ImPlot3D context. nullptr if no context has been set IMPLOT3D_API ImPlot3DContext* GetCurrentContext(); +// Sets the current ImPlot3D context IMPLOT3D_API void SetCurrentContext(ImPlot3DContext* ctx); //----------------------------------------------------------------------------- @@ -394,8 +406,11 @@ IMPLOT3D_API void EndPlot(); // Only call if BeginPlot() returns true! // Enables an axis or sets the label and/or flags for an existing axis. Leave #label = nullptr for no label IMPLOT3D_API void SetupAxis(ImAxis3D axis, const char* label = nullptr, ImPlot3DAxisFlags flags = 0); +// Sets an axis range limits. If ImPlot3DCond_Always is used, the axis limits will be locked. +// Note: To invert an axis, use ImPlot3DAxisFlags_Invert with SetupAxis instead of swapping min/max IMPLOT3D_API void SetupAxisLimits(ImAxis3D axis, double v_min, double v_max, ImPlot3DCond cond = ImPlot3DCond_Once); +// Sets the format of numeric axis labels via formatter callback. Given value, write a label into buff. Optionally pass user data IMPLOT3D_API void SetupAxisFormat(ImAxis3D axis, ImPlot3DFormatter formatter, void* data = nullptr); // Sets an axis' ticks and optionally the labels. To keep the default ticks, set #keep_default=true @@ -405,17 +420,17 @@ IMPLOT3D_API void SetupAxisTicks(ImAxis3D axis, const double* values, int n_tick IMPLOT3D_API void SetupAxisTicks(ImAxis3D axis, double v_min, double v_max, int n_ticks, const char* const labels[] = nullptr, bool keep_default = false); -// Sets an axis' limits constraints +// Sets an axis' limits constraints. The axis will be constrained to never go below #v_min or above #v_max IMPLOT3D_API void SetupAxisLimitsConstraints(ImAxis3D axis, double v_min, double v_max); -// Sets an axis' zoom constraints -IMPLOT3D_API void SetupAxisZoomConstraints(ImAxis3D axis, double z_min, double z_max); +// Sets an axis' zoom constraints. The zoom (axis range size: range.max - range.min) will be constrained between #zoom_min and #zoom_max +IMPLOT3D_API void SetupAxisZoomConstraints(ImAxis3D axis, double zoom_min, double zoom_max); // Sets the label and/or flags for primary X/Y/Z axes (shorthand for three calls to SetupAxis) IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags = 0, ImPlot3DAxisFlags y_flags = 0, ImPlot3DAxisFlags z_flags = 0); -// Sets the X/Y/Z axes range limits. If ImPlot3DCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits) +// Sets the X/Y/Z axes range limits. If ImPlot3DCond_Always is used, the axes limits will be locked (shorthand for three 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); @@ -436,21 +451,51 @@ IMPLOT3D_API void SetupBoxInitialRotation(ImPlot3DQuat rotation); // 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(double x, double y, double z); +// Sets up the plot legend location and flags IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0); //----------------------------------------------------------------------------- // [SECTION] Plot Items //----------------------------------------------------------------------------- +// The plotting API is provided below. Call these functions between +// BeginPlot/EndPlot and after any Setup API calls. +// +// The templated functions are explicitly instantiated in implot3d_items.cpp. +// They are not intended to be used generically with custom types. You will get +// a linker error if you try! All functions support the following scalar types: +// +// float, double, ImS8, ImU8, ImS16, ImU16, ImS32, ImU32, ImS64, ImU64 +// +// If you need to plot custom or non-homogenous data you have a few options: +// +// 1. If your data is a simple struct/class (e.g. Vector3f), you can use striding. +// This is the most performant option if applicable. +// +// struct Vector3f { float X, Y, Z; }; +// ... +// Vector3f data[42]; +// ImPlot3D::PlotLine("line", &data[0].X, &data[0].Y, &data[0].Z, 42, 0, 0, sizeof(Vector3f)); +// +// 2. If your data is in separate arrays or requires computation, you can copy/transform +// it into temporary float or double arrays before plotting. +// +// NB: All types are converted to double before plotting. You may lose information +// if you try plotting extremely large 64-bit integral types. Proceed with caution! + +// Plots a scatter plot in 3D. Points are rendered as markers at the specified coordinates IMPLOT3D_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags = 0, int offset = 0, int stride = sizeof(T)); +// Plots a line in 3D. Consecutive points are connected with line segments IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags = 0, int offset = 0, int stride = sizeof(T)); +// Plots triangles in 3D. Every 3 consecutive points define a triangle IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags = 0, int offset = 0, int stride = sizeof(T)); +// Plots quads in 3D. Every 4 consecutive points define a quadrilateral IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags = 0, int offset = 0, int stride = sizeof(T)); @@ -460,6 +505,7 @@ 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)); +// Plots a 3D mesh given vertex positions and indices. Triangles are defined by the index buffer (every 3 indices form a triangle) IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0); @@ -483,9 +529,12 @@ IMPLOT3D_API void PlotImage(const char* label_id, ImTextureRef tex_ref, const Im const ImVec2& uv2 = ImVec2(1, 1), const ImVec2& uv3 = ImVec2(0, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), ImPlot3DImageFlags flags = 0); -// Plots a centered text label at point x,y,z. It is possible to set the text angle in radians and offset in pixels +// Plots a centered text label at point x,y,z with optional rotation angle (in radians) and pixel offset IMPLOT3D_API void PlotText(const char* text, double x, double y, double z, double angle = 0.0, const ImVec2& pix_offset = ImVec2(0, 0)); +// Plots a dummy item (can be used to modify legend entry appearance when called after plotting an item, or add a dummy legend entry) +IMPLOT3D_API void PlotDummy(const char* label_id, ImPlot3DDummyFlags flags = 0); + //----------------------------------------------------------------------------- // [SECTION] Plot Utils //----------------------------------------------------------------------------- @@ -493,20 +542,27 @@ IMPLOT3D_API void PlotText(const char* text, double x, double y, double z, doubl // Convert a position in the current plot's coordinate system to pixels IMPLOT3D_API ImVec2 PlotToPixels(const ImPlot3DPoint& point); IMPLOT3D_API ImVec2 PlotToPixels(double x, double y, double z); -// Convert a pixel coordinate to a ray in the current plot's coordinate system + +// Convert a pixel coordinate to a ray in the current plot's coordinate system. Useful for 3D picking and intersection tests IMPLOT3D_API ImPlot3DRay PixelsToPlotRay(const ImVec2& pix); IMPLOT3D_API ImPlot3DRay PixelsToPlotRay(double x, double y); -// Convert a pixel coordinate to a point in an axis plane in the current plot's coordinate system + +// Convert a pixel coordinate to a point on one of the plot box's axis-aligned planes (XY, XZ, or YZ). +// By default, the result is masked to the axis ranges. Set #mask=false to project to the infinite plane. +// Returns ImPlot3DPoint(NAN, NAN, NAN) if the ray does not intersect the plane IMPLOT3D_API ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask = true); IMPLOT3D_API ImPlot3DPoint PixelsToPlotPlane(double x, double y, ImPlane3D plane, bool mask = true); -IMPLOT3D_API ImVec2 GetPlotPos(); // Get the current plot position (top-left) in pixels -IMPLOT3D_API ImVec2 GetPlotSize(); // Get the current plot size in pixels +// Get the current plot rect position (top-left) in absolute screen coordinates +IMPLOT3D_API ImVec2 GetPlotRectPos(); +// Get the current plot rect size in pixels +IMPLOT3D_API ImVec2 GetPlotRectSize(); //----------------------------------------------------------------------------- // [SECTION] Miscellaneous //----------------------------------------------------------------------------- +// Returns the ImDrawList used for rendering plot items. Use this to add custom rendering inside plots IMPLOT3D_API ImDrawList* GetPlotDrawList(); //----------------------------------------------------------------------------- @@ -611,19 +667,26 @@ IMPLOT3D_API void ShowAllDemos(); // Shows ImPlot3D style editor block (not a window) IMPLOT3D_API void ShowStyleEditor(ImPlot3DStyle* ref = nullptr); +// Shows ImPlot3D style selector and returns true if selection is changed (not a window) +IMPLOT3D_API bool ShowStyleSelector(const char* label); +// Shows ImPlot3D colormap selector and returns true if selection is changed (not a window) +IMPLOT3D_API bool ShowColormapSelector(const char* label); // Shows ImPlot3D metrics/debug information window. IMPLOT3D_API void ShowMetricsWindow(bool* p_popen = nullptr); +// Shows ImPlot3D about window. +IMPLOT3D_API void ShowAboutWindow(bool* p_open = nullptr); + } // namespace ImPlot3D //----------------------------------------------------------------------------- // [SECTION] ImPlot3DPoint //----------------------------------------------------------------------------- -// ImPlot3DPoint: 3D vector to store points in 3D +// ImPlot3DPoint: 3D vector to store points in 3D space struct ImPlot3DPoint { - double x, y, z; + double x, y, z; // Coordinates constexpr ImPlot3DPoint() : x(0.0), y(0.0), z(0.0) {} constexpr ImPlot3DPoint(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {} @@ -694,27 +757,30 @@ struct ImPlot3DPoint { // [SECTION] ImPlot3DRay //----------------------------------------------------------------------------- +// ImPlot3DRay: Represents a ray in 3D space with an origin and direction struct ImPlot3DRay { - ImPlot3DPoint Origin; - ImPlot3DPoint Direction; + ImPlot3DPoint Origin; // Ray origin point + ImPlot3DPoint Direction; // Ray direction (not necessarily normalized) }; //----------------------------------------------------------------------------- // [SECTION] ImPlot3DPlane //----------------------------------------------------------------------------- +// ImPlot3DPlane: Represents a plane in 3D space defined by a point and normal vector struct ImPlot3DPlane { - ImPlot3DPoint Point; - ImPlot3DPoint Normal; + ImPlot3DPoint Point; // A point on the plane + ImPlot3DPoint Normal; // Plane normal vector }; //----------------------------------------------------------------------------- // [SECTION] ImPlot3DBox //----------------------------------------------------------------------------- +// ImPlot3DBox: Axis-aligned bounding box in 3D space struct ImPlot3DBox { - ImPlot3DPoint Min; - ImPlot3DPoint Max; + ImPlot3DPoint Min; // Minimum corner of the box + ImPlot3DPoint Max; // Maximum corner of the box // Default constructor constexpr ImPlot3DBox() : Min(ImPlot3DPoint()), Max(ImPlot3DPoint()) {} @@ -736,35 +802,38 @@ struct ImPlot3DBox { // [SECTION] ImPlot3DRange //----------------------------------------------------------------------------- +// ImPlot3DRange: Represents a 1D range with min and max values struct ImPlot3DRange { - double Min; - double Max; + double Min; // Minimum value + double Max; // Maximum value constexpr ImPlot3DRange() : Min(0.0), Max(0.0) {} constexpr ImPlot3DRange(double min, double max) : Min(min), Max(max) {} - IMPLOT3D_API void Expand(double value); - IMPLOT3D_API bool Contains(double value) const; - double Size() const { return Max - Min; } + IMPLOT3D_API void Expand(double value); // Expand range to include value + IMPLOT3D_API bool Contains(double value) const; // Check if value is within range + double Size() const { return Max - Min; } // Get range size }; //----------------------------------------------------------------------------- // [SECTION] ImPlot3DQuat //----------------------------------------------------------------------------- +// ImPlot3DQuat: Quaternion for representing 3D rotations struct ImPlot3DQuat { - double x, y, z, w; + double x, y, z, w; // Quaternion components // Constructors constexpr ImPlot3DQuat() : x(0.0), y(0.0), z(0.0), w(1.0) {} constexpr ImPlot3DQuat(double _x, double _y, double _z, double _w) : x(_x), y(_y), z(_z), w(_w) {} + // Construct quaternion from angle-axis representation (angle in radians) IMPLOT3D_API ImPlot3DQuat(double _angle, const ImPlot3DPoint& _axis); - // Set quaternion from two vectors + // Create quaternion that rotates from v0 to v1 IMPLOT3D_API static ImPlot3DQuat FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3DPoint& v1); - // Set quaternion given elevation and azimuth angles in radians + // Create quaternion from elevation and azimuth angles (in radians) IMPLOT3D_API static ImPlot3DQuat FromElAz(double elevation, double azimuth); // Get quaternion length @@ -780,7 +849,7 @@ struct ImPlot3DQuat { IMPLOT3D_API ImPlot3DQuat Inverse() const; // Binary operators - IMPLOT3D_API ImPlot3DQuat operator*(const ImPlot3DQuat& rhs) const; + IMPLOT3D_API ImPlot3DQuat operator*(const ImPlot3DQuat& rhs) const; // Quaternion multiplication // Normalize the quaternion in place IMPLOT3D_API ImPlot3DQuat& Normalize(); @@ -792,7 +861,7 @@ struct ImPlot3DQuat { IMPLOT3D_API bool operator==(const ImPlot3DQuat& rhs) const; IMPLOT3D_API bool operator!=(const ImPlot3DQuat& rhs) const; - // Interpolate between two quaternions + // Spherical linear interpolation between two quaternions (t in [0,1]) IMPLOT3D_API static ImPlot3DQuat Slerp(const ImPlot3DQuat& q1, const ImPlot3DQuat& q2, double t); // Get quaternion dot product @@ -816,21 +885,21 @@ struct ImPlot3DStyle { float MarkerWeight; // Marker outline weight in pixels float FillAlpha; // Alpha modifier applied to plot fills // Plot style - ImVec2 PlotDefaultSize; - ImVec2 PlotMinSize; - ImVec2 PlotPadding; - ImVec2 LabelPadding; - float ViewScaleFactor; + ImVec2 PlotDefaultSize; // Default size used when ImVec2(0,0) is passed to BeginPlot + ImVec2 PlotMinSize; // Minimum size plot frame can be when shrunk + ImVec2 PlotPadding; // Padding between widget frame and plot area + ImVec2 LabelPadding; // Padding between axes labels, tick labels, and plot edge + float ViewScaleFactor; // Scale factor for 3D view // Legend style ImVec2 LegendPadding; // Legend padding from plot edges ImVec2 LegendInnerPadding; // Legend inner padding from legend edges ImVec2 LegendSpacing; // Spacing between legend entries // Colors - ImVec4 Colors[ImPlot3DCol_COUNT]; + ImVec4 Colors[ImPlot3DCol_COUNT]; // Array of plot colors inline ImVec4 GetColor(ImPlot3DCol idx) const { return Colors[idx]; } inline void SetColor(ImPlot3DCol idx, const ImVec4& col) { Colors[idx] = col; } // Colormap - ImPlot3DColormap Colormap; // The current colormap. Set this to either an ImPlot3DColormap_ enum or an index returned by AddColormap + ImPlot3DColormap Colormap; // The current colormap (ImPlot3DColormap_ enum or index from AddColormap) // Constructor IMPLOT3D_API ImPlot3DStyle(); ImPlot3DStyle(const ImPlot3DStyle& other) = default; @@ -862,4 +931,39 @@ extern unsigned int duck_idx[DUCK_IDX_COUNT]; // Duck indices } // namespace ImPlot3D +//----------------------------------------------------------------------------- +// [SECTION] Obsolete API +//----------------------------------------------------------------------------- + +// The following functions will be removed! Keep your copy of ImPlot3D up to date! +// Occasionally set '#define IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS' to stay ahead. +// If you absolutely must use these functions and do not want to receive compiler +// warnings, set '#define IMPLOT3D_DISABLE_OBSOLETE_WARNINGS'. + +#ifndef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS + +#ifndef IMPLOT3D_DISABLE_OBSOLETE_WARNINGS +#if __cplusplus > 201402L +#define IMPLOT3D_DEPRECATED(method) [[deprecated]] method +#elif defined(__GNUC__) && !defined(__INTEL_COMPILER) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define IMPLOT3D_DEPRECATED(method) method __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define IMPLOT3D_DEPRECATED(method) __declspec(deprecated) method +#else +#define IMPLOT3D_DEPRECATED(method) method +#endif +#else +#define IMPLOT3D_DEPRECATED(method) method +#endif + +namespace ImPlot3D { + +// OBSOLETED in v0.3 -> PLANNED REMOVAL in v1.0 +IMPLOT3D_DEPRECATED(IMPLOT3D_API ImVec2 GetPlotPos()); // Renamed to GetPlotRectPos() +IMPLOT3D_DEPRECATED(IMPLOT3D_API ImVec2 GetPlotSize()); // Renamed to GetPlotRectSize() + +} // namespace ImPlot3D + +#endif // #ifndef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS + #endif // #ifndef IMGUI_DISABLE diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 87b03ef..8d3cb11 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -1,22 +1,22 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d_demo.cpp -// Date: 2024-11-17 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] User Namespace // [SECTION] Helpers // [SECTION] Plots // [SECTION] Axes +// [SECTION] Tools // [SECTION] Custom +// [SECTION] Config // [SECTION] Demo Window // [SECTION] Style Editor // [SECTION] User Namespace Implementation @@ -85,6 +85,22 @@ struct ScrollingBuffer { } }; +// Custom axis formatter that adds metric prefixes (G, M, k, m, u, n) +int MetricFormatter(double value, char* buff, int size, void* data) { + const char* unit = (const char*)data; + static double v[] = {1000000000, 1000000, 1000, 1, 0.001, 0.000001, 0.000000001}; + static const char* p[] = {"G", "M", "k", "", "m", "u", "n"}; + if (value == 0) { + return snprintf(buff, size, "0 %s", unit); + } + for (int i = 0; i < 7; ++i) { + if (fabs(value) >= v[i]) { + return snprintf(buff, size, "%g %s%s", value / v[i], p[i], unit); + } + } + return snprintf(buff, size, "%g %s%s", value / v[6], p[6], unit); +} + //----------------------------------------------------------------------------- // [SECTION] Plots //----------------------------------------------------------------------------- @@ -565,6 +581,217 @@ void DemoRealtimePlots() { } } +void DemoPlotFlags() { + static ImPlot3DFlags flags = ImPlot3DFlags_None; + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoTitle); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hide plot title"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoLegend); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hide plot legend"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoMouseText); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hide mouse position in plot coordinates"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoClip); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disable 3D box clipping"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoMenus); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("The user will not be able to open context menus"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_Equal); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("X, Y, and Z axes will be constrained to have the same units/pixel"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoRotate); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Lock rotation interaction"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoPan); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Lock panning/translation interaction"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoZoom); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Lock zooming interaction"); + } + + CHECKBOX_FLAG(flags, ImPlot3DFlags_NoInputs); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Disable all user inputs"); + } + + if (ImPlot3D::BeginPlot("Plot Flags Demo", ImVec2(-1, 0), flags)) { + ImPlot3D::SetupAxes("X-axis", "Y-axis", "Z-axis"); + ImPlot3D::SetupAxesLimits(-10, 10, -10, 10, -5, 5); + + // Generate some sample data for demonstration + static float x[100], y[100], z[100]; + static bool first = true; + if (first) { + for (int i = 0; i < 100; i++) { + float t = i * 0.1f; + x[i] = 3.0f * cosf(t); + y[i] = 3.0f * sinf(t); + z[i] = t - 5.0f; + } + first = false; + } + + ImPlot3D::PlotLine("Helix", x, y, z, 100); + + // Add some scatter points to show equal scaling effect + float scatter_x[8] = {-10, 10, -10, 10, -10, 10, -10, 10}; + float scatter_y[8] = {-10, -10, 10, 10, -10, -10, 10, 10}; + float scatter_z[8] = {-5, -5, -5, -5, 5, 5, 5, 5}; + ImPlot3D::PlotScatter("Cube corners", scatter_x, scatter_y, scatter_z, 8); + + ImPlot3D::EndPlot(); + } +} + +void DemoOffsetAndStride() { + static const int k_spirals = 11; + static const int k_points_per = 50; + static const int k_size = 3 * k_points_per * k_spirals; + static double interleaved_data[k_size]; + for (int p = 0; p < k_points_per; ++p) { + for (int s = 0; s < k_spirals; ++s) { + double r = (double)s / (k_spirals - 1) * 0.2 + 0.2; + double theta = (double)p / k_points_per * 6.28; + interleaved_data[p * 3 * k_spirals + 3 * s + 0] = 0.5 + r * cos(theta); + interleaved_data[p * 3 * k_spirals + 3 * s + 1] = 0.5 + r * sin(theta); + interleaved_data[p * 3 * k_spirals + 3 * s + 2] = 0.5 + 0.5 * sin(2.0 * theta); + } + } + static int offset = 0; + ImGui::BulletText("Offsetting is useful for realtime plots (see above) and circular buffers."); + ImGui::BulletText("Striding is useful for interleaved data (e.g. audio) or plotting structs."); + ImGui::BulletText("Here, all spiral data is stored in a single interleaved buffer:"); + ImGui::BulletText("[s0.x0 s0.y0 s0.z0 ... sn.x0 sn.y0 sn.z0 s0.x1 s0.y1 s0.z1 ... sn.x1 sn.y1 sn.z1 ... sn.xm sn.ym sn.zm]"); + ImGui::BulletText("The offset value indicates which spiral point index is considered the first."); + ImGui::BulletText("Offsets can be negative and/or larger than the actual data count."); + ImGui::SliderInt("Offset", &offset, -2 * k_points_per, 2 * k_points_per); + if (ImPlot3D::BeginPlot("##strideoffset", ImVec2(-1, 0))) { + ImPlot3D::PushColormap(ImPlot3DColormap_Jet); + char buff[32]; + for (int s = 0; s < k_spirals; ++s) { + snprintf(buff, sizeof(buff), "Spiral %d", s); + ImPlot3D::PlotLine(buff, &interleaved_data[s * 3 + 0], &interleaved_data[s * 3 + 1], &interleaved_data[s * 3 + 2], k_points_per, 0, + offset, 3 * k_spirals * sizeof(double)); + } + ImPlot3D::EndPlot(); + ImPlot3D::PopColormap(); + } +} + +void DemoLegendOptions() { + static ImPlot3DLocation loc = ImPlot3DLocation_East; + ImGui::CheckboxFlags("North", (unsigned int*)&loc, ImPlot3DLocation_North); + ImGui::SameLine(); + ImGui::CheckboxFlags("South", (unsigned int*)&loc, ImPlot3DLocation_South); + ImGui::SameLine(); + ImGui::CheckboxFlags("West", (unsigned int*)&loc, ImPlot3DLocation_West); + ImGui::SameLine(); + ImGui::CheckboxFlags("East", (unsigned int*)&loc, ImPlot3DLocation_East); + + static ImPlot3DLegendFlags flags = 0; + + CHECKBOX_FLAG(flags, ImPlot3DLegendFlags_Horizontal); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Legend entries will be displayed horizontally"); + } + + CHECKBOX_FLAG(flags, ImPlot3DLegendFlags_NoButtons); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Legend icons will not function as hide/show buttons"); + } + + CHECKBOX_FLAG(flags, ImPlot3DLegendFlags_NoHighlightItem); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Plot items will not be highlighted when their legend entry is hovered"); + } + + ImGui::SliderFloat2("LegendPadding", (float*)&ImPlot3D::GetStyle().LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&ImPlot3D::GetStyle().LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&ImPlot3D::GetStyle().LegendSpacing, 0.0f, 5.0f, "%.0f"); + + if (ImPlot3D::BeginPlot("Legend Options Demo", ImVec2(-1, 0))) { + ImPlot3D::SetupAxes("X-Axis", "Y-Axis", "Z-Axis"); + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1, 1); + ImPlot3D::SetupLegend(loc, flags); + + // Generate some 3D line data + static float t = 0; + t += ImGui::GetIO().DeltaTime * 0.5f; + + constexpr int count = 50; + static float xs1[count], ys1[count], zs1[count]; + static float xs2[count], ys2[count], zs2[count]; + static float xs3[count], ys3[count], zs3[count]; + + for (int i = 0; i < count; i++) { + float phase = i * 0.1f + t; + xs1[i] = 0.8f * cosf(phase); + ys1[i] = 0.8f * sinf(phase); + zs1[i] = 0.5f * sinf(phase * 2); + + xs2[i] = 0.6f * cosf(phase + 1.0f); + ys2[i] = 0.6f * sinf(phase + 1.0f); + zs2[i] = -0.3f * cosf(phase * 1.5f); + + xs3[i] = 0.4f * sinf(phase); + ys3[i] = 0.4f * cosf(phase); + zs3[i] = 0.7f * cosf(phase * 0.8f); + } + + ImPlot3D::PlotLine("Helix A", xs1, ys1, zs1, count); + ImPlot3D::PlotLine("Helix B##IDText", xs2, ys2, zs2, count); // Text after ## used for ID only + ImPlot3D::PlotLine("##NotListed", xs3, ys3, zs3, count); // Plotted, but not added to legend + + ImPlot3D::EndPlot(); + } +} + void DemoMarkersAndText() { static float mk_size = ImPlot3D::GetStyle().MarkerSize; static float mk_weight = ImPlot3D::GetStyle().MarkerWeight; @@ -716,8 +943,11 @@ void DemoBoxRotation() { } void DemoTickLabels() { - static bool custom_ticks = true; + static bool custom_fmt = true; + static bool custom_ticks = false; static bool custom_labels = true; + ImGui::Checkbox("Show Custom Format", &custom_fmt); + ImGui::SameLine(); ImGui::Checkbox("Show Custom Ticks", &custom_ticks); if (custom_ticks) { ImGui::SameLine(); @@ -728,11 +958,15 @@ void DemoTickLabels() { static double letters_ticks[] = {0.0, 0.2, 0.4, 0.6, 0.8, 1.0}; static const char* letters_labels[] = {"A", "B", "C", "D", "E", "F"}; if (ImPlot3D::BeginPlot("##Ticks")) { - ImPlot3D::SetupAxesLimits(2, 5, 0, 1, 0, 1); + ImPlot3D::SetupAxesLimits(2, 5, 0, 1, 0, 1000); + if (custom_fmt) { + ImPlot3D::SetupAxisFormat(ImAxis3D_Y, MetricFormatter, (void*)"Hz"); + ImPlot3D::SetupAxisFormat(ImAxis3D_Z, MetricFormatter, (void*)"m"); + } if (custom_ticks) { ImPlot3D::SetupAxisTicks(ImAxis3D_X, &pi, 1, custom_labels ? pi_str : nullptr, true); ImPlot3D::SetupAxisTicks(ImAxis3D_Y, letters_ticks, 6, custom_labels ? letters_labels : nullptr, false); - ImPlot3D::SetupAxisTicks(ImAxis3D_Z, 0, 1, 6, custom_labels ? letters_labels : nullptr, false); + ImPlot3D::SetupAxisTicks(ImAxis3D_Z, 0, 1000, 6, custom_labels ? letters_labels : nullptr, false); } ImPlot3D::EndPlot(); } @@ -758,103 +992,152 @@ void DemoAxisConstraints() { } } -void DemoPlotFlags() { - static ImPlot3DFlags flags = ImPlot3DFlags_None; - - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoTitle); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hide plot title"); +void DemoEqualAxes() { + ImGui::BulletText("Equal constraint applies to all three axes (X, Y, Z)"); + ImGui::BulletText("When enabled, the axes maintain the same units/pixel ratio"); + + static double circle_xs[360], circle_ys[360], circle_zs[360]; + static double helix_xs[360], helix_ys[360], helix_zs[360]; + float square_xs[] = {-0.5f, 0.5f, 0.5f, -0.5f, -0.5f}; + float square_ys[] = {-0.5f, -0.5f, 0.5f, 0.5f, -0.5f}; + float square_zs[] = {-0.5f, -0.5f, -0.5f, -0.5f, -0.5f}; + static bool initialized = false; + if (!initialized) { + for (int i = 0; i < 360; ++i) { + double angle = i * 2 * IM_PI / 359.0; + // Circle in XY plane at Z=0 + circle_xs[i] = cos(angle); + circle_ys[i] = sin(angle); + circle_zs[i] = 0; + // Helix + helix_xs[i] = 0.5 * cos(angle); + helix_ys[i] = 0.5 * sin(angle); + helix_zs[i] = (double)i / 359.0 * 2.0 - 1.0; + } + initialized = true; } - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoLegend); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hide plot legend"); - } + static ImPlot3DFlags flags = ImPlot3DFlags_Equal; + CHECKBOX_FLAG(flags, ImPlot3DFlags_Equal); - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoMouseText); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hide mouse position in plot coordinates"); + if (ImPlot3D::BeginPlot("##EqualAxes", ImVec2(-1, 0), flags)) { + ImPlot3D::SetupAxes("X-Axis", "Y-Axis", "Z-Axis"); + ImPlot3D::PlotLine("Circle", circle_xs, circle_ys, circle_zs, 360); + ImPlot3D::PlotLine("Helix", helix_xs, helix_ys, helix_zs, 360); + ImPlot3D::PlotLine("Square", square_xs, square_ys, square_zs, 5); + ImPlot3D::EndPlot(); } +} - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoClip); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disable 3D box clipping"); - } +void DemoAutoFittingData() { + ImGui::BulletText("Axes can be configured to auto-fit to data extents."); + ImGui::BulletText("Try panning and zooming to see the axes adjust."); + ImGui::BulletText("Disable AutoFit on an axis to fix its range."); - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoMenus); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("The user will not be able to open context menus"); - } + static ImPlot3DAxisFlags xflags = ImPlot3DAxisFlags_None; + static ImPlot3DAxisFlags yflags = ImPlot3DAxisFlags_None; + static ImPlot3DAxisFlags zflags = ImPlot3DAxisFlags_AutoFit; - CHECKBOX_FLAG(flags, ImPlot3DFlags_Equal); + ImGui::TextUnformatted("X: "); ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("X, Y, and Z axes will be constrained to have the same units/pixel"); - } + ImGui::CheckboxFlags("ImPlot3DAxisFlags_AutoFit##X", (unsigned int*)&xflags, ImPlot3DAxisFlags_AutoFit); - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoRotate); + ImGui::TextUnformatted("Y: "); ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Lock rotation interaction"); - } + ImGui::CheckboxFlags("ImPlot3DAxisFlags_AutoFit##Y", (unsigned int*)&yflags, ImPlot3DAxisFlags_AutoFit); - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoPan); + ImGui::TextUnformatted("Z: "); ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Lock panning/translation interaction"); + ImGui::CheckboxFlags("ImPlot3DAxisFlags_AutoFit##Z", (unsigned int*)&zflags, ImPlot3DAxisFlags_AutoFit); + + static double data_x[101], data_y[101], data_z[101]; + static bool initialized = false; + if (!initialized) { + srand(0); + for (int i = 0; i < 101; ++i) { + data_x[i] = i * 0.1; + data_y[i] = i * 0.1; + data_z[i] = 1 + sin(i / 10.0); + } + initialized = true; } - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoZoom); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Lock zooming interaction"); + if (ImPlot3D::BeginPlot("##AutoFitting")) { + ImPlot3D::SetupAxes("X-Axis", "Y-Axis", "Z-Axis", xflags, yflags, zflags); + ImPlot3D::PlotLine("Wave", data_x, data_y, data_z, 101); + ImPlot3D::EndPlot(); } +} - CHECKBOX_FLAG(flags, ImPlot3DFlags_NoInputs); +//----------------------------------------------------------------------------- +// [SECTION] Tools +//----------------------------------------------------------------------------- + +void DemoMousePicking() { + static ImVector points; + static ImVector rays; + + ImGui::BulletText("Click anywhere in the plot to place points/rays."); + + static int selected_plane = ImPlane3D_XY; + ImGui::RadioButton("XY-Plane", &selected_plane, ImPlane3D_XY); ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disable all user inputs"); + ImGui::RadioButton("XZ-Plane", &selected_plane, ImPlane3D_XZ); + ImGui::SameLine(); + ImGui::RadioButton("YZ-Plane", &selected_plane, ImPlane3D_YZ); + + static bool mask_plane = true; + ImGui::Checkbox("Mask Plane", &mask_plane); + if (ImGui::Button("Clear")) { + points.clear(); + rays.clear(); } - if (ImPlot3D::BeginPlot("Plot Flags Demo", ImVec2(-1, 0), flags)) { - ImPlot3D::SetupAxes("X-axis", "Y-axis", "Z-axis"); - ImPlot3D::SetupAxesLimits(-10, 10, -10, 10, -5, 5); + if (ImPlot3D::BeginPlot("Mouse Picking", ImVec2(-1, 0), ImPlot3DFlags_NoClip)) { + ImPlot3D::SetupAxes("X-Axis", "Y-Axis", "Z-Axis"); + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1, 1); - // Generate some sample data for demonstration - static float x[100], y[100], z[100]; - static bool first = true; - if (first) { - for (int i = 0; i < 100; i++) { - float t = i * 0.1f; - x[i] = 3.0f * cosf(t); - y[i] = 3.0f * sinf(t); - z[i] = t - 5.0f; - } - first = false; + // Get mouse position and convert to ray + ImVec2 mouse_pos = ImGui::GetMousePos(); + ImPlot3DRay ray = ImPlot3D::PixelsToPlotRay(mouse_pos); + + // Convert to selected plane + ImPlane3D plane = (ImPlane3D)selected_plane; + ImPlot3DPoint point = ImPlot3D::PixelsToPlotPlane(mouse_pos, plane, mask_plane); + + // Show intersection point + if (ImGui::IsItemHovered() && !point.IsNaN()) { + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle, 5, ImVec4(1, 1, 0, 1)); + ImPlot3D::PlotScatter("##Intersection", &point.x, &point.y, &point.z, 1); } - ImPlot3D::PlotLine("Helix", x, y, z, 100); + // Add point/ray on click + if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && !point.IsNaN()) { + points.push_back(point); + rays.push_back(ray); + } - // Add some scatter points to show equal scaling effect - float scatter_x[8] = {-10, 10, -10, 10, -10, 10, -10, 10}; - float scatter_y[8] = {-10, -10, 10, 10, -10, -10, 10, 10}; - float scatter_z[8] = {-5, -5, -5, -5, 5, 5, 5, 5}; - ImPlot3D::PlotScatter("Cube corners", scatter_x, scatter_y, scatter_z, 8); + // Draw all placed points + if (!points.empty()) { + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle, 3); + // Plot points + ImPlot3D::PlotScatter("Placed Points", &points[0].x, &points[0].y, &points[0].z, (int)points.Size, ImPlot3DScatterFlags_None, 0, + sizeof(ImPlot3DPoint)); + } + + // Draw all placed rays + if (!rays.empty()) { + ImVector ray_points; + ray_points.reserve(rays.Size * 2); + for (int i = 0; i < rays.Size; i++) { + ImPlot3DPoint p1 = points[i]; + ImPlot3DPoint p2 = points[i] - rays[i].Direction; // Ray from added point to the camera + ray_points.push_back(p1); + ray_points.push_back(p2); + } + ImPlot3D::PlotLine("Placed Rays", &ray_points[0].x, &ray_points[0].y, &ray_points[0].z, (int)rays.Size * 2, ImPlot3DLineFlags_Segments, 0, + sizeof(ImPlot3DPoint)); + } ImPlot3D::EndPlot(); } @@ -913,6 +1196,267 @@ void DemoCustomRendering() { } } +void DemoCustomOverlay() { + ImGui::BulletText("Demonstrates custom 2D overlays using GetPlotRectPos/GetPlotRectSize."); + ImGui::BulletText("Shows mouse tooltip, line to closest point, and orientation gizmo."); + + // Generate some 3D scatter data + static float xs[50], ys[50], zs[50]; + static bool initialized = false; + if (!initialized) { + srand(0); + for (int i = 0; i < 50; i++) { + xs[i] = (float)rand() / (float)RAND_MAX; + ys[i] = (float)rand() / (float)RAND_MAX; + zs[i] = (float)rand() / (float)RAND_MAX; + } + initialized = true; + } + + if (ImPlot3D::BeginPlot("##CustomOverlay", ImVec2(-1, 0))) { + ImPlot3D::SetupAxes("X-Axis", "Y-Axis", "Z-Axis"); + ImPlot3D::SetupAxesLimits(0, 1, 0, 1, 0, 1); + ImPlot3D::PlotScatter("Data", xs, ys, zs, 50); + + ImDrawList* draw_list = ImPlot3D::GetPlotDrawList(); + ImVec2 mouse_pos = ImGui::GetMousePos(); + ImVec2 plot_pos = ImPlot3D::GetPlotRectPos(); + ImVec2 plot_size = ImPlot3D::GetPlotRectSize(); + + // Check if mouse is over plot + bool is_hovered = ImGui::IsItemHovered(); + + if (is_hovered) { + // Find closest point to mouse in screen space + int closest_idx = -1; + float min_dist_sq = 1e10f; // Large value instead of FLT_MAX + ImVec2 closest_px; + + for (int i = 0; i < 50; i++) { + ImVec2 point_px = ImPlot3D::PlotToPixels(xs[i], ys[i], zs[i]); + float dx = point_px.x - mouse_pos.x; + float dy = point_px.y - mouse_pos.y; + float dist_sq = dx * dx + dy * dy; + if (dist_sq < min_dist_sq) { + min_dist_sq = dist_sq; + closest_idx = i; + closest_px = point_px; + } + } + + // Draw line to closest point + if (closest_idx >= 0) { + draw_list->AddLine(mouse_pos, closest_px, IM_COL32(255, 255, 0, 255), 2.0f); + + // Draw tooltip + ImGui::BeginTooltip(); + ImGui::Text("Mouse: (%.1f, %.1f)", mouse_pos.x, mouse_pos.y); + ImGui::Text("Closest Point #%d", closest_idx); + ImGui::Text("Position: (%.3f, %.3f, %.3f)", xs[closest_idx], ys[closest_idx], zs[closest_idx]); + ImGui::Text("Distance: %.1f px", ImSqrt(min_dist_sq)); + ImGui::EndTooltip(); + } + } + + // Draw orientation gizmo in bottom-right corner + ImPlot3DContext& gp = *GImPlot3D; + ImPlot3DPlot* plot = gp.CurrentPlot; + if (plot) { + ImVec2 gizmo_center = ImVec2(plot_pos.x + plot_size.x - 50, plot_pos.y + plot_size.y - 50); + float gizmo_size = 30.0f; + + // Get rotation quaternion + ImPlot3DQuat rot = plot->Rotation; + + // Define axis directions in plot space + ImPlot3DPoint axes[3] = { + ImPlot3DPoint(1, 0, 0), // X-axis (red) + ImPlot3DPoint(0, 1, 0), // Y-axis (green) + ImPlot3DPoint(0, 0, 1) // Z-axis (blue) + }; + + ImU32 colors[3] = { + IM_COL32(200, 50, 50, 255), // Red + IM_COL32(50, 200, 50, 255), // Green + IM_COL32(50, 50, 200, 255) // Blue + }; + + const char* labels[3] = {"X", "Y", "Z"}; + + // Draw gizmo background circle + draw_list->AddCircleFilled(gizmo_center, gizmo_size + 5, IM_COL32(0, 0, 0, 100)); + + // Draw each axis + for (int i = 0; i < 3; i++) { + // Rotate axis by quaternion + ImPlot3DPoint rotated = rot * axes[i]; + + // Project to 2D gizmo space (simple orthographic projection) + ImVec2 axis_end = ImVec2(gizmo_center.x + float(rotated.x) * gizmo_size, + gizmo_center.y - float(rotated.y) * gizmo_size // Flip Y for screen coords + ); + + // Draw line + draw_list->AddLine(gizmo_center, axis_end, colors[i], 2.0f); + + // Draw circle at end + draw_list->AddCircleFilled(axis_end, 4.0f, colors[i]); + + // Draw label + ImVec2 label_pos = ImVec2(axis_end.x + 8, axis_end.y - 8); + draw_list->AddText(label_pos, colors[i], labels[i]); + } + } + + ImPlot3D::EndPlot(); + } +} + +void DemoCustomPerPointStyle() { + ImGui::BulletText("Demonstrates per-point coloring using colormap sampling."); + ImGui::BulletText("Each point calls SetNextMarkerStyle with a sampled color."); + ImGui::BulletText("All points share the same label for a single legend entry."); + + static float marker_size = 4.0f; + static ImPlot3DColormap cmap = ImPlot3DColormap_Viridis; + + ImGui::SliderFloat("Marker Size", &marker_size, 2.0f, 10.0f); + if (ImGui::BeginCombo("Colormap", ImPlot3D::GetColormapName(cmap))) { + ImPlot3DContext& gp = *GImPlot3D; + for (int i = 0; i < ImPlot3D::GetColormapCount(); i++) { + // Only show continuous colormaps + if (!gp.ColormapData.IsQual(i)) { + if (ImGui::Selectable(ImPlot3D::GetColormapName(i), cmap == i)) + cmap = i; + } + } + ImGui::EndCombo(); + } + + // Generate three stacked toruses with different color patterns + static float torus_data[3][400][4]; // 3 toruses, 400 points each, XYZT per point + static bool initialized = false; + if (!initialized) { + const float R = 0.6f; // Major radius + const float r = 0.2f; // Minor radius + const int u_samples = 20; + const int v_samples = 20; + + for (int torus = 0; torus < 3; torus++) { + float z_offset = (2 - torus) * 0.6f; + int idx = 0; + + for (int i = 0; i < u_samples; i++) { + float u = (float)i / u_samples * 2.0f * IM_PI; + for (int j = 0; j < v_samples; j++) { + float v = (float)j / v_samples * 2.0f * IM_PI; + + // Parametric torus equations + float x = (R + r * ImCos(v)) * ImCos(u); + float y = (R + r * ImCos(v)) * ImSin(u); + float z = r * ImSin(v) + z_offset; + + // Different color pattern for each torus + float t; + if (torus == 0) { + // Color by height (Z coordinate) + // Normalize Z to [0, 1] range for this torus + t = (z - (z_offset - r)) / (2.0f * r); + } else if (torus == 1) { + // Color by radial distance from tube center + // V parameter directly gives us position around tube + // Map from [-r, +r] to [0, 1] + t = (ImCos(v) + 1.0f) / 2.0f; + } else { + // Angular pattern: varies smoothly around the major circle + // Creates wave-like color pattern + t = (ImCos(u) + 1.0f) / 2.0f; // Maps [-1, 1] to [0, 1] + } + + torus_data[torus][idx][0] = x; + torus_data[torus][idx][1] = y; + torus_data[torus][idx][2] = z; + torus_data[torus][idx][3] = t; + idx++; + } + } + } + initialized = true; + } + + if (ImPlot3D::BeginPlot("##PerPointStyle", ImVec2(-1, 0))) { + ImPlot3D::SetupAxes("X", "Y", "Z"); + ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -0.5, 1.5); + + const char* labels[3] = {"Height-colored", "Radial-colored", "Angular-colored"}; + const ImVec4 legend_colors[3] = { + ImVec4(1.0f, 0.0f, 0.0f, 1.0f), // Red + ImVec4(0.0f, 1.0f, 0.0f, 1.0f), // Green + ImVec4(0.0f, 0.0f, 1.0f, 1.0f) // Blue + }; + + for (int torus = 0; torus < 3; torus++) { + const int point_count = 400; + for (int i = 0; i < point_count; i++) { + float x = torus_data[torus][i][0]; + float y = torus_data[torus][i][1]; + float z = torus_data[torus][i][2]; + float t = torus_data[torus][i][3]; + + // Sample colormap and set marker style + ImVec4 color = ImPlot3D::SampleColormap(t, cmap); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle, marker_size, color, IMPLOT3D_AUTO, color); + ImPlot3D::PlotScatter(labels[torus], &x, &y, &z, 1); + } + // Override legend color with PlotDummy + ImPlot3D::SetNextLineStyle(legend_colors[torus]); + ImPlot3D::PlotDummy(labels[torus]); + } + + ImPlot3D::EndPlot(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Config +//----------------------------------------------------------------------------- + +void DemoConfig() { + ImGui::ShowFontSelector("Font"); + ImGui::ShowStyleSelector("ImGui Style"); + ImPlot3D::ShowStyleSelector("ImPlot3D Style"); + ImPlot3D::ShowColormapSelector("ImPlot3D Colormap"); + ImGui::Separator(); + + // Preview plot with 3D spirals + if (ImPlot3D::BeginPlot("Preview", ImVec2(-1, 0))) { + // Generate 10 spirals at different heights + static float xs[10][50], ys[10][50], zs[10][50]; + static bool initialized = false; + if (!initialized) { + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 50; ++j) { + float t = j / 49.0f; + float angle = t * 4.0f * IM_PI; + float radius = 0.3f + i * 0.05f; + xs[i][j] = radius * ImCos(angle); + ys[i][j] = radius * ImSin(angle); + zs[i][j] = i / 9.0f; + } + } + initialized = true; + } + + for (int i = 0; i < 10; ++i) { + ImGui::PushID(i); + ImPlot3D::PlotLine("##Spiral", xs[i], ys[i], zs[i], 50); + ImGui::PopID(); + } + + ImPlot3D::EndPlot(); + } +} + //----------------------------------------------------------------------------- // [SECTION] Demo Window //----------------------------------------------------------------------------- @@ -1005,6 +1549,8 @@ void ShowAllDemos() { // Plot Options ImGui::SeparatorText("Plot Options"); DemoHeader("Plot Flags", DemoPlotFlags); + DemoHeader("Offset and Stride", DemoOffsetAndStride); + DemoHeader("Legend Options", DemoLegendOptions); DemoHeader("Markers and Text", DemoMarkersAndText); DemoHeader("NaN Values", DemoNaNValues); ImGui::EndTabItem(); @@ -1014,11 +1560,23 @@ void ShowAllDemos() { DemoHeader("Box Rotation", DemoBoxRotation); DemoHeader("Tick Labels", DemoTickLabels); DemoHeader("Axis Constraints", DemoAxisConstraints); + DemoHeader("Equal Axes", DemoEqualAxes); + DemoHeader("Auto-Fitting Data", DemoAutoFittingData); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Tools")) { + DemoHeader("Mouse Picking", DemoMousePicking); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Custom")) { DemoHeader("Custom Styles", DemoCustomStyles); DemoHeader("Custom Rendering", DemoCustomRendering); + DemoHeader("Custom Overlay", DemoCustomOverlay); + DemoHeader("Custom Per-Point Style", DemoCustomPerPointStyle); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Config")) { + DemoConfig(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Help")) { @@ -1032,6 +1590,7 @@ void ShowAllDemos() { void ShowDemoWindow(bool* p_open) { static bool show_implot3d_metrics = false; static bool show_implot3d_style_editor = false; + static bool show_implot3d_about = false; static bool show_imgui_metrics = false; static bool show_imgui_style_editor = false; static bool show_imgui_demo = false; @@ -1043,6 +1602,8 @@ void ShowDemoWindow(bool* p_open) { ImPlot3D::ShowStyleEditor(); ImGui::End(); } + if (show_implot3d_about) + ImPlot3D::ShowAboutWindow(&show_implot3d_about); if (show_imgui_style_editor) { ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor); ImGui::ShowStyleEditor(); @@ -1060,6 +1621,7 @@ void ShowDemoWindow(bool* p_open) { if (ImGui::BeginMenu("Tools")) { ImGui::MenuItem("Metrics", nullptr, &show_implot3d_metrics); ImGui::MenuItem("Style Editor", nullptr, &show_implot3d_style_editor); + ImGui::MenuItem("About ImPlot3D", nullptr, &show_implot3d_about); ImGui::Separator(); ImGui::MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); ImGui::MenuItem("ImGui Style Editor", nullptr, &show_imgui_style_editor); @@ -1076,20 +1638,6 @@ void ShowDemoWindow(bool* p_open) { // [SECTION] Style Editor //----------------------------------------------------------------------------- -bool ShowStyleSelector(const char* label) { - static int style_idx = -1; - if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0")) { - switch (style_idx) { - case 0: StyleColorsAuto(); break; - case 1: StyleColorsClassic(); break; - case 2: StyleColorsDark(); break; - case 3: StyleColorsLight(); break; - } - return true; - } - return false; -} - bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlot3DColormap cmap) { ImGuiContext& G = *GImGui; const ImGuiStyle& style = G.Style; @@ -1396,6 +1944,86 @@ void ShowStyleEditor(ImPlot3DStyle* ref) { } } +void ShowAboutWindow(bool* p_open) { + if (!ImGui::Begin("About ImPlot3D", p_open, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::End(); + return; + } + + ImGui::Text("ImPlot3D %s (%d)", IMPLOT3D_VERSION, IMPLOT3D_VERSION_NUM); + + ImGui::TextLinkOpenURL("Homepage", "https://github.com/brenocq/implot3d"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Q&A", "https://github.com/brenocq/implot3d/discussions/categories/q-a"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Releases", "https://github.com/brenocq/implot3d/releases"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Sponsors", "https://github.com/sponsors/brenocq"); + + ImGui::Separator(); + ImGui::Text("(c) 2024-2025 Breno Cunha Queiroz"); + ImGui::Text("Developed by Breno Cunha Queiroz and all ImPlot3D contributors."); + ImGui::Text("ImPlot3D is licensed under the MIT License."); + ImGui::Text("If your company uses ImPlot3D, please consider sponsoring the project."); + + static bool show_config_info = false; + ImGui::Checkbox("Config/Build Information", &show_config_info); + if (show_config_info) { + bool copy_to_clipboard = ImGui::Button("Copy to clipboard"); + ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18); + ImGui::BeginChild(ImGui::GetID("cfg_infos"), child_size, ImGuiChildFlags_FrameStyle); + if (copy_to_clipboard) { + ImGui::LogToClipboard(); + ImGui::LogText("```cpp\n"); + } + + ImGui::Text("ImPlot3D %s (%d)", IMPLOT3D_VERSION, IMPLOT3D_VERSION_NUM); + ImGui::Separator(); + ImGui::Text("sizeof(size_t): %d, sizeof(ImPlot3DPoint): %d", (int)sizeof(size_t), (int)sizeof(ImPlot3DPoint)); + +#ifdef IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS + ImGui::Text("define: IMPLOT3D_DISABLE_OBSOLETE_FUNCTIONS"); +#endif + + ImGui::Text("define: __cplusplus=%d", (int)__cplusplus); +#ifdef _WIN32 + ImGui::Text("define: _WIN32"); +#endif +#ifdef _WIN64 + ImGui::Text("define: _WIN64"); +#endif +#ifdef __linux__ + ImGui::Text("define: __linux__"); +#endif +#ifdef __APPLE__ + ImGui::Text("define: __APPLE__"); +#endif +#ifdef _MSC_VER + ImGui::Text("define: _MSC_VER=%d", _MSC_VER); +#endif +#ifdef __MINGW32__ + ImGui::Text("define: __MINGW32__"); +#endif +#ifdef __MINGW64__ + ImGui::Text("define: __MINGW64__"); +#endif +#ifdef __GNUC__ + ImGui::Text("define: __GNUC__=%d", (int)__GNUC__); +#endif +#ifdef __clang_version__ + ImGui::Text("define: __clang_version__=%s", __clang_version__); +#endif + + if (copy_to_clipboard) { + ImGui::LogText("```\n"); + ImGui::LogFinish(); + } + ImGui::EndChild(); + } + + ImGui::End(); +} + } // namespace ImPlot3D //----------------------------------------------------------------------------- diff --git a/implot3d_internal.h b/implot3d_internal.h index d8894ff..6f93658 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -1,15 +1,13 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d_internal.h -// Date: 2024-11-17 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] Constants @@ -58,6 +56,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(double val) { return isnan(val); } // Returns true if val is NAN or INFINITY diff --git a/implot3d_items.cpp b/implot3d_items.cpp index b247fcb..367a027 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -1,15 +1,13 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d_items.cpp -// Date: 2024-11-26 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] Includes @@ -909,7 +907,7 @@ template IMPLOT3D_INLINE T IndexData(const T* data, int idx, int co } template struct IndexerIdx { - IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : Data(data), Count(count), Offset(offset), Stride(stride) {} + IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : Data(data), Count(count), Offset(count ? ImPosMod(offset, count) : 0), Stride(stride) {} template IMPLOT3D_INLINE double operator()(I idx) const { return (double)IndexData(Data, idx, Count, Offset, Stride); } const T* Data; int Count; @@ -1479,6 +1477,11 @@ void PlotText(const char* text, double x, double y, double z, double angle, cons AddTextRotated(GetPlotDrawList(), p, (float)angle, GetStyleColorU32(ImPlot3DCol_InlayText), text); } +void PlotDummy(const char* label_id, ImPlot3DDummyFlags flags) { + if (BeginItem(label_id, flags, ImPlot3DCol_Line)) + EndItem(); +} + } // namespace ImPlot3D #endif // #ifndef IMGUI_DISABLE diff --git a/implot3d_meshes.cpp b/implot3d_meshes.cpp index fd55508..35b73a1 100644 --- a/implot3d_meshes.cpp +++ b/implot3d_meshes.cpp @@ -1,15 +1,13 @@ -//-------------------------------------------------- +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2025 Breno Cunha Queiroz + // ImPlot3D v0.3 WIP -// implot3d_meshes.cpp -// Date: 2024-12-07 -// Author: Breno Cunha Queiroz (brenocq.com) -// + // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. -//-------------------------------------------------- // Table of Contents: // [SECTION] Includes