From 9f7c33474f376d5c37e387ce164c55705b427663 Mon Sep 17 00:00:00 2001 From: vincentjzy <13809474@qq.com> Date: Wed, 31 Dec 2025 11:09:44 +0800 Subject: [PATCH] Feature: Implementation of Per Point Custom Color for Plots of Line, Scatter and Surface --- implot3d.cpp | 9 ++ implot3d.h | 30 +++- implot3d_demo.cpp | 129 ++++++++++++++--- implot3d_items.cpp | 354 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 479 insertions(+), 43 deletions(-) diff --git a/implot3d.cpp b/implot3d.cpp index 8affab1..4fd809f 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -3069,6 +3069,15 @@ ImU32 SampleColormapU32(float t, ImPlot3DColormap cmap) { ImVec4 SampleColormap(float t, ImPlot3DColormap cmap) { return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t, cmap)); } +template +void ConvertValueToColor(T* value, ImU32* cs, int count, float v_min, float v_max, ImPlot3DColormap colormap) { + for (int i = 0; i < count; i++) + cs[i] = SampleColormapU32(ImClamp(ImRemap01(static_cast(value[i]), v_min, v_max), 0.0f, 1.0f), colormap); +} +template void ConvertValueToColor(double* value, ImU32* cs, int count, float v_min, float v_max, ImPlot3DColormap colormap); +template void ConvertValueToColor(float* value, ImU32* cs, int count, float v_min, float v_max, ImPlot3DColormap colormap); +template void ConvertValueToColor(int* value, ImU32* cs, int count, float v_min, float v_max, ImPlot3DColormap colormap); + void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) { const int n = continuous ? size - 1 : size; ImU32 col1, col2; diff --git a/implot3d.h b/implot3d.h index 19a9cad..6ec9c79 100644 --- a/implot3d.h +++ b/implot3d.h @@ -193,6 +193,7 @@ enum ImPlot3DScatterFlags_ { ImPlot3DScatterFlags_None = 0, // Default ImPlot3DScatterFlags_NoLegend = ImPlot3DItemFlags_NoLegend, ImPlot3DScatterFlags_NoFit = ImPlot3DItemFlags_NoFit, + ImPlot3DScatterFlags_PerPointCustomColor = 1 << 2, // Each point is assigned with a specific color according to its value }; // Flags for PlotLine @@ -200,9 +201,10 @@ enum ImPlot3DLineFlags_ { ImPlot3DLineFlags_None = 0, // Default ImPlot3DLineFlags_NoLegend = ImPlot3DItemFlags_NoLegend, ImPlot3DLineFlags_NoFit = ImPlot3DItemFlags_NoFit, - ImPlot3DLineFlags_Segments = 1 << 10, // A line segment will be rendered from every two consecutive points - ImPlot3DLineFlags_Loop = 1 << 11, // The last and first point will be connected to form a closed loop - ImPlot3DLineFlags_SkipNaN = 1 << 12, // NaNs values will be skipped instead of rendered as missing data + ImPlot3DLineFlags_Segments = 1 << 10, // A line segment will be rendered from every two consecutive points + ImPlot3DLineFlags_Loop = 1 << 11, // The last and first point will be connected to form a closed loop + ImPlot3DLineFlags_SkipNaN = 1 << 12, // NaNs values will be skipped instead of rendered as missing data + ImPlot3DLineFlags_PerPointCustomColor = 1 << 13, // Each point is assigned with a specific color according to its value }; // Flags for PlotTriangle @@ -233,6 +235,7 @@ enum ImPlot3DSurfaceFlags_ { ImPlot3DSurfaceFlags_NoLines = 1 << 10, // No lines will be rendered ImPlot3DSurfaceFlags_NoFill = 1 << 11, // No fill will be rendered ImPlot3DSurfaceFlags_NoMarkers = 1 << 12, // No markers will be rendered + ImPlot3DSurfaceFlags_PerPointCustomColor = 1 << 13, // No markers will be rendered }; // Flags for PlotMesh @@ -504,10 +507,18 @@ IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags fla 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 scatter plot in 3D. Each point is assigned with specific color +IMPLOT3D_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, 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 a line in 3D. Each point is assigned with specific color +IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, 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)); @@ -522,6 +533,10 @@ 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)); +// Plot the surface defined by a grid of vertices. Each vertex is assigned with a specific color +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, 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); @@ -672,6 +687,9 @@ IMPLOT3D_API ImVec4 GetColormapColor(int idx, ImPlot3DColormap cmap = IMPLOT3D_A // Sample a color from the current colormap given t between 0 and 1 IMPLOT3D_API ImVec4 SampleColormap(float t, ImPlot3DColormap cmap = IMPLOT3D_AUTO); +// Convert values to colors using a specific colormap. +IMPLOT3D_TMP void ConvertValueToColor(T* value, ImU32* cs, int count, float v_min, float v_max, ImPlot3DColormap colormap); + //----------------------------------------------------------------------------- // [SECTION] Demo //----------------------------------------------------------------------------- @@ -704,8 +722,10 @@ IMPLOT3D_API void ShowAboutWindow(bool* p_open = nullptr); // ImPlot3DPoint: 3D vector to store points in 3D space struct ImPlot3DPoint { 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) {} + ImU32 c; //color + constexpr ImPlot3DPoint() : x(0.0), y(0.0), z(0.0), c(ImU32()) {} + constexpr ImPlot3DPoint(double _x, double _y, double _z) : x(_x), y(_y), z(_z), c(ImU32()) {} + constexpr ImPlot3DPoint(double _x, double _y, double _z, ImU32 _c) : x(_x), y(_y), z(_z), c(_c) {} // Accessors double& operator[](size_t idx) { diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 611b70c..31bc597 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -26,6 +26,7 @@ #define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS #endif +#include "implot.h" #include "implot3d.h" #include "implot3d_internal.h" @@ -106,49 +107,114 @@ int MetricFormatter(double value, char* buff, int size, void* data) { //----------------------------------------------------------------------------- void DemoLinePlots() { - static float xs1[1001], ys1[1001], zs1[1001]; + static ImU32 colors1[1001], colors2[20]; + static float scale_min = FLT_MAX; + static float scale_max = -FLT_MAX; + const char* colormaps[] = { "Viridis", "Plasma", "Hot", "Cool", "Pink", "Jet", "Twilight", "RdBu", "BrBG", "PiYG", "Spectral", "Greys" }; + static int sel_colormap = 5; // Jet by default + + static float xs1[1001], ys1[1001], zs1[1001], vs1[1001]; for (int i = 0; i < 1001; i++) { xs1[i] = i * 0.001f; ys1[i] = 0.5f + 0.5f * cosf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); zs1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + vs1[i] = sin(i * 0.15f); + scale_min = (scale_min > vs1[i]) ? vs1[i] : scale_min; + scale_max = (scale_max < vs1[i]) ? vs1[i] : scale_max; } - static double xs2[20], ys2[20], zs2[20]; + static float xs2[20], ys2[20], zs2[20], vs2[20]; for (int i = 0; i < 20; i++) { xs2[i] = i * 1 / 19.0f; ys2[i] = xs2[i] * xs2[i]; zs2[i] = xs2[i] * ys2[i]; + vs2[i] = cos(i * 0.15f); + } + + static bool ppc_color = false; + ImGui::Checkbox("Per Point Custom Color", &ppc_color); + if(!ppc_color) { + if (ImPlot3D::BeginPlot("Line Plots")) { + ImPlot3D::SetupAxes("x", "y", "z"); + ImPlot3D::PlotLine("f(x)", xs1, ys1, zs1, 1001); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle); + ImPlot3D::PlotLine("g(x)", xs2, ys2, zs2, 20, ImPlot3DLineFlags_Segments); + ImPlot3D::EndPlot(); + } } - if (ImPlot3D::BeginPlot("Line Plots")) { - ImPlot3D::SetupAxes("x", "y", "z"); - ImPlot3D::PlotLine("f(x)", xs1, ys1, zs1, 1001); - ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle); - ImPlot3D::PlotLine("g(x)", xs2, ys2, zs2, 20, ImPlot3DLineFlags_Segments); - ImPlot3D::EndPlot(); + else { + ImGui::Combo("##ScatterColormap", &sel_colormap, colormaps, IM_ARRAYSIZE(colormaps)); + ImGui::SameLine(); + ImGui::Text("Choose colormap"); + ImGui::DragFloatRange2("Min / Max", &scale_min, &scale_max, 0.05); + ImPlot3DColormap cmap = ImPlot3D::GetColormapIndex(colormaps[sel_colormap]); + ImPlot3D::ConvertValueToColor(vs1, colors1, 1001, scale_min, scale_max, cmap); + ImPlot3D::ConvertValueToColor(vs2, colors2, 20, scale_min, scale_max, cmap); + ImPlot::ColormapScale("##Z-Scale", scale_min, scale_max, ImVec2(60, 400), "%.2f", 0, cmap); + ImGui::SameLine(); + if (ImPlot3D::BeginPlot("Line Plots with Per Point Custom Colors")) { + ImPlot3D::SetupAxes("x", "y", "z"); + ImPlot3D::PlotLine("f(x)", xs1, ys1, zs1, colors1, 1001, ImPlot3DLineFlags_PerPointCustomColor); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Circle); + ImPlot3D::PlotLine("g(x)", xs2, ys2, zs2, colors2, 20, ImPlot3DLineFlags_Segments | ImPlot3DLineFlags_PerPointCustomColor); + ImPlot3D::EndPlot(); + } } } void DemoScatterPlots() { + static ImU32 colors1[100], colors2[50]; + static float scale_min = FLT_MAX; + static float scale_max = -FLT_MAX; + const char* colormaps[] = { "Viridis", "Plasma", "Hot", "Cool", "Pink", "Jet", "Twilight", "RdBu", "BrBG", "PiYG", "Spectral", "Greys" }; + static int sel_colormap = 5; // Jet by default + srand(0); - static float xs1[100], ys1[100], zs1[100]; + static float xs1[100], ys1[100], zs1[100], vs1[100]; for (int i = 0; i < 100; i++) { xs1[i] = i * 0.01f; ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); zs1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); + vs1[i] = cos(i * 0.1f); + scale_min = (scale_min > vs1[i]) ? vs1[i] : scale_min; + scale_max = (scale_max < vs1[i]) ? vs1[i] : scale_max; } - static float xs2[50], ys2[50], zs2[50]; + static float xs2[50], ys2[50], zs2[50], vs2[50]; for (int i = 0; i < 50; i++) { xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX); ys2[i] = 0.50f + 0.2f * ((float)rand() / (float)RAND_MAX); zs2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX); + vs2[i] = cos(i * 0.1f); + } + + static bool ppc_color = false; + ImGui::Checkbox("Per Point Custom Color", &ppc_color); + if (!ppc_color) { + if (ImPlot3D::BeginPlot("Scatter Plots")) { + ImPlot3D::PlotScatter("Data 1", xs1, ys1, zs1, 100); + ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.25f); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 6, ImPlot3D::GetColormapColor(1), IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(1)); + ImPlot3D::PlotScatter("Data 2", xs2, ys2, zs2, 50); + ImPlot3D::PopStyleVar(); + ImPlot3D::EndPlot(); + } } - - if (ImPlot3D::BeginPlot("Scatter Plots")) { - ImPlot3D::PlotScatter("Data 1", xs1, ys1, zs1, 100); - ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.25f); - ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 6, ImPlot3D::GetColormapColor(1), IMPLOT3D_AUTO, ImPlot3D::GetColormapColor(1)); - ImPlot3D::PlotScatter("Data 2", xs2, ys2, zs2, 50); - ImPlot3D::PopStyleVar(); - ImPlot3D::EndPlot(); + else { + ImGui::Combo("##ScatterColormap", &sel_colormap, colormaps, IM_ARRAYSIZE(colormaps)); + ImGui::SameLine(); + ImGui::Text("Choose colormap"); + ImGui::DragFloatRange2("Min / Max", &scale_min, &scale_max, 0.05); + ImPlot3DColormap cmap = ImPlot3D::GetColormapIndex(colormaps[sel_colormap]); + ImPlot3D::ConvertValueToColor(vs1, colors1, 100, scale_min, scale_max, cmap); + ImPlot3D::ConvertValueToColor(vs2, colors2, 50, scale_min, scale_max, cmap); + ImPlot::ColormapScale("##Z-Scale", scale_min, scale_max, ImVec2(60, 400), "%.2f", 0, cmap); + ImGui::SameLine(); + if (ImPlot3D::BeginPlot("Scatter Plots with Per Point Custom Colors")) { + ImPlot3D::PlotScatter("Data 1", xs1, ys1, zs1, colors1, 100, ImPlot3DScatterFlags_PerPointCustomColor); + ImPlot3D::PushStyleVar(ImPlot3DStyleVar_FillAlpha, 0.25f); + ImPlot3D::SetNextMarkerStyle(ImPlot3DMarker_Square, 6); + ImPlot3D::PlotScatter("Data 2", xs2, ys2, zs2, colors2, 50, ImPlot3DScatterFlags_PerPointCustomColor); + ImPlot3D::EndPlot(); + } } } @@ -314,7 +380,7 @@ void DemoQuadPlots() { void DemoSurfacePlots() { constexpr int N = 20; - static float xs[N * N], ys[N * N], zs[N * N]; + static float xs[N * N], ys[N * N], zs[N * N], vs[N * N]; static float t = 0.0f; t += ImGui::GetIO().DeltaTime; @@ -323,6 +389,12 @@ void DemoSurfacePlots() { constexpr float max_val = 1.0f; constexpr float step = (max_val - min_val) / (N - 1); + // Define parameters for per-point color + ImU32 colors[N * N]; + static float scale_min = FLT_MAX; + static float scale_max = -FLT_MAX; + ImPlot3DColormap cmap; + // Populate the xs, ys, and zs arrays for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { @@ -330,9 +402,12 @@ void DemoSurfacePlots() { xs[idx] = min_val + j * step; // X values are constant along rows ys[idx] = min_val + i * step; // Y values are constant along columns zs[idx] = ImSin(2 * t + ImSqrt((xs[idx] * xs[idx] + ys[idx] * ys[idx]))); // z = sin(2t + sqrt(x^2 + y^2)) + vs[idx] = ImSin(10 * ImSqrt((xs[idx] * xs[idx] + ys[idx] * ys[idx]))); + scale_min = (scale_min > vs[i]) ? vs[i] : scale_min; + scale_max = (scale_max < vs[i]) ? vs[i] : scale_max; } } - + // Choose fill color ImGui::Text("Fill color"); static int selected_fill = 1; // Colormap by default @@ -381,6 +456,7 @@ void DemoSurfacePlots() { CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoLines); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoFill); CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_NoMarkers); + CHECKBOX_FLAG(flags, ImPlot3DSurfaceFlags_PerPointCustomColor); // Begin the plot if (selected_fill == 1) @@ -401,9 +477,18 @@ void DemoSurfacePlots() { // Plot the surface if (custom_range) - ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N, (double)range_min, (double)range_max, flags); + if(flags & ImPlot3DSurfaceFlags_PerPointCustomColor) + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, colors, N, N, (double)range_min, (double)range_max, flags); + else + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N, (double)range_min, (double)range_max, flags); else - ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N, 0.0, 0.0, flags); + if (flags & ImPlot3DSurfaceFlags_PerPointCustomColor) { + // Map values to colors + ImPlot3D::ConvertValueToColor(vs, colors, N * N, scale_min, scale_max, ImPlot3D::GetColormapIndex(colormaps[sel_colormap])); + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, colors, N, N, 0.0, 0.0, flags); + } + else + ImPlot3D::PlotSurface("Wave Surface", xs, ys, zs, N, N, 0.0, 0.0, flags); // End the plot ImPlot3D::PopStyleVar(); diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 75930b5..84c3b15 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -441,6 +441,78 @@ template struct RendererMarkersLine : RendererBase { mutable ImVec2 UV1; }; +template struct RendererMarkersFillC : RendererBase { + RendererMarkersFillC(const _Getter& getter, const ImVec2* marker, int count, float size) + : RendererBase(getter.Count, (count - 2) * 3, count), Getter(getter), Marker(marker), Count(count), Size(size) { + } + + 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 = Getter(prim).c; + 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; + mutable ImVec2 UV; +}; + +template struct RendererMarkersLineC : RendererBase { + RendererMarkersLineC(const _Getter& getter, const ImVec2* marker, int count, float size, float weight) + : RendererBase(getter.Count, count / 2 * 6, count / 2 * 4), Getter(getter), Marker(marker), Count(count), + HalfWeight(ImMax(1.0f, weight) * 0.5f), Size(size) { + } + + 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, Getter(prim).c, UV0, UV1, GetPointDepth(p_plot)); + } + return true; + } + + const _Getter& Getter; + const ImVec2* Marker; + const int Count; + mutable float HalfWeight; + const float Size; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + template 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) { @@ -479,6 +551,43 @@ template struct RendererLineStrip : RendererBase { mutable ImVec2 UV1; }; +template struct RendererLineStripC : RendererBase { + RendererLineStripC(const _Getter& getter, float weight) + : RendererBase(getter.Count - 1, 6, 4), Getter(getter), 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, Getter(prim).c, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5)); + } + + // Update for next segment + P1_plot = P2_plot; + + return visible; + } + + const _Getter& Getter; + mutable float HalfWeight; + mutable ImPlot3DPoint P1_plot; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + template 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) { @@ -562,6 +671,44 @@ template struct RendererLineSegments : RendererBase { mutable ImVec2 UV1; }; +template struct RendererLineSegmentsC : RendererBase { + RendererLineSegmentsC(const _Getter& getter, float weight) + : RendererBase(getter.Count / 2, 6, 4), Getter(getter), 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, Getter(prim).c, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5)); + } + return visible; + } + + return false; + } + + const _Getter& Getter; + mutable float HalfWeight; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + template struct RendererTriangleFill : RendererBase { RendererTriangleFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 3, 3, 3), Getter(getter), Col(col) {} @@ -891,6 +1038,87 @@ template struct RendererSurfaceFill : RendererBase { const double ScaleMax; }; +template struct RendererSurfaceFillC : RendererBase { + RendererSurfaceFillC(const _Getter& getter, int x_count, int y_count) + : RendererBase((x_count - 1)* (y_count - 1), 6, 4), Getter(getter), XCount(x_count), YCount(y_count) { + } + + 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 { + 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; + + // 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 = p_plot[0].c; + + 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 = p_plot[1].c; + + 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 = p_plot[2].c; + + 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 = p_plot[3].c; + + 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.0); + draw_list_3d._ZWritePtr[1] = GetPointDepth((p_plot[0] + p_plot[2] + p_plot[3]) / 3.0); + draw_list_3d._ZWritePtr += 2; + + // Update vertex count + draw_list_3d._VtxCurrentIdx += 4; + + return true; + } + + const _Getter& Getter; + mutable ImVec2 UV; + const int XCount; + const int YCount; +}; + //----------------------------------------------------------------------------- // [SECTION] Indexers //----------------------------------------------------------------------------- @@ -916,6 +1144,16 @@ template struct IndexerIdx { int Stride; }; +struct IndexerIdxC { + IndexerIdxC(const ImU32* data, int count, int offset = 0, int stride = sizeof(ImU32)) + : Data(data), Count(count), Offset(count ? ImPosMod(offset, count) : 0), Stride(stride) {} + template IMPLOT3D_INLINE ImU32 operator()(I idx) const { return IndexData(Data, idx, Count, Offset, Stride); } + const ImU32* Data; + int Count; + int Offset; + int Stride; +}; + //----------------------------------------------------------------------------- // [SECTION] Getters //----------------------------------------------------------------------------- @@ -929,6 +1167,16 @@ template struct Get const int Count; }; +template struct GetterXYZC { + GetterXYZC(_IndexerX x, _IndexerY y, _IndexerZ z, _IndexerC c, int count) : IndexerX(x), IndexerY(y), IndexerZ(z), IndexerC(c), Count(count) {} + template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { return ImPlot3DPoint(IndexerX(idx), IndexerY(idx), IndexerZ(idx), IndexerC(idx)); } + const _IndexerX IndexerX; + const _IndexerY IndexerY; + const _IndexerZ IndexerZ; + const _IndexerC IndexerC; + const int Count; +}; + template struct GetterLoop { GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) {} template IMPLOT3D_INLINE ImPlot3DPoint operator()(I idx) const { @@ -1149,6 +1397,35 @@ template void RenderMarkers(const _Getter& getter, ImPlot3DMa } } +template void RenderMarkersC(const _Getter& getter, ImPlot3DMarker marker, float size, bool rend_fill, + bool rend_line, float weight) { + if (rend_fill) { + switch (marker) { + case ImPlot3DMarker_Circle: RenderPrimitives(getter, MARKER_FILL_CIRCLE, 10, size); break; + case ImPlot3DMarker_Square: RenderPrimitives(getter, MARKER_FILL_SQUARE, 4, size); break; + case ImPlot3DMarker_Diamond: RenderPrimitives(getter, MARKER_FILL_DIAMOND, 4, size); break; + case ImPlot3DMarker_Up: RenderPrimitives(getter, MARKER_FILL_UP, 3, size); break; + case ImPlot3DMarker_Down: RenderPrimitives(getter, MARKER_FILL_DOWN, 3, size); break; + case ImPlot3DMarker_Left: RenderPrimitives(getter, MARKER_FILL_LEFT, 3, size); break; + case ImPlot3DMarker_Right: RenderPrimitives(getter, MARKER_FILL_RIGHT, 3, size); break; + } + } + if (rend_line) { + switch (marker) { + case ImPlot3DMarker_Circle: RenderPrimitives(getter, MARKER_LINE_CIRCLE, 20, size, weight); break; + case ImPlot3DMarker_Square: RenderPrimitives(getter, MARKER_LINE_SQUARE, 8, size, weight); break; + case ImPlot3DMarker_Diamond: RenderPrimitives(getter, MARKER_LINE_DIAMOND, 8, size, weight); break; + case ImPlot3DMarker_Up: RenderPrimitives(getter, MARKER_LINE_UP, 6, size, weight); break; + case ImPlot3DMarker_Down: RenderPrimitives(getter, MARKER_LINE_DOWN, 6, size, weight); break; + case ImPlot3DMarker_Left: RenderPrimitives(getter, MARKER_LINE_LEFT, 6, size, weight); break; + case ImPlot3DMarker_Right: RenderPrimitives(getter, MARKER_LINE_RIGHT, 6, size, weight); break; + case ImPlot3DMarker_Asterisk: RenderPrimitives(getter, MARKER_LINE_ASTERISK, 6, size, weight); break; + case ImPlot3DMarker_Plus: RenderPrimitives(getter, MARKER_LINE_PLUS, 4, size, weight); break; + case ImPlot3DMarker_Cross: RenderPrimitives(getter, MARKER_LINE_CROSS, 4, size, weight); break; + } + } +} + //----------------------------------------------------------------------------- // [SECTION] PlotScatter //----------------------------------------------------------------------------- @@ -1160,7 +1437,10 @@ template void PlotScatterEx(const char* label_id, const Getter 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, marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight); + if(ImHasFlag(flags, ImPlot3DScatterFlags_PerPointCustomColor)) + RenderMarkersC(getter, marker, n.MarkerSize, n.RenderMarkerFill, n.RenderMarkerLine, n.MarkerWeight); + else + RenderMarkers(getter, marker, n.MarkerSize, n.RenderMarkerFill, col_fill, n.RenderMarkerLine, col_line, n.MarkerWeight); EndItem(); } } @@ -1174,9 +1454,18 @@ void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, in return PlotScatterEx(label_id, getter, flags); } +template +void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int count, ImPlot3DScatterFlags flags, int offset, int stride) { + if (count < 1) + return; + GetterXYZC, IndexerIdx, IndexerIdx, IndexerIdxC> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), + IndexerIdx(zs, count, offset, stride), IndexerIdxC(cs, count, offset, stride), count); + return PlotScatterEx(label_id, getter, flags); +} + #define INSTANTIATE_MACRO(T) \ - template IMPLOT3D_API void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, \ - int offset, int stride); + template IMPLOT3D_API void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DScatterFlags flags, int offset, int stride); \ + template IMPLOT3D_API void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int count, ImPlot3DScatterFlags flags, int offset, int stride); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO @@ -1190,7 +1479,10 @@ template void PlotLineEx(const char* label_id, const _Getter& if (getter.Count >= 2 && n.RenderLine) { const ImU32 col_line = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Line]); if (ImHasFlag(flags, ImPlot3DLineFlags_Segments)) { - RenderPrimitives(getter, col_line, n.LineWeight); + if (ImHasFlag(flags, ImPlot3DLineFlags_PerPointCustomColor)) + RenderPrimitives(getter, n.LineWeight); + else + RenderPrimitives(getter, col_line, n.LineWeight); } else if (ImHasFlag(flags, ImPlot3DLineFlags_Loop)) { if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN)) RenderPrimitives(GetterLoop<_Getter>(getter), col_line, n.LineWeight); @@ -1199,16 +1491,24 @@ template void PlotLineEx(const char* label_id, const _Getter& } else { if (ImHasFlag(flags, ImPlot3DLineFlags_SkipNaN)) RenderPrimitives(getter, col_line, n.LineWeight); - else - RenderPrimitives(getter, col_line, n.LineWeight); + else { + if (ImHasFlag(flags, ImPlot3DLineFlags_PerPointCustomColor)) + RenderPrimitives(getter, n.LineWeight); + else + RenderPrimitives(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); + if (ImHasFlag(flags, ImPlot3DLineFlags_PerPointCustomColor)) + RenderMarkersC<_Getter>(getter, n.Marker, n.MarkerSize, n.RenderMarkerFill, n.RenderMarkerLine, n.MarkerWeight); + else { + 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(); } @@ -1222,9 +1522,17 @@ IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const return PlotLineEx(label_id, getter, flags); } -#define INSTANTIATE_MACRO(T) \ - template IMPLOT3D_API void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, \ - int offset, int stride); +IMPLOT3D_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int count, ImPlot3DLineFlags flags, int offset, int stride) { + if (count < 2) + return; + GetterXYZC, IndexerIdx, IndexerIdx, IndexerIdxC> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), + IndexerIdx(zs, count, offset, stride), IndexerIdxC(cs, count, offset, stride), count); + return PlotLineEx(label_id, getter, flags); +} + +#define INSTANTIATE_MACRO(T) \ + template IMPLOT3D_API void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DLineFlags flags, int offset, int stride); \ + template IMPLOT3D_API void PlotLine(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int count, ImPlot3DLineFlags flags, int offset, int stride); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO @@ -1330,8 +1638,12 @@ template void PlotSurfaceEx(const char* label_id, const _Gett // Render fill if (getter.Count >= 4 && n.RenderFill && !ImHasFlag(flags, ImPlot3DSurfaceFlags_NoFill)) { - const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]); - RenderPrimitives(getter, x_count, y_count, col_fill, scale_min, scale_max); + if (ImHasFlag(flags, ImPlot3DSurfaceFlags_PerPointCustomColor)) + RenderPrimitives(getter, x_count, y_count); + else { + const ImU32 col_fill = ImGui::GetColorU32(n.Colors[ImPlot3DCol_Fill]); + RenderPrimitives(getter, x_count, y_count, col_fill, scale_min, scale_max); + } } // Render lines @@ -1361,9 +1673,19 @@ IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, co return PlotSurfaceEx(label_id, getter, x_count, y_count, scale_min, scale_max, flags); } +IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int x_count, int y_count, + double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride) { + int count = x_count * y_count; + if (count < 4) + return; + GetterXYZC, IndexerIdx, IndexerIdx, IndexerIdxC> getter(IndexerIdx(xs, count, offset, stride), IndexerIdx(ys, count, offset, stride), + IndexerIdx(zs, count, offset, stride), IndexerIdxC(cs, count, offset, stride), count); + return PlotSurfaceEx(label_id, getter, x_count, y_count, scale_min, scale_max, flags); +} + #define INSTANTIATE_MACRO(T) \ - template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, \ - double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); + template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); \ + template IMPLOT3D_API void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, const ImU32* cs, int x_count, int y_count, double scale_min, double scale_max, ImPlot3DSurfaceFlags flags, int offset, int stride); CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO