//--------------------------------------------------
// ImPlot3D v0.1
// implot3d_items.cpp
// Date: 2024-11-26
// Author: Breno Cunha Queiroz (brenocq.com)
//
// Acknowledgments:
//  ImPlot3D is heavily inspired by ImPlot
//  (https://github.com/epezent/implot) by Evan Pezent,
//  and follows a similar code style and structure to
//  maintain consistency with ImPlot's API.
//--------------------------------------------------

// Table of Contents:
// [SECTION] Includes
// [SECTION] Macros & Defines
// [SECTION] Template instantiation utility
// [SECTION] Item Utils
// [SECTION] Draw Utils
// [SECTION] Renderers
// [SECTION] Indexers
// [SECTION] Getters
// [SECTION] RenderPrimitives
// [SECTION] Markers
// [SECTION] PlotScatter
// [SECTION] PlotLine
// [SECTION] PlotTriangle
// [SECTION] PlotText

//-----------------------------------------------------------------------------
// [SECTION] Includes
//-----------------------------------------------------------------------------

#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif

#include "implot3d.h"
#include "implot3d_internal.h"

#ifndef IMGUI_DISABLE

//-----------------------------------------------------------------------------
// [SECTION] Macros & Defines
//-----------------------------------------------------------------------------

#define SQRT_1_2 0.70710678118f
#define SQRT_3_2 0.86602540378f

// clang-format off
#ifndef IMPLOT3D_NO_FORCE_INLINE
    #ifdef _MSC_VER
        #define IMPLOT3D_INLINE __forceinline
    #elif defined(__GNUC__)
        #define IMPLOT3D_INLINE inline __attribute__((__always_inline__))
    #elif defined(__CLANG__)
        #if __has_attribute(__always_inline__)
            #define IMPLOT3D_INLINE inline __attribute__((__always_inline__))
        #else
            #define IMPLOT3D_INLINE inline
        #endif
    #else
        #define IMPLOT3D_INLINE inline
    #endif
#else
    #define IMPLOT3D_INLINE inline
#endif
// clang-format on

#define IMPLOT3D_NORMALIZE2F(VX, VY)     \
    do {                                 \
        float d2 = VX * VX + VY * VY;    \
        if (d2 > 0.0f) {                 \
            float inv_len = ImRsqrt(d2); \
            VX *= inv_len;               \
            VY *= inv_len;               \
        }                                \
    } while (0)

IMPLOT3D_INLINE void GetLineRenderProps(const ImDrawList3D& draw_list_3d, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) {
    const bool aa = ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLines) &&
                    ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLinesUseTex);
    if (aa) {
        ImVec4 tex_uvs = draw_list_3d._SharedData->TexUvLines[(int)(half_weight * 2)];
        tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y);
        tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w);
        half_weight += 1;
    } else {
        tex_uv0 = tex_uv1 = draw_list_3d._SharedData->TexUvWhitePixel;
    }
}

//-----------------------------------------------------------------------------
// [SECTION] Template instantiation utility
//-----------------------------------------------------------------------------

// By default, templates are instantiated for `float`, `double`, and for the following integer types, which are defined in imgui.h:
//     signed char         ImS8;   // 8-bit signed integer
//     unsigned char       ImU8;   // 8-bit unsigned integer
//     signed short        ImS16;  // 16-bit signed integer
//     unsigned short      ImU16;  // 16-bit unsigned integer
//     signed int          ImS32;  // 32-bit signed integer == int
//     unsigned int        ImU32;  // 32-bit unsigned integer
//     signed   long long  ImS64;  // 64-bit signed integer
//     unsigned long long  ImU64;  // 64-bit unsigned integer
// (note: this list does *not* include `long`, `unsigned long` and `long double`)
//
// You can customize the supported types by defining IMPLOT3D_CUSTOM_NUMERIC_TYPES at compile time to define your own type list.
//    As an example, you could use the compile time define given by the line below in order to support only float and double.
//        -DIMPLOT3D_CUSTOM_NUMERIC_TYPES="(float)(double)"
//    In order to support all known C++ types, use:
//        -DIMPLOT3D_CUSTOM_NUMERIC_TYPES="(signed char)(unsigned char)(signed short)(unsigned short)(signed int)(unsigned int)(signed long)(unsigned long)(signed long long)(unsigned long long)(float)(double)(long double)"

#ifdef IMPLOT3D_CUSTOM_NUMERIC_TYPES
#define IMPLOT3D_NUMERIC_TYPES IMPLOT3D_CUSTOM_NUMERIC_TYPES
#else
#define IMPLOT3D_NUMERIC_TYPES (ImS8)(ImU8)(ImS16)(ImU16)(ImS32)(ImU32)(ImS64)(ImU64)(float)(double)
#endif

// CALL_INSTANTIATE_FOR_NUMERIC_TYPES will duplicate the template instantiation code `INSTANTIATE_MACRO(T)` on supported types.
#define _CAT(x, y) _CAT_(x, y)
#define _CAT_(x, y) x##y
#define _INSTANTIATE_FOR_NUMERIC_TYPES(chain) _CAT(_INSTANTIATE_FOR_NUMERIC_TYPES_1 chain, _END)
#define _INSTANTIATE_FOR_NUMERIC_TYPES_1(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_2
#define _INSTANTIATE_FOR_NUMERIC_TYPES_2(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_1
#define _INSTANTIATE_FOR_NUMERIC_TYPES_1_END
#define _INSTANTIATE_FOR_NUMERIC_TYPES_2_END
#define CALL_INSTANTIATE_FOR_NUMERIC_TYPES() _INSTANTIATE_FOR_NUMERIC_TYPES(IMPLOT3D_NUMERIC_TYPES)

//-----------------------------------------------------------------------------
// [SECTION] Item Utils
//-----------------------------------------------------------------------------
namespace ImPlot3D {

static const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f;
static const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f;

bool BeginItem(const char* label_id, ImPlot3DItemFlags flags, ImPlot3DCol recolor_from) {
    ImPlot3DContext& gp = *GImPlot3D;
    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotX() needs to be called between BeginPlot() and EndPlot()!");

    // Lock setup
    SetupLock();

    ImPlot3DStyle& style = gp.Style;
    ImPlot3DNextItemData& n = gp.NextItemData;

    // Register item
    bool just_created;
    ImPlot3DItem* item = RegisterOrGetItem(label_id, flags, &just_created);

    // Set/override item color
    if (recolor_from != -1) {
        if (!IsColorAuto(n.Colors[recolor_from]))
            item->Color = ImGui::ColorConvertFloat4ToU32(n.Colors[recolor_from]);
        else if (!IsColorAuto(gp.Style.Colors[recolor_from]))
            item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]);
        else if (just_created)
            item->Color = NextColormapColorU32();
    } else if (just_created) {
        item->Color = NextColormapColorU32();
    }

    // Set next item color
    ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color);
    n.IsAutoLine = IsColorAuto(n.Colors[ImPlot3DCol_Line]) && IsColorAuto(ImPlot3DCol_Line);
    n.IsAutoFill = IsColorAuto(n.Colors[ImPlot3DCol_Fill]) && IsColorAuto(ImPlot3DCol_Fill);
    n.Colors[ImPlot3DCol_Line] = IsColorAuto(n.Colors[ImPlot3DCol_Line]) ? (IsColorAuto(ImPlot3DCol_Line) ? item_color : gp.Style.Colors[ImPlot3DCol_Line]) : n.Colors[ImPlot3DCol_Line];
    n.Colors[ImPlot3DCol_Fill] = IsColorAuto(n.Colors[ImPlot3DCol_Fill]) ? (IsColorAuto(ImPlot3DCol_Fill) ? item_color : gp.Style.Colors[ImPlot3DCol_Fill]) : n.Colors[ImPlot3DCol_Fill];
    n.Colors[ImPlot3DCol_MarkerOutline] = IsColorAuto(n.Colors[ImPlot3DCol_MarkerOutline]) ? (IsColorAuto(ImPlot3DCol_MarkerOutline) ? n.Colors[ImPlot3DCol_Line] : gp.Style.Colors[ImPlot3DCol_MarkerOutline]) : n.Colors[ImPlot3DCol_MarkerOutline];
    n.Colors[ImPlot3DCol_MarkerFill] = IsColorAuto(n.Colors[ImPlot3DCol_MarkerFill]) ? (IsColorAuto(ImPlot3DCol_MarkerFill) ? n.Colors[ImPlot3DCol_Line] : gp.Style.Colors[ImPlot3DCol_MarkerFill]) : n.Colors[ImPlot3DCol_MarkerFill];

    // Set size & weight
    n.LineWeight = n.LineWeight < 0.0f ? style.LineWeight : n.LineWeight;
    n.Marker = n.Marker < 0 ? style.Marker : n.Marker;
    n.MarkerSize = n.MarkerSize < 0.0f ? style.MarkerSize : n.MarkerSize;
    n.MarkerWeight = n.MarkerWeight < 0.0f ? style.MarkerWeight : n.MarkerWeight;
    n.FillAlpha = n.FillAlpha < 0 ? gp.Style.FillAlpha : n.FillAlpha;

    // Apply alpha modifiers
    n.Colors[ImPlot3DCol_Fill].w *= n.FillAlpha;
    n.Colors[ImPlot3DCol_MarkerFill].w *= n.FillAlpha;

    // Set render flags
    n.RenderLine = n.Colors[ImPlot3DCol_Line].w > 0 && n.LineWeight > 0;
    n.RenderFill = n.Colors[ImPlot3DCol_Fill].w > 0;
    n.RenderMarkerFill = n.Colors[ImPlot3DCol_MarkerFill].w > 0;
    n.RenderMarkerLine = n.Colors[ImPlot3DCol_MarkerOutline].w > 0 && n.MarkerWeight > 0;

    // Don't render if item is hidden
    if (!item->Show) {
        EndItem();
        return false;
    } else {
        // Legend hover highlight
        if (item->LegendHovered) {
            if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlot3DLegendFlags_NoHighlightItem)) {
                n.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE;
                n.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE;
                n.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE;
            }
        }
    }

    return true;
}

template <typename _Getter>
bool BeginItemEx(const char* label_id, const _Getter& getter, ImPlot3DItemFlags flags = 0, ImPlot3DCol recolor_from = IMPLOT3D_AUTO) {
    if (BeginItem(label_id, flags, recolor_from)) {
        ImPlot3DContext& gp = *GImPlot3D;
        ImPlot3DPlot& plot = *gp.CurrentPlot;
        if (plot.FitThisFrame && !ImHasFlag(flags, ImPlot3DItemFlags_NoFit)) {
            for (int i = 0; i < getter.Count; i++)
                plot.ExtendFit(getter(i));
        }
        return true;
    }
    return false;
}

void EndItem() {
    ImPlot3DContext& gp = *GImPlot3D;
    gp.NextItemData.Reset();
}

ImPlot3DItem* RegisterOrGetItem(const char* label_id, ImPlot3DItemFlags flags, bool* just_created) {
    ImPlot3DContext& gp = *GImPlot3D;
    ImPlot3DItemGroup& Items = *gp.CurrentItems;
    ImGuiID id = Items.GetItemID(label_id);
    if (just_created != nullptr)
        *just_created = Items.GetItem(id) == nullptr;
    ImPlot3DItem* item = Items.GetOrAddItem(id);

    // Avoid re-adding the same item to the legend (the legend is reset every frame)
    if (item->SeenThisFrame)
        return item;
    item->SeenThisFrame = true;

    // Add item to the legend
    int idx = Items.GetItemIndex(item);
    item->ID = id;
    if (!ImHasFlag(flags, ImPlot3DItemFlags_NoLegend) && ImGui::FindRenderedTextEnd(label_id, nullptr) != label_id) {
        Items.Legend.Indices.push_back(idx);
        item->NameOffset = Items.Legend.Labels.size();
        Items.Legend.Labels.append(label_id, label_id + strlen(label_id) + 1);
    }
    return item;
}

void BustItemCache() {
    ImPlot3DContext& gp = *GImPlot3D;
    for (int p = 0; p < gp.Plots.GetBufSize(); ++p) {
        ImPlot3DPlot& plot = *gp.Plots.GetByIndex(p);
        plot.Items.Reset();
    }
}

void SetNextLineStyle(const ImVec4& col, float weight) {
    ImPlot3DContext& gp = *GImPlot3D;
    ImPlot3DNextItemData& n = gp.NextItemData;
    n.Colors[ImPlot3DCol_Line] = col;
    n.LineWeight = weight;
}

void SetNextFillStyle(const ImVec4& col, float alpha) {
    ImPlot3DContext& gp = *GImPlot3D;
    ImPlot3DNextItemData& n = gp.NextItemData;
    gp.NextItemData.Colors[ImPlot3DCol_Fill] = col;
    gp.NextItemData.FillAlpha = alpha;
}

void SetNextMarkerStyle(ImPlot3DMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) {
    ImPlot3DContext& gp = *GImPlot3D;
    ImPlot3DNextItemData& n = gp.NextItemData;
    n.Marker = marker;
    n.Colors[ImPlot3DCol_MarkerFill] = fill;
    n.MarkerSize = size;
    n.Colors[ImPlot3DCol_MarkerOutline] = outline;
    n.MarkerWeight = weight;
}

//-----------------------------------------------------------------------------
// [SECTION] Draw Utils
//-----------------------------------------------------------------------------

IMPLOT3D_INLINE void PrimLine(ImDrawList3D& draw_list_3d, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, const ImVec2 tex_uv1, float z) {
    float dx = P2.x - P1.x;
    float dy = P2.y - P1.y;
    IMPLOT3D_NORMALIZE2F(dx, dy);
    dx *= half_weight;
    dy *= half_weight;
    draw_list_3d._VtxWritePtr[0].pos.x = P1.x + dy;
    draw_list_3d._VtxWritePtr[0].pos.y = P1.y - dx;
    draw_list_3d._VtxWritePtr[0].uv = tex_uv0;
    draw_list_3d._VtxWritePtr[0].col = col;
    draw_list_3d._VtxWritePtr[1].pos.x = P2.x + dy;
    draw_list_3d._VtxWritePtr[1].pos.y = P2.y - dx;
    draw_list_3d._VtxWritePtr[1].uv = tex_uv0;
    draw_list_3d._VtxWritePtr[1].col = col;
    draw_list_3d._VtxWritePtr[2].pos.x = P2.x - dy;
    draw_list_3d._VtxWritePtr[2].pos.y = P2.y + dx;
    draw_list_3d._VtxWritePtr[2].uv = tex_uv1;
    draw_list_3d._VtxWritePtr[2].col = col;
    draw_list_3d._VtxWritePtr[3].pos.x = P1.x - dy;
    draw_list_3d._VtxWritePtr[3].pos.y = P1.y + dx;
    draw_list_3d._VtxWritePtr[3].uv = tex_uv1;
    draw_list_3d._VtxWritePtr[3].col = col;
    draw_list_3d._VtxWritePtr += 4;
    draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
    draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
    draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
    draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
    draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
    draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);
    draw_list_3d._IdxWritePtr += 6;
    draw_list_3d._VtxCurrentIdx += 4;
    draw_list_3d._ZWritePtr[0] = z;
    draw_list_3d._ZWritePtr[1] = z;
    draw_list_3d._ZWritePtr += 2;
}

//-----------------------------------------------------------------------------
// [SECTION] Renderers
//-----------------------------------------------------------------------------

float GetPointDepth(ImPlot3DPoint p) {
    ImPlot3DContext& gp = *GImPlot3D;
    ImPlot3DPlot& plot = *gp.CurrentPlot;
    ImPlot3DPoint p_rot = plot.Rotation * p;
    return p_rot.z;
}

struct RendererBase {
    RendererBase(int prims, int idx_consumed, int vtx_consumed) : Prims(prims),
                                                                  IdxConsumed(idx_consumed),
                                                                  VtxConsumed(vtx_consumed) {}
    const unsigned int Prims;       // Number of primitives to render
    const unsigned int IdxConsumed; // Number of indices consumed per primitive
    const unsigned int VtxConsumed; // Number of vertices consumed per primitive
};

template <class _Getter>
struct RendererMarkersFill : RendererBase {
    RendererMarkersFill(const _Getter& getter, const ImVec2* marker, int count, float size, ImU32 col) : RendererBase(getter.Count, (count - 2) * 3, count),
                                                                                                         Getter(getter),
                                                                                                         Marker(marker),
                                                                                                         Count(count),
                                                                                                         Size(size),
                                                                                                         Col(col) {}
    void Init(ImDrawList3D& draw_list_3d) const {
        UV = draw_list_3d._SharedData->TexUvWhitePixel;
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        ImPlot3DPoint p_plot = Getter(prim);
        if (!cull_box.Contains(p_plot))
            return false;
        ImVec2 p = PlotToPixels(p_plot);
        // 3 vertices per triangle
        for (int i = 0; i < Count; i++) {
            draw_list_3d._VtxWritePtr[0].pos.x = p.x + Marker[i].x * Size;
            draw_list_3d._VtxWritePtr[0].pos.y = p.y + Marker[i].y * Size;
            draw_list_3d._VtxWritePtr[0].uv = UV;
            draw_list_3d._VtxWritePtr[0].col = Col;
            draw_list_3d._VtxWritePtr++;
        }
        // 3 indices per triangle
        for (int i = 2; i < Count; i++) {
            // Indices
            draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
            draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i - 1);
            draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i);
            draw_list_3d._IdxWritePtr += 3;
            // Z
            draw_list_3d._ZWritePtr[0] = GetPointDepth(p_plot);
            draw_list_3d._ZWritePtr++;
        }
        // Update vertex count
        draw_list_3d._VtxCurrentIdx += (ImDrawIdx)Count;
        return true;
    }
    const _Getter& Getter;
    const ImVec2* Marker;
    const int Count;
    const float Size;
    const ImU32 Col;
    mutable ImVec2 UV;
};

template <class _Getter>
struct RendererMarkersLine : RendererBase {
    RendererMarkersLine(const _Getter& getter, const ImVec2* marker, int count, float size, float weight, ImU32 col) : RendererBase(getter.Count, count / 2 * 6, count / 2 * 4), Getter(getter), Marker(marker), Count(count), HalfWeight(ImMax(1.0f, weight) * 0.5f), Size(size), Col(col) {}

    void Init(ImDrawList3D& draw_list_3d) const {
        GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        ImPlot3DPoint p_plot = Getter(prim);
        if (!cull_box.Contains(p_plot))
            return false;
        ImVec2 p = PlotToPixels(p_plot);
        for (int i = 0; i < Count; i = i + 2) {
            ImVec2 p1(p.x + Marker[i].x * Size, p.y + Marker[i].y * Size);
            ImVec2 p2(p.x + Marker[i + 1].x * Size, p.y + Marker[i + 1].y * Size);
            PrimLine(draw_list_3d, p1, p2, HalfWeight, Col, UV0, UV1, GetPointDepth(p_plot));
        }
        return true;
    }

    const _Getter& Getter;
    const ImVec2* Marker;
    const int Count;
    mutable float HalfWeight;
    const float Size;
    const ImU32 Col;
    mutable ImVec2 UV0;
    mutable ImVec2 UV1;
};

template <class _Getter>
struct RendererLineStrip : RendererBase {
    RendererLineStrip(const _Getter& getter, ImU32 col, float weight)
        : RendererBase(getter.Count - 1, 6, 4),
          Getter(getter),
          Col(col),
          HalfWeight(ImMax(1.0f, weight) * 0.5f) {
        // Initialize the first point in plot coordinates
        P1_plot = Getter(0);
    }

    void Init(ImDrawList3D& draw_list_3d) const {
        GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        ImPlot3DPoint P2_plot = Getter(prim + 1);

        // Clip the line segment to the culling box using Liang-Barsky algorithm
        ImPlot3DPoint P1_clipped, P2_clipped;
        bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);

        if (visible) {
            // Convert clipped points to pixel coordinates
            ImVec2 P1_screen = PlotToPixels(P1_clipped);
            ImVec2 P2_screen = PlotToPixels(P2_clipped);
            // Render the line segment
            PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
        }

        // Update for next segment
        P1_plot = P2_plot;

        return visible;
    }

    const _Getter& Getter;
    const ImU32 Col;
    mutable float HalfWeight;
    mutable ImPlot3DPoint P1_plot;
    mutable ImVec2 UV0;
    mutable ImVec2 UV1;
};

template <class _Getter>
struct RendererLineStripSkip : RendererBase {
    RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight)
        : RendererBase(getter.Count - 1, 6, 4),
          Getter(getter),
          Col(col),
          HalfWeight(ImMax(1.0f, weight) * 0.5f) {
        // Initialize the first point in plot coordinates
        P1_plot = Getter(0);
    }

    void Init(ImDrawList3D& draw_list_3d) const {
        GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        // Get the next point in plot coordinates
        ImPlot3DPoint P2_plot = Getter(prim + 1);
        bool visible = false;

        // Check for NaNs in P1_plot and P2_plot
        if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) &&
            !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) {

            // Clip the line segment to the culling box
            ImPlot3DPoint P1_clipped, P2_clipped;
            visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);

            if (visible) {
                // Convert clipped points to pixel coordinates
                ImVec2 P1_screen = PlotToPixels(P1_clipped);
                ImVec2 P2_screen = PlotToPixels(P2_clipped);
                // Render the line segment
                PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
            }
        }

        // Update P1_plot if P2_plot is valid
        if (!ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z))
            P1_plot = P2_plot;

        return visible;
    }

    const _Getter& Getter;
    const ImU32 Col;
    mutable float HalfWeight;
    mutable ImPlot3DPoint P1_plot;
    mutable ImVec2 UV0;
    mutable ImVec2 UV1;
};

template <class _Getter>
struct RendererLineSegments : RendererBase {
    RendererLineSegments(const _Getter& getter, ImU32 col, float weight)
        : RendererBase(getter.Count / 2, 6, 4),
          Getter(getter),
          Col(col),
          HalfWeight(ImMax(1.0f, weight) * 0.5f) {}

    void Init(ImDrawList3D& draw_list_3d) const {
        GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1);
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        // Get the segment's endpoints in plot coordinates
        ImPlot3DPoint P1_plot = Getter(prim * 2 + 0);
        ImPlot3DPoint P2_plot = Getter(prim * 2 + 1);

        // Check for NaNs in P1_plot and P2_plot
        if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) &&
            !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) {

            // Clip the line segment to the culling box
            ImPlot3DPoint P1_clipped, P2_clipped;
            bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped);

            if (visible) {
                // Convert clipped points to pixel coordinates
                ImVec2 P1_screen = PlotToPixels(P1_clipped);
                ImVec2 P2_screen = PlotToPixels(P2_clipped);
                // Render the line segment
                PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5f));
            }
            return visible;
        }

        return false;
    }

    const _Getter& Getter;
    const ImU32 Col;
    mutable float HalfWeight;
    mutable ImVec2 UV0;
    mutable ImVec2 UV1;
};

template <class _Getter>
struct RendererTriangleFill : RendererBase {
    RendererTriangleFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 3, 3, 3),
                                                             Getter(getter),
                                                             Col(col) {}
    void Init(ImDrawList3D& draw_list_3d) const {
        UV = draw_list_3d._SharedData->TexUvWhitePixel;
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        ImPlot3DPoint p_plot[3];
        p_plot[0] = Getter(3 * prim);
        p_plot[1] = Getter(3 * prim + 1);
        p_plot[2] = Getter(3 * prim + 2);

        // Check if the triangle is outside the culling box
        if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]))
            return false;

        // Project the triangle vertices to screen space
        ImVec2 p[3];
        p[0] = PlotToPixels(p_plot[0]);
        p[1] = PlotToPixels(p_plot[1]);
        p[2] = PlotToPixels(p_plot[2]);

        // 3 vertices per triangle
        draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
        draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
        draw_list_3d._VtxWritePtr[0].uv = UV;
        draw_list_3d._VtxWritePtr[0].col = Col;
        draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
        draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
        draw_list_3d._VtxWritePtr[1].uv = UV;
        draw_list_3d._VtxWritePtr[1].col = Col;
        draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
        draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
        draw_list_3d._VtxWritePtr[2].uv = UV;
        draw_list_3d._VtxWritePtr[2].col = Col;
        draw_list_3d._VtxWritePtr += 3;

        // 3 indices per triangle
        draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
        draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
        draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
        draw_list_3d._IdxWritePtr += 3;
        // 1 Z per vertex
        draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3);
        draw_list_3d._ZWritePtr++;

        // Update vertex count
        draw_list_3d._VtxCurrentIdx += 3;

        return true;
    }

    const _Getter& Getter;
    mutable ImVec2 UV;
    const ImU32 Col;
};

template <class _Getter>
struct RendererQuadFill : RendererBase {
    RendererQuadFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 4, 6, 4),
                                                         Getter(getter),
                                                         Col(col) {}
    void Init(ImDrawList3D& draw_list_3d) const {
        UV = draw_list_3d._SharedData->TexUvWhitePixel;
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        ImPlot3DPoint p_plot[4];
        p_plot[0] = Getter(4 * prim);
        p_plot[1] = Getter(4 * prim + 1);
        p_plot[2] = Getter(4 * prim + 2);
        p_plot[3] = Getter(4 * prim + 3);

        // Check if the quad is outside the culling box
        if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) &&
            !cull_box.Contains(p_plot[2]) && !cull_box.Contains(p_plot[3]))
            return false;

        // Project the quad vertices to screen space
        ImVec2 p[4];
        p[0] = PlotToPixels(p_plot[0]);
        p[1] = PlotToPixels(p_plot[1]);
        p[2] = PlotToPixels(p_plot[2]);
        p[3] = PlotToPixels(p_plot[3]);

        // Add vertices for two triangles
        draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
        draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
        draw_list_3d._VtxWritePtr[0].uv = UV;
        draw_list_3d._VtxWritePtr[0].col = Col;

        draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
        draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
        draw_list_3d._VtxWritePtr[1].uv = UV;
        draw_list_3d._VtxWritePtr[1].col = Col;

        draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
        draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
        draw_list_3d._VtxWritePtr[2].uv = UV;
        draw_list_3d._VtxWritePtr[2].col = Col;

        draw_list_3d._VtxWritePtr[3].pos.x = p[3].x;
        draw_list_3d._VtxWritePtr[3].pos.y = p[3].y;
        draw_list_3d._VtxWritePtr[3].uv = UV;
        draw_list_3d._VtxWritePtr[3].col = Col;

        draw_list_3d._VtxWritePtr += 4;

        // Add indices for two triangles
        draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
        draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
        draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);

        draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
        draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
        draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);

        draw_list_3d._IdxWritePtr += 6;

        // Add depth values for the two triangles
        draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3.0f);
        draw_list_3d._ZWritePtr[1] = GetPointDepth((p_plot[0] + p_plot[2] + p_plot[3]) / 3.0f);
        draw_list_3d._ZWritePtr += 2;

        // Update vertex count
        draw_list_3d._VtxCurrentIdx += 4;

        return true;
    }

    const _Getter& Getter;
    mutable ImVec2 UV;
    const ImU32 Col;
};

template <class _Getter>
struct RendererSurfaceFill : RendererBase {
    RendererSurfaceFill(const _Getter& getter, int x_count, int y_count, ImU32 col) : RendererBase((x_count - 1) * (y_count - 1), 6, 4),
                                                                                      Getter(getter),
                                                                                      XCount(x_count),
                                                                                      YCount(y_count),
                                                                                      Col(col) {}
    void Init(ImDrawList3D& draw_list_3d) const {
        UV = draw_list_3d._SharedData->TexUvWhitePixel;

        // Compute min and max values for the colormap (if not solid fill)
        const ImPlot3DNextItemData& n = GetItemData();
        if (n.IsAutoFill) {
            Min = FLT_MAX;
            Max = -FLT_MAX;
            for (int i = 0; i < Getter.Count; i++) {
                float z = Getter(i).z;
                Min = ImMin(Min, z);
                Max = ImMax(Max, z);
            }
        }
    }

    IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const {
        int x = prim % (XCount - 1);
        int y = prim / (XCount - 1);

        ImPlot3DPoint p_plot[4];
        p_plot[0] = Getter(x + y * XCount);
        p_plot[1] = Getter(x + 1 + y * XCount);
        p_plot[2] = Getter(x + 1 + (y + 1) * XCount);
        p_plot[3] = Getter(x + (y + 1) * XCount);

        // Check if the quad is outside the culling box
        if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) &&
            !cull_box.Contains(p_plot[2]) && !cull_box.Contains(p_plot[3]))
            return false;

        // Compute colors
        ImU32 cols[4] = {Col, Col, Col, Col};
        const ImPlot3DNextItemData& n = GetItemData();
        if (n.IsAutoFill) {
            float alpha = GImPlot3D->NextItemData.FillAlpha;
            for (int i = 0; i < 4; i++) {
                ImVec4 col = SampleColormap(ImClamp(ImRemap01(p_plot[i].z, Min, Max), 0.0f, 1.0f));
                col.w *= alpha;
                cols[i] = ImGui::ColorConvertFloat4ToU32(col);
            }
        }

        // Project the quad vertices to screen space
        ImVec2 p[4];
        p[0] = PlotToPixels(p_plot[0]);
        p[1] = PlotToPixels(p_plot[1]);
        p[2] = PlotToPixels(p_plot[2]);
        p[3] = PlotToPixels(p_plot[3]);

        // Add vertices for two triangles
        draw_list_3d._VtxWritePtr[0].pos.x = p[0].x;
        draw_list_3d._VtxWritePtr[0].pos.y = p[0].y;
        draw_list_3d._VtxWritePtr[0].uv = UV;
        draw_list_3d._VtxWritePtr[0].col = cols[0];

        draw_list_3d._VtxWritePtr[1].pos.x = p[1].x;
        draw_list_3d._VtxWritePtr[1].pos.y = p[1].y;
        draw_list_3d._VtxWritePtr[1].uv = UV;
        draw_list_3d._VtxWritePtr[1].col = cols[1];

        draw_list_3d._VtxWritePtr[2].pos.x = p[2].x;
        draw_list_3d._VtxWritePtr[2].pos.y = p[2].y;
        draw_list_3d._VtxWritePtr[2].uv = UV;
        draw_list_3d._VtxWritePtr[2].col = cols[2];

        draw_list_3d._VtxWritePtr[3].pos.x = p[3].x;
        draw_list_3d._VtxWritePtr[3].pos.y = p[3].y;
        draw_list_3d._VtxWritePtr[3].uv = UV;
        draw_list_3d._VtxWritePtr[3].col = cols[3];

        draw_list_3d._VtxWritePtr += 4;

        // Add indices for two triangles
        draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
        draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1);
        draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);

        draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx);
        draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2);
        draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3);

        draw_list_3d._IdxWritePtr += 6;

        // Add depth values for the two triangles
        draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3.0f);
        draw_list_3d._ZWritePtr[1] = GetPointDepth((p_plot[0] + p_plot[2] + p_plot[3]) / 3.0f);
        draw_list_3d._ZWritePtr += 2;

        // Update vertex count
        draw_list_3d._VtxCurrentIdx += 4;

        return true;
    }

    const _Getter& Getter;
    mutable ImVec2 UV;
    mutable float Min; // Minimum value for the colormap
    mutable float Max; // Minimum value for the colormap
    const int XCount;
    const int YCount;
    const ImU32 Col;
};

//-----------------------------------------------------------------------------
// [SECTION] Indexers
//-----------------------------------------------------------------------------

template <typename T>
IMPLOT3D_INLINE T IndexData(const T* data, int idx, int count, int offset, int stride) {
    const int s = ((offset == 0) << 0) | ((stride == sizeof(T)) << 1);
    switch (s) {
        case 3: return data[idx];
        case 2: return data[(offset + idx) % count];
        case 1: return *(const T*)(const void*)((const unsigned char*)data + (size_t)((idx)) * stride);
        case 0: return *(const T*)(const void*)((const unsigned char*)data + (size_t)((offset + idx) % count) * stride);
        default: return T(0);
    }
}

template <typename T>
struct IndexerIdx {
    IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : Data(data),
                                                                                   Count(count),
                                                                                   Offset(offset),
                                                                                   Stride(stride) {}
    template <typename I> IMPLOT3D_INLINE double operator()(I idx) const {
        return (double)IndexData(Data, idx, Count, Offset, Stride);
    }
    const T* Data;
    int Count;
    int Offset;
    int Stride;
};

//-----------------------------------------------------------------------------
// [SECTION] Getters
//-----------------------------------------------------------------------------

template <typename _IndexerX, typename _IndexerY, typename _IndexerZ>
struct GetterXYZ {
    GetterXYZ(_IndexerX x, _IndexerY y, _IndexerZ z, int count) : IndexerX(x), IndexerY(y), IndexerZ(z), Count(count) {}
    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        return ImPlot3DPoint(IndexerX(idx), IndexerY(idx), IndexerZ(idx));
    }
    const _IndexerX IndexerX;
    const _IndexerY IndexerY;
    const _IndexerZ IndexerZ;
    const int Count;
};

template <typename _Getter>
struct GetterLoop {
    GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) {}
    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        idx = idx % (Count - 1);
        return Getter(idx);
    }
    const _Getter Getter;
    const int Count;
};

template <typename _Getter>
struct GetterTriangleLines {
    GetterTriangleLines(_Getter getter) : Getter(getter), Count(getter.Count * 2) {}
    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        idx = ((idx % 6 + 1) / 2) % 3 + idx / 6 * 3;
        return Getter(idx);
    }
    const _Getter Getter;
    const int Count;
};

template <typename _Getter>
struct GetterQuadLines {
    GetterQuadLines(_Getter getter) : Getter(getter), Count(getter.Count * 2) {}
    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        idx = ((idx % 8 + 1) / 2) % 4 + idx / 8 * 4;
        return Getter(idx);
    }
    const _Getter Getter;
    const int Count;
};

template <typename _Getter>
struct GetterSurfaceLines {
    GetterSurfaceLines(_Getter getter, int x_count, int y_count)
        : Getter(getter), XCount(x_count), YCount(y_count) {
        int horizontal_segments = (XCount - 1) * YCount;
        int vertical_segments = (YCount - 1) * XCount;
        int segments = horizontal_segments + vertical_segments;
        Count = segments * 2; // Each segment has 2 endpoints
    }

    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        // idx is an endpoint index
        int endpoint_i = (int)(idx % 2);
        int segment_i = (int)(idx / 2);

        int horizontal_segments = (XCount - 1) * YCount;

        int px, py;
        if (segment_i < horizontal_segments) {
            // Horizontal segment
            int row = segment_i / (XCount - 1);
            int col = segment_i % (XCount - 1);
            // Endpoint 0 is (col, row), endpoint 1 is (col+1, row)
            px = endpoint_i == 0 ? col : col + 1;
            py = row;
        } else {
            // Vertical segment
            int seg_v = segment_i - horizontal_segments;
            int col = seg_v / (YCount - 1);
            int row = seg_v % (YCount - 1);
            // Endpoint 0 is (col, row), endpoint 1 is (col, row+1)
            px = col;
            py = row + endpoint_i;
        }

        return Getter(py * XCount + px);
    }

    const _Getter Getter;
    int Count;
    const int XCount;
    const int YCount;
};

struct Getter3DPoints {
    Getter3DPoints(const ImPlot3DPoint* points, int count) : Points(points), Count(count) {}
    template <typename I> IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const {
        return Points[idx];
    }
    const ImPlot3DPoint* Points;
    const int Count;
};

struct GetterMeshTriangles {
    GetterMeshTriangles(const ImPlot3DPoint* vtx, const unsigned int* idx, int idx_count)
        : Vtx(vtx), Idx(idx), IdxCount(idx_count), TriCount(idx_count / 3), Count(idx_count) {}

    template <typename I>
    IMPLOT3D_INLINE ImPlot3DPoint operator()(I i) const {
        unsigned int vi = Idx[i];
        return Vtx[vi];
    }

    const ImPlot3DPoint* Vtx;
    const unsigned int* Idx;
    int IdxCount;
    int TriCount;
    int Count;
};

//-----------------------------------------------------------------------------
// [SECTION] RenderPrimitives
//-----------------------------------------------------------------------------

/// Renders primitive shapes
template <template <class> class _Renderer, class _Getter, typename... Args>
void RenderPrimitives(const _Getter& getter, Args... args) {
    _Renderer<_Getter> renderer(getter, args...);
    ImPlot3DPlot& plot = *GetCurrentPlot();
    ImDrawList3D& draw_list_3d = plot.DrawList;
    ImPlot3DBox cull_box;
    if (ImHasFlag(plot.Flags, ImPlot3DFlags_NoClip)) {
        cull_box.Min = ImPlot3DPoint(-HUGE_VAL, -HUGE_VAL, -HUGE_VAL);
        cull_box.Max = ImPlot3DPoint(HUGE_VAL, HUGE_VAL, HUGE_VAL);
    } else {
        cull_box.Min = plot.RangeMin();
        cull_box.Max = plot.RangeMax();
    }

    // Find how many can be reserved up to end of current draw command's limit
    unsigned int prims_to_render = ImMin(renderer.Prims, (ImDrawList3D::MaxIdx() - draw_list_3d._VtxCurrentIdx) / renderer.VtxConsumed);

    // Reserve vertices and indices to render the primitives
    draw_list_3d.PrimReserve(prims_to_render * renderer.IdxConsumed, prims_to_render * renderer.VtxConsumed);

    // Initialize renderer
    renderer.Init(draw_list_3d);

    // Render primitives
    int num_culled = 0;
    for (unsigned int i = 0; i < prims_to_render; i++)
        if (!renderer.Render(draw_list_3d, cull_box, i))
            num_culled++;
    // Unreserve unused vertices and indices
    draw_list_3d.PrimUnreserve(num_culled * renderer.IdxConsumed, num_culled * renderer.VtxConsumed);
}

//-----------------------------------------------------------------------------
// [SECTION] Markers
//-----------------------------------------------------------------------------

static const ImVec2 MARKER_FILL_CIRCLE[10] = {ImVec2(1.0f, 0.0f), ImVec2(0.809017f, 0.58778524f), ImVec2(0.30901697f, 0.95105654f), ImVec2(-0.30901703f, 0.9510565f), ImVec2(-0.80901706f, 0.5877852f), ImVec2(-1.0f, 0.0f), ImVec2(-0.80901694f, -0.58778536f), ImVec2(-0.3090171f, -0.9510565f), ImVec2(0.30901712f, -0.9510565f), ImVec2(0.80901694f, -0.5877853f)};
static const ImVec2 MARKER_FILL_SQUARE[4] = {ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2)};
static const ImVec2 MARKER_FILL_DIAMOND[4] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)};
static const ImVec2 MARKER_FILL_UP[3] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f)};
static const ImVec2 MARKER_FILL_DOWN[3] = {ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f)};
static const ImVec2 MARKER_FILL_LEFT[3] = {ImVec2(-1, 0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)};
static const ImVec2 MARKER_FILL_RIGHT[3] = {ImVec2(1, 0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)};
static const ImVec2 MARKER_LINE_CIRCLE[20] = {
    ImVec2(1.0f, 0.0f),
    ImVec2(0.809017f, 0.58778524f),
    ImVec2(0.809017f, 0.58778524f),
    ImVec2(0.30901697f, 0.95105654f),
    ImVec2(0.30901697f, 0.95105654f),
    ImVec2(-0.30901703f, 0.9510565f),
    ImVec2(-0.30901703f, 0.9510565f),
    ImVec2(-0.80901706f, 0.5877852f),
    ImVec2(-0.80901706f, 0.5877852f),
    ImVec2(-1.0f, 0.0f),
    ImVec2(-1.0f, 0.0f),
    ImVec2(-0.80901694f, -0.58778536f),
    ImVec2(-0.80901694f, -0.58778536f),
    ImVec2(-0.3090171f, -0.9510565f),
    ImVec2(-0.3090171f, -0.9510565f),
    ImVec2(0.30901712f, -0.9510565f),
    ImVec2(0.30901712f, -0.9510565f),
    ImVec2(0.80901694f, -0.5877853f),
    ImVec2(0.80901694f, -0.5877853f),
    ImVec2(1.0f, 0.0f)};
static const ImVec2 MARKER_LINE_SQUARE[8] = {ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, SQRT_1_2)};
static const ImVec2 MARKER_LINE_DIAMOND[8] = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(-1, 0), ImVec2(0, 1), ImVec2(0, 1), ImVec2(1, 0)};
static const ImVec2 MARKER_LINE_UP[6] = {ImVec2(SQRT_3_2, 0.5f), ImVec2(0, -1), ImVec2(0, -1), ImVec2(-SQRT_3_2, 0.5f), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, 0.5f)};
static const ImVec2 MARKER_LINE_DOWN[6] = {ImVec2(SQRT_3_2, -0.5f), ImVec2(0, 1), ImVec2(0, 1), ImVec2(-SQRT_3_2, -0.5f), ImVec2(-SQRT_3_2, -0.5f), ImVec2(SQRT_3_2, -0.5f)};
static const ImVec2 MARKER_LINE_LEFT[6] = {ImVec2(-1, 0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2), ImVec2(0.5, -SQRT_3_2), ImVec2(-1, 0)};
static const ImVec2 MARKER_LINE_RIGHT[6] = {ImVec2(1, 0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(1, 0)};
static const ImVec2 MARKER_LINE_ASTERISK[6] = {ImVec2(-SQRT_3_2, -0.5f), ImVec2(SQRT_3_2, 0.5f), ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, -0.5f), ImVec2(0, -1), ImVec2(0, 1)};
static const ImVec2 MARKER_LINE_PLUS[4] = {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, 1)};
static const ImVec2 MARKER_LINE_CROSS[4] = {ImVec2(-SQRT_1_2, -SQRT_1_2), ImVec2(SQRT_1_2, SQRT_1_2), ImVec2(SQRT_1_2, -SQRT_1_2), ImVec2(-SQRT_1_2, SQRT_1_2)};

template <typename _Getter>
void RenderMarkers(const _Getter& getter, ImPlot3DMarker marker, float size, bool rend_fill, ImU32 col_fill, bool rend_line, ImU32 col_line, float weight) {
    if (rend_fill) {
        switch (marker) {
            case ImPlot3DMarker_Circle: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_CIRCLE, 10, size, col_fill); break;
            case ImPlot3DMarker_Square: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_SQUARE, 4, size, col_fill); break;
            case ImPlot3DMarker_Diamond: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_DIAMOND, 4, size, col_fill); break;
            case ImPlot3DMarker_Up: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_UP, 3, size, col_fill); break;
            case ImPlot3DMarker_Down: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_DOWN, 3, size, col_fill); break;
            case ImPlot3DMarker_Left: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_LEFT, 3, size, col_fill); break;
            case ImPlot3DMarker_Right: RenderPrimitives<RendererMarkersFill>(getter, MARKER_FILL_RIGHT, 3, size, col_fill); break;
        }
    }
    if (rend_line) {
        switch (marker) {
            case ImPlot3DMarker_Circle: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_CIRCLE, 20, size, weight, col_line); break;
            case ImPlot3DMarker_Square: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_SQUARE, 8, size, weight, col_line); break;
            case ImPlot3DMarker_Diamond: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_DIAMOND, 8, size, weight, col_line); break;
            case ImPlot3DMarker_Up: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_UP, 6, size, weight, col_line); break;
            case ImPlot3DMarker_Down: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_DOWN, 6, size, weight, col_line); break;
            case ImPlot3DMarker_Left: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_LEFT, 6, size, weight, col_line); break;
            case ImPlot3DMarker_Right: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_RIGHT, 6, size, weight, col_line); break;
            case ImPlot3DMarker_Asterisk: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_ASTERISK, 6, size, weight, col_line); break;
            case ImPlot3DMarker_Plus: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_PLUS, 4, size, weight, col_line); break;
            case ImPlot3DMarker_Cross: RenderPrimitives<RendererMarkersLine>(getter, MARKER_LINE_CROSS, 4, size, weight, col_line); break;
        }
    }
}

//-----------------------------------------------------------------------------
// [SECTION] PlotScatter
//-----------------------------------------------------------------------------

template <typename Getter>
void PlotScatterEx(const char* label_id, const Getter& getter, ImPlot3DScatterFlags flags) {
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_MarkerOutline)) {
        const ImPlot3DNextItemData& n = GetItemData();
        ImPlot3DMarker marker = n.Marker == ImPlot3DMarker_None ? ImPlot3DMarker_Circle : n.Marker;
        const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
        const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
        if (marker != ImPlot3DMarker_None)
            RenderMarkers<Getter>(getter, marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        EndItem();
    }
}

template <typename T>
void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, int offset, int stride) {
    if (count < 1)
        return;
    GetterXYZ<IndexerIdx<T>, IndexerIdx<T>, IndexerIdx<T>> getter(IndexerIdx<T>(xs, count, offset, stride), IndexerIdx<T>(ys, count, offset, stride), IndexerIdx<T>(zs, count, offset, stride), count);
    return PlotScatterEx(label_id, getter, flags);
}

#define INSTANTIATE_MACRO(T) \
    template IMPLOT3D_API void PlotScatter<T>(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, int offset, int stride);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

//-----------------------------------------------------------------------------
// [SECTION] PlotLine
//-----------------------------------------------------------------------------

template <typename _Getter>
void PlotLineEx(const char* label_id, const _Getter& getter, ImPlot3DLineFlags flags) {
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Line)) {
        const ImPlot3DNextItemData& n = GetItemData();
        if (getter.Count >= 2 && n.RenderLine) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
            if (ImHasFlag(flags, ImPlot3DLineFlags_Segments)) {
                RenderPrimitives<RendererLineSegments>(getter, col_line, n.LineWeight);
            } else if (ImHasFlag(flags, ImPlot3DLineFlags_Loop)) {
                if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN))
                    RenderPrimitives<RendererLineStripSkip>(GetterLoop<_Getter>(getter), col_line, n.LineWeight);
                else
                    RenderPrimitives<RendererLineStrip>(GetterLoop<_Getter>(getter), col_line, n.LineWeight);
            } else {
                if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN))
                    RenderPrimitives<RendererLineStripSkip>(getter, col_line, n.LineWeight);
                else
                    RenderPrimitives<RendererLineStrip>(getter, col_line, n.LineWeight);
            }
        }

        // Render markers
        if (n.Marker != ImPlot3DMarker_None) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
            RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        }
        EndItem();
    }
}

IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, int offset, int stride) {
    if (count < 2)
        return;
    GetterXYZ<IndexerIdx<T>, IndexerIdx<T>, IndexerIdx<T>> getter(IndexerIdx<T>(xs, count, offset, stride), IndexerIdx<T>(ys, count, offset, stride), IndexerIdx<T>(zs, count, offset, stride), count);
    return PlotLineEx(label_id, getter, flags);
}

#define INSTANTIATE_MACRO(T) \
    template IMPLOT3D_API void PlotLine<T>(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, int offset, int stride);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

//-----------------------------------------------------------------------------
// [SECTION] PlotTriangle
//-----------------------------------------------------------------------------

template <typename _Getter>
void PlotTriangleEx(const char* label_id, const _Getter& getter, ImPlot3DTriangleFlags flags) {
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
        const ImPlot3DNextItemData& n = GetItemData();

        // Render fill
        if (getter.Count >= 3 && n.RenderFill) {
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
            RenderPrimitives<RendererTriangleFill>(getter, col_fill);
        }

        // Render lines
        if (getter.Count >= 2 && n.RenderLine) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
            RenderPrimitives<RendererLineSegments>(GetterTriangleLines<_Getter>(getter), col_line, n.LineWeight);
        }

        // Render markers
        if (n.Marker != ImPlot3DMarker_None) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
            RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        }

        EndItem();
    }
}

IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags, int offset, int stride) {
    if (count < 3)
        return;
    GetterXYZ<IndexerIdx<T>, IndexerIdx<T>, IndexerIdx<T>> getter(IndexerIdx<T>(xs, count, offset, stride), IndexerIdx<T>(ys, count, offset, stride), IndexerIdx<T>(zs, count, offset, stride), count);
    return PlotTriangleEx(label_id, getter, flags);
}

#define INSTANTIATE_MACRO(T) \
    template IMPLOT3D_API void PlotTriangle<T>(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DTriangleFlags flags, int offset, int stride);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

//-----------------------------------------------------------------------------
// [SECTION] PlotQuad
//-----------------------------------------------------------------------------

template <typename _Getter>
void PlotQuadEx(const char* label_id, const _Getter& getter, ImPlot3DQuadFlags flags) {
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
        const ImPlot3DNextItemData& n = GetItemData();

        // Render fill
        if (getter.Count >= 4 && n.RenderFill) {
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
            RenderPrimitives<RendererQuadFill>(getter, col_fill);
        }

        // Render lines
        if (getter.Count >= 2 && n.RenderLine) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
            RenderPrimitives<RendererLineSegments>(GetterQuadLines<_Getter>(getter), col_line, n.LineWeight);
        }

        // Render markers
        if (n.Marker != ImPlot3DMarker_None) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
            RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        }

        EndItem();
    }
}

IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags, int offset, int stride) {
    if (count < 3)
        return;
    GetterXYZ<IndexerIdx<T>, IndexerIdx<T>, IndexerIdx<T>> getter(IndexerIdx<T>(xs, count, offset, stride), IndexerIdx<T>(ys, count, offset, stride), IndexerIdx<T>(zs, count, offset, stride), count);
    return PlotQuadEx(label_id, getter, flags);
}

#define INSTANTIATE_MACRO(T) \
    template IMPLOT3D_API void PlotQuad<T>(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags, int offset, int stride);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

//-----------------------------------------------------------------------------
// [SECTION] PlotSurface
//-----------------------------------------------------------------------------

template <typename _Getter>
void PlotSurfaceEx(const char* label_id, const _Getter& getter, int x_count, int y_count, ImPlot3DSurfaceFlags flags) {
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
        const ImPlot3DNextItemData& n = GetItemData();

        // Render fill
        if (getter.Count >= 4 && n.RenderFill) {
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
            RenderPrimitives<RendererSurfaceFill>(getter, x_count, y_count, col_fill);
        }

        // Render lines
        if (getter.Count >= 2 && n.RenderLine) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
            RenderPrimitives<RendererLineSegments>(GetterSurfaceLines<_Getter>(getter, x_count, y_count), col_line, n.LineWeight);
        }

        // Render markers
        if (n.Marker != ImPlot3DMarker_None) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
            RenderMarkers<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        }

        EndItem();
    }
}

IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, ImPlot3DSurfaceFlags flags, int offset, int stride) {
    int count = x_count * y_count;
    if (count < 4)
        return;
    GetterXYZ<IndexerIdx<T>, IndexerIdx<T>, IndexerIdx<T>> getter(IndexerIdx<T>(xs, count, offset, stride), IndexerIdx<T>(ys, count, offset, stride), IndexerIdx<T>(zs, count, offset, stride), count);
    return PlotSurfaceEx(label_id, getter, x_count, y_count, flags);
}

#define INSTANTIATE_MACRO(T) \
    template IMPLOT3D_API void PlotSurface<T>(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, ImPlot3DSurfaceFlags flags, int offset, int stride);
CALL_INSTANTIATE_FOR_NUMERIC_TYPES()
#undef INSTANTIATE_MACRO

void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags) {
    Getter3DPoints getter(vtx, vtx_count);                     // Get vertices
    GetterMeshTriangles getter_triangles(vtx, idx, idx_count); // Get triangle vertices
    if (BeginItemEx(label_id, getter, flags, ImPlot3DCol_Fill)) {
        const ImPlot3DNextItemData& n = GetItemData();

        // Render fill
        if (getter.Count >= 3 && n.RenderFill) {
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]);
            RenderPrimitives<RendererTriangleFill>(getter_triangles, col_fill);
        }

        // Render lines
        if (getter.Count >= 2 && n.RenderLine && !n.IsAutoLine) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]);
            RenderPrimitives<RendererLineSegments>(GetterTriangleLines<GetterMeshTriangles>(getter_triangles), col_line, n.LineWeight);
        }

        // Render markers
        if (n.Marker != ImPlot3DMarker_None) {
            const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerOutline]);
            const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_MarkerFill]);
            RenderMarkers(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight);
        }

        EndItem();
    }
}

//-----------------------------------------------------------------------------
// [SECTION] PlotText
//-----------------------------------------------------------------------------

void PlotText(const char* text, float x, float y, float z, float angle, const ImVec2& pix_offset) {
    ImPlot3DContext& gp = *GImPlot3D;
    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotText() needs to be called between BeginPlot() and EndPlot()!");
    SetupLock();
    ImPlot3DPlot& plot = *gp.CurrentPlot;

    ImPlot3DBox cull_box;
    if (ImHasFlag(plot.Flags, ImPlot3DFlags_NoClip)) {
        cull_box.Min = ImPlot3DPoint(-HUGE_VAL, -HUGE_VAL, -HUGE_VAL);
        cull_box.Max = ImPlot3DPoint(HUGE_VAL, HUGE_VAL, HUGE_VAL);
    } else {
        cull_box.Min = plot.RangeMin();
        cull_box.Max = plot.RangeMax();
    }
    if (!cull_box.Contains(ImPlot3DPoint(x, y, z)))
        return;

    ImVec2 p = PlotToPixels(ImPlot3DPoint(x, y, z));
    p.x += pix_offset.x;
    p.y += pix_offset.y;
    AddTextRotated(GetPlotDrawList(), p, angle, GetStyleColorU32(ImPlot3DCol_InlayText), text);
}

} // namespace ImPlot3D

#endif // #ifndef IMGUI_DISABLE
