//-------------------------------------------------- // ImPlot3D v0.1 // implot3d_internal.h // Date: 2024-11-17 // Author: Breno Cunha Queiroz (brenocq.com) // // Acknowledgments: // ImPlot3D is heavily inspired by ImPlot // (https://github.com/epezent/implot) by Evan Pezent, // and follows a similar code style and structure to // maintain consistency with ImPlot's API. //-------------------------------------------------- // Table of Contents: // [SECTION] Constants // [SECTION] Generic Helpers // [SECTION] Forward Declarations // [SECTION] Callbacks // [SECTION] Structs // [SECTION] Context Pointer // [SECTION] Context Utils // [SECTION] Style Utils // [SECTION] Item Utils // [SECTION] Plot Utils // [SECTION] Setup Utils // [SECTION] Formatter // [SECTION] Locator #pragma once #ifndef IMPLOT3D_VERSION #include "implot3d.h" #endif #ifndef IMGUI_DISABLE #include "imgui_internal.h" //----------------------------------------------------------------------------- // [SECTION] Constants //----------------------------------------------------------------------------- // Default label format for axis labels #define IMPLOT3D_LABEL_FORMAT "%g" // Max character size for tick labels #define IMPLOT3D_LABEL_MAX_SIZE 32 //----------------------------------------------------------------------------- // [SECTION] Generic Helpers //----------------------------------------------------------------------------- namespace ImPlot3D { // Computes the common (base-10) logarithm static inline float ImLog10(float x) { return log10f(x); } // Returns true if flag is set template static inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; } // Flips a flag in a flagset template static inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; } template static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } // Returns true if val is NAN static inline bool ImNan(float val) { return isnan(val); } // Returns true if val is NAN or INFINITY static inline bool ImNanOrInf(float val) { return !(val >= -FLT_MAX && val <= FLT_MAX) || ImNan(val); } // Turns NANs to 0s static inline double ImConstrainNan(float val) { return ImNan(val) ? 0 : val; } // Turns infinity to floating point maximums static inline double ImConstrainInf(double val) { return val >= FLT_MAX ? FLT_MAX : val <= -FLT_MAX ? -FLT_MAX : val; } // True if two numbers are approximately equal using units in the last place. static inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1 - v2) < FLT_EPSILON * ImAbs(v1 + v2) * ulp || ImAbs(v1 - v2) < FLT_MIN; } // Set alpha channel of 32-bit color from float in range [0.0 1.0] static inline ImU32 ImAlphaU32(ImU32 col, float alpha) { return col & ~((ImU32)((1.0f - alpha) * 255) << IM_COL32_A_SHIFT); } // Mix color a and b by factor s in [0 256] static inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) { #ifdef IMPLOT3D_MIX64 const ImU32 af = 256 - s; const ImU32 bf = s; const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24); const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24); const ImU64 mix = (al * af + bl * bf); return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8); #else const ImU32 af = 256 - s; const ImU32 bf = s; const ImU32 al = (a & 0x00ff00ff); const ImU32 ah = (a & 0xff00ff00) >> 8; const ImU32 bl = (b & 0x00ff00ff); const ImU32 bh = (b & 0xff00ff00) >> 8; const ImU32 ml = (al * af + bl * bf); const ImU32 mh = (ah * af + bh * bf); return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8); #endif } } // namespace ImPlot3D //----------------------------------------------------------------------------- // [SECTION] Forward Declarations //----------------------------------------------------------------------------- struct ImPlot3DTicker; //------------------------------------------------------------------------------ // [SECTION] Callbacks //------------------------------------------------------------------------------ typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data); //----------------------------------------------------------------------------- // [SECTION] Structs //----------------------------------------------------------------------------- struct ImDrawList3D { ImVector IdxBuffer = {}; // Index buffer ImVector VtxBuffer = {}; // Vertex buffer ImVector ZBuffer = {}; // Z buffer. Depth value for each triangle unsigned int _VtxCurrentIdx = 0; // [Internal] current vertex index ImDrawVert* _VtxWritePtr = nullptr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawIdx* _IdxWritePtr = nullptr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) float* _ZWritePtr = nullptr; // [Internal] point within ZBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawListFlags _Flags = ImDrawListFlags_None; // [Internal] draw list flags ImDrawListSharedData* _SharedData = nullptr; // [Internal] shared draw list data ImDrawList3D() { } void PrimReserve(int idx_count, int vtx_count); void PrimUnreserve(int idx_count, int vtx_count); void SortedMoveToImGuiDrawList(); constexpr static unsigned int MaxIdx() { return sizeof(ImDrawIdx) == 2 ? 65535 : 4294967295; } }; struct ImPlot3DNextItemData { ImVec4 Colors[4]; // ImPlot3DCol_Line, ImPlot3DCol_Fill, ImPlot3DCol_MarkerOutline, ImPlot3DCol_MarkerFill, float LineWeight; ImPlot3DMarker Marker; float MarkerSize; float MarkerWeight; float FillAlpha; bool RenderLine; bool RenderFill; bool RenderMarkerLine; bool RenderMarkerFill; bool IsAutoFill; bool IsAutoLine; bool Hidden; ImPlot3DNextItemData() { Reset(); } void Reset() { for (int i = 0; i < 4; i++) Colors[i] = IMPLOT3D_AUTO_COL; LineWeight = IMPLOT3D_AUTO; Marker = IMPLOT3D_AUTO; MarkerSize = IMPLOT3D_AUTO; MarkerWeight = IMPLOT3D_AUTO; FillAlpha = IMPLOT3D_AUTO; RenderLine = false; RenderFill = false; RenderMarkerLine = true; RenderMarkerFill = true; IsAutoFill = true; IsAutoLine = true; Hidden = false; } }; // Colormap data storage struct ImPlot3DColormapData { ImVector Keys; ImVector KeyCounts; ImVector KeyOffsets; ImVector Tables; ImVector TableSizes; ImVector TableOffsets; ImGuiTextBuffer Text; ImVector TextOffsets; ImVector Quals; ImGuiStorage Map; int Count; ImPlot3DColormapData() { Count = 0; } int Append(const char* name, const ImU32* keys, int count, bool qual) { if (GetIndex(name) != -1) return -1; KeyOffsets.push_back(Keys.size()); KeyCounts.push_back(count); Keys.reserve(Keys.size() + count); for (int i = 0; i < count; ++i) Keys.push_back(keys[i]); TextOffsets.push_back(Text.size()); Text.append(name, name + strlen(name) + 1); Quals.push_back(qual); ImGuiID id = ImHashStr(name); int idx = Count++; Map.SetInt(id, idx); _AppendTable(idx); return idx; } void _AppendTable(ImPlot3DColormap cmap) { int key_count = GetKeyCount(cmap); const ImU32* keys = GetKeys(cmap); int off = Tables.size(); TableOffsets.push_back(off); if (IsQual(cmap)) { Tables.reserve(key_count); for (int i = 0; i < key_count; ++i) Tables.push_back(keys[i]); TableSizes.push_back(key_count); } else { int max_size = 255 * (key_count - 1) + 1; Tables.reserve(off + max_size); // ImU32 last = keys[0]; // Tables.push_back(last); // int n = 1; for (int i = 0; i < key_count - 1; ++i) { for (int s = 0; s < 255; ++s) { ImU32 a = keys[i]; ImU32 b = keys[i + 1]; ImU32 c = ImPlot3D::ImMixU32(a, b, s); // if (c != last) { Tables.push_back(c); // last = c; // n++; // } } } ImU32 c = keys[key_count - 1]; // if (c != last) { Tables.push_back(c); // n++; // } // TableSizes.push_back(n); TableSizes.push_back(max_size); } } void RebuildTables() { Tables.resize(0); TableSizes.resize(0); TableOffsets.resize(0); for (int i = 0; i < Count; ++i) _AppendTable(i); } inline bool IsQual(ImPlot3DColormap cmap) const { return Quals[cmap]; } inline const char* GetName(ImPlot3DColormap cmap) const { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : nullptr; } inline ImPlot3DColormap GetIndex(const char* name) const { ImGuiID key = ImHashStr(name); return Map.GetInt(key, -1); } inline const ImU32* GetKeys(ImPlot3DColormap cmap) const { return &Keys[KeyOffsets[cmap]]; } inline int GetKeyCount(ImPlot3DColormap cmap) const { return KeyCounts[cmap]; } inline ImU32 GetKeyColor(ImPlot3DColormap cmap, int idx) const { return Keys[KeyOffsets[cmap] + idx]; } inline void SetKeyColor(ImPlot3DColormap cmap, int idx, ImU32 value) { Keys[KeyOffsets[cmap] + idx] = value; RebuildTables(); } inline const ImU32* GetTable(ImPlot3DColormap cmap) const { return &Tables[TableOffsets[cmap]]; } inline int GetTableSize(ImPlot3DColormap cmap) const { return TableSizes[cmap]; } inline ImU32 GetTableColor(ImPlot3DColormap cmap, int idx) const { return Tables[TableOffsets[cmap] + idx]; } inline ImU32 LerpTable(ImPlot3DColormap cmap, float t) const { int off = TableOffsets[cmap]; int siz = TableSizes[cmap]; int idx = Quals[cmap] ? ImClamp((int)(siz * t), 0, siz - 1) : (int)((siz - 1) * t + 0.5f); return Tables[off + idx]; } }; // State information for plot items struct ImPlot3DItem { ImGuiID ID; ImU32 Color; int NameOffset; bool Show; bool LegendHovered; bool SeenThisFrame; ImPlot3DItem() { ID = 0; Color = IM_COL32_WHITE; NameOffset = -1; Show = true; LegendHovered = false; SeenThisFrame = false; } ~ImPlot3DItem() { ID = 0; } }; // Holds legend state struct ImPlot3DLegend { ImPlot3DLegendFlags Flags; ImPlot3DLegendFlags PreviousFlags; ImPlot3DLocation Location; ImPlot3DLocation PreviousLocation; ImVector Indices; ImGuiTextBuffer Labels; ImRect Rect; bool Hovered; bool Held; ImPlot3DLegend() { PreviousFlags = Flags = ImPlot3DLegendFlags_None; Hovered = Held = false; PreviousLocation = Location = ImPlot3DLocation_NorthWest; } void Reset() { Indices.shrink(0); Labels.Buf.shrink(0); } }; // Holds items struct ImPlot3DItemGroup { ImPool ItemPool; ImPlot3DLegend Legend; int ColormapIdx; ImPlot3DItemGroup() { ColormapIdx = 0; } int GetItemCount() const { return ItemPool.GetBufSize(); } ImGuiID GetItemID(const char* label_id) { return ImGui::GetID(label_id); } ImPlot3DItem* GetItem(ImGuiID id) { return ItemPool.GetByKey(id); } ImPlot3DItem* GetItem(const char* label_id) { return GetItem(GetItemID(label_id)); } ImPlot3DItem* GetOrAddItem(ImGuiID id) { return ItemPool.GetOrAddByKey(id); } ImPlot3DItem* GetItemByIndex(int i) { return ItemPool.GetByIndex(i); } int GetItemIndex(ImPlot3DItem* item) { return ItemPool.GetIndex(item); } int GetLegendCount() const { return Legend.Indices.size(); } ImPlot3DItem* GetLegendItem(int i) { return ItemPool.GetByIndex(Legend.Indices[i]); } const char* GetLegendLabel(int i) { return Legend.Labels.Buf.Data + GetLegendItem(i)->NameOffset; } void Reset() { ItemPool.Clear(); Legend.Reset(); ColormapIdx = 0; } }; // Tick mark info struct ImPlot3DTick { float PlotPos; bool Major; bool ShowLabel; ImVec2 LabelSize; int TextOffset; int Idx; ImPlot3DTick(double value, bool major, bool show_label) { PlotPos = value; Major = major; ShowLabel = show_label; TextOffset = -1; } }; // Collection of ticks struct ImPlot3DTicker { ImVector Ticks; ImGuiTextBuffer TextBuffer; ImPlot3DTicker() { Reset(); } ImPlot3DTick& AddTick(double value, bool major, bool show_label, const char* label) { ImPlot3DTick tick(value, major, show_label); if (show_label && label != nullptr) { tick.TextOffset = TextBuffer.size(); TextBuffer.append(label, label + strlen(label) + 1); tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); } return AddTick(tick); } ImPlot3DTick& AddTick(double value, bool major, bool show_label, ImPlot3DFormatter formatter, void* data) { ImPlot3DTick tick(value, major, show_label); if (show_label && formatter != nullptr) { char buff[IMPLOT3D_LABEL_MAX_SIZE]; tick.TextOffset = TextBuffer.size(); formatter(tick.PlotPos, buff, sizeof(buff), data); TextBuffer.append(buff, buff + strlen(buff) + 1); tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); } return AddTick(tick); } inline ImPlot3DTick& AddTick(ImPlot3DTick tick) { tick.Idx = Ticks.size(); Ticks.push_back(tick); return Ticks.back(); } const char* GetText(int idx) const { return TextBuffer.Buf.Data + Ticks[idx].TextOffset; } const char* GetText(const ImPlot3DTick& tick) const { return GetText(tick.Idx); } void Reset() { Ticks.shrink(0); TextBuffer.Buf.shrink(0); } int TickCount() const { return Ticks.Size; } }; // Holds axis information struct ImPlot3DAxis { ImPlot3DAxisFlags Flags; ImPlot3DAxisFlags PreviousFlags; ImPlot3DRange Range; ImPlot3DCond RangeCond; ImGuiTextBuffer Label; // Ticks ImPlot3DTicker Ticker; ImPlot3DFormatter Formatter; void* FormatterData; ImPlot3DLocator Locator; // Fit data bool FitThisFrame; ImPlot3DRange FitExtents; // User input bool Held; // Constructor ImPlot3DAxis() { PreviousFlags = Flags = ImPlot3DAxisFlags_None; // Range Range.Min = 0.0f; Range.Max = 1.0f; RangeCond = ImPlot3DCond_None; // Ticks Formatter = nullptr; FormatterData = nullptr; Locator = nullptr; // Fit data FitThisFrame = true; FitExtents.Min = HUGE_VAL; FitExtents.Max = -HUGE_VAL; // User input Held = false; } inline void SetRange(double v1, double v2) { Range.Min = ImMin(v1, v2); Range.Max = ImMax(v1, v2); } inline bool SetMin(double _min, bool force = false) { if (!force && IsLockedMin()) return false; _min = ImPlot3D::ImConstrainNan(ImPlot3D::ImConstrainInf(_min)); if (_min >= Range.Max) return false; Range.Min = _min; return true; } inline bool SetMax(double _max, bool force = false) { if (!force && IsLockedMax()) return false; _max = ImPlot3D::ImConstrainNan(ImPlot3D::ImConstrainInf(_max)); if (_max <= Range.Min) return false; Range.Max = _max; return true; } inline bool IsRangeLocked() const { return RangeCond == ImPlot3DCond_Always; } inline bool IsLockedMin() const { return IsRangeLocked() || ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_LockMin); } inline bool IsLockedMax() const { return IsRangeLocked() || ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_LockMax); } inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); } inline void SetLabel(const char* label) { Label.Buf.shrink(0); if (label && ImGui::FindRenderedTextEnd(label, nullptr) != label) Label.append(label, label + strlen(label) + 1); } inline const char* GetLabel() const { return Label.Buf.Data; } bool HasLabel() const; bool HasGridLines() const; bool HasTickLabels() const; bool HasTickMarks() const; bool IsAutoFitting() const; void ExtendFit(float value); void ApplyFit(); float PlotToNDC(float value) const; float NDCToPlot(float value) const; }; // Holds plot state information that must persist after EndPlot struct ImPlot3DPlot { ImGuiID ID; ImPlot3DFlags Flags; ImPlot3DFlags PreviousFlags; ImGuiTextBuffer Title; bool JustCreated; bool Initialized; // Bounding rectangles ImRect FrameRect; // Outermost bounding rectangle that encapsulates whole the plot/title/padding/etc ImRect CanvasRect; // Frame rectangle reduced by padding ImRect PlotRect; // Bounding rectangle for the actual plot area // Rotation & Axes ImPlot3DQuat Rotation; ImPlot3DAxis Axes[3]; // Animation float AnimationTime; // Remaining animation time ImPlot3DQuat RotationAnimationEnd; // End rotation for animation // User input bool SetupLocked; bool Hovered; bool Held; int HeldEdgeIdx; // Index of the edge being held int HeldPlaneIdx; // Index of the plane being held // Fit data bool FitThisFrame; // Items ImPlot3DItemGroup Items; ImPlot3DItem* CurrentItem; // 3D draw list ImDrawList3D DrawList; // Misc bool ContextClick; // True if context button was clicked (to distinguish from double click) bool OpenContextThisFrame; ImPlot3DPlot() { PreviousFlags = Flags = ImPlot3DFlags_None; JustCreated = true; Initialized = false; Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f); for (int i = 0; i < 3; i++) Axes[i] = ImPlot3DAxis(); AnimationTime = 0.0f; RotationAnimationEnd = Rotation; SetupLocked = false; Hovered = Held = false; HeldEdgeIdx = -1; HeldPlaneIdx = -1; FitThisFrame = true; CurrentItem = nullptr; ContextClick = false; OpenContextThisFrame = false; } inline void SetTitle(const char* title) { Title.Buf.shrink(0); if (title && ImGui::FindRenderedTextEnd(title, nullptr) != title) Title.append(title, title + strlen(title) + 1); } inline bool HasTitle() const { return !Title.empty() && !ImPlot3D::ImHasFlag(Flags, ImPlot3DFlags_NoTitle); } inline const char* GetTitle() const { return Title.Buf.Data; } void ExtendFit(const ImPlot3DPoint& point); ImPlot3DPoint RangeMin() const; ImPlot3DPoint RangeMax() const; ImPlot3DPoint RangeCenter() const; void SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max); }; struct ImPlot3DContext { ImPool Plots; ImPlot3DPlot* CurrentPlot; ImPlot3DItemGroup* CurrentItems; ImPlot3DNextItemData NextItemData; ImPlot3DStyle Style; ImVector ColorModifiers; ImVector StyleModifiers; ImVector ColormapModifiers; ImPlot3DColormapData ColormapData; }; //----------------------------------------------------------------------------- // [SECTION] Context Pointer //----------------------------------------------------------------------------- namespace ImPlot3D { #ifndef GImPlot3D extern IMPLOT3D_API ImPlot3DContext* GImPlot3D; // Current context pointer #endif //----------------------------------------------------------------------------- // [SECTION] Context Utils //----------------------------------------------------------------------------- IMPLOT3D_API void InitializeContext(ImPlot3DContext* ctx); // Initialize ImPlot3DContext IMPLOT3D_API void ResetContext(ImPlot3DContext* ctx); // Reset ImPlot3DContext //----------------------------------------------------------------------------- // [SECTION] Style Utils //----------------------------------------------------------------------------- IMPLOT3D_API bool IsColorAuto(const ImVec4& col); IMPLOT3D_API bool IsColorAuto(ImPlot3DCol idx); IMPLOT3D_API ImVec4 GetAutoColor(ImPlot3DCol idx); IMPLOT3D_API const char* GetStyleColorName(ImPlot3DCol idx); // Get styling data for next item (call between BeginItem/EndItem) IMPLOT3D_API const ImPlot3DNextItemData& GetItemData(); // Returns a color from the Color map given an index >= 0 (modulo will be performed) IMPLOT3D_API ImU32 GetColormapColorU32(int idx, ImPlot3DColormap cmap); // Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired IMPLOT3D_API ImU32 NextColormapColorU32(); //----------------------------------------------------------------------------- // [SECTION] Item Utils //----------------------------------------------------------------------------- IMPLOT3D_API bool BeginItem(const char* label_id, ImPlot3DItemFlags flags = 0, ImPlot3DCol recolor_from = IMPLOT3D_AUTO); IMPLOT3D_API void EndItem(); // Register or get an existing item from the current plot IMPLOT3D_API ImPlot3DItem* RegisterOrGetItem(const char* label_id, ImPlot3DItemFlags flags, bool* just_created = nullptr); // Busts the cache for every item for every plot in the current context IMPLOT3D_API void BustItemCache(); // TODO move to another place IMPLOT3D_API void AddTextRotated(ImDrawList* draw_list, ImVec2 pos, float angle, ImU32 col, const char* text_begin, const char* text_end = nullptr); //----------------------------------------------------------------------------- // [SECTION] Plot Utils //----------------------------------------------------------------------------- // Gets the current plot from the current ImPlot3DContext IMPLOT3D_API ImPlot3DPlot* GetCurrentPlot(); // Busts the cache for every plot in the current context IMPLOT3D_API void BustPlotCache(); IMPLOT3D_API ImVec2 GetFramePos(); // Get the current frame position (top-left) in pixels IMPLOT3D_API ImVec2 GetFrameSize(); // Get the current frame size in pixels // Convert a position in the current plot's coordinate system to the current plot's normalized device coordinate system (NDC) // When the cube aspect ratio is [1,1,1], the NDC varies from [-0.5, 0.5] in each axis IMPLOT3D_API ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point); IMPLOT3D_API ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point); // Convert a position in the current plot's NDC to pixels IMPLOT3D_API ImVec2 NDCToPixels(const ImPlot3DPoint& point); // Convert a pixel coordinate to a ray in the NDC IMPLOT3D_API ImPlot3DRay PixelsToNDCRay(const ImVec2& pix); // Convert a ray in the NDC to a ray in the current plot's coordinate system IMPLOT3D_API ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray); //----------------------------------------------------------------------------- // [SECTION] Setup Utils //----------------------------------------------------------------------------- IMPLOT3D_API void SetupLock(); //----------------------------------------------------------------------------- // [SECTION] Formatter //----------------------------------------------------------------------------- int Formatter_Default(float value, char* buff, int size, void* data); //------------------------------------------------------------------------------ // [SECTION] Locator //------------------------------------------------------------------------------ void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data); } // namespace ImPlot3D #endif // #ifndef IMGUI_DISABLE