From f2480d8253a6d2b6f19a0ea88ee216a173b8323d Mon Sep 17 00:00:00 2001 From: Raphael Nogueira Rezende Laroca Pinto <61098580+katyushapolye@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:48:17 -0300 Subject: [PATCH] feat: quiver (vector field) plot added. --- implot3d.h | 37 ++- implot3d_demo.cpp | 59 +++++ implot3d_internal.h | 1 + implot3d_items.cpp | 620 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 716 insertions(+), 1 deletion(-) diff --git a/implot3d.h b/implot3d.h index 19a9cad..e588760 100644 --- a/implot3d.h +++ b/implot3d.h @@ -58,6 +58,7 @@ struct ImPlot3DContext; struct ImPlot3DStyle; struct ImPlot3DPoint; +struct ImPlot3DQuiver; struct ImPlot3DRay; struct ImPlot3DPlane; struct ImPlot3DBox; @@ -79,6 +80,7 @@ typedef int ImPlot3DColormap; // -> ImPlot3DColormap_ // Enum: Colormap typedef int ImPlot3DFlags; // -> ImPlot3DFlags_ // Flags: for BeginPlot() typedef int ImPlot3DItemFlags; // -> ImPlot3DItemFlags_ // Flags: Item flags typedef int ImPlot3DScatterFlags; // -> ImPlot3DScatterFlags_ // Flags: Scatter plot flags +typedef int ImPlot3DQuiverFlags; // -> ImPlot3DQuiverFlags_ // Flags: Quiver plot flags typedef int ImPlot3DLineFlags; // -> ImPlot3DLineFlags_ // Flags: Line plot flags typedef int ImPlot3DTriangleFlags; // -> ImPlot3DTriangleFlags_ // Flags: Triangle plot flags typedef int ImPlot3DQuadFlags; // -> ImPlot3DQuadFlags_ // Flags: Quad plot flags @@ -194,6 +196,12 @@ enum ImPlot3DScatterFlags_ { ImPlot3DScatterFlags_NoLegend = ImPlot3DItemFlags_NoLegend, ImPlot3DScatterFlags_NoFit = ImPlot3DItemFlags_NoFit, }; +// Flags for PlotQuiver +enum ImPlot3DQuiverFlags_ { + ImPlot3DQuiverFlags_None = 0, // default + ImPlot3DQuiverFlags_Normalize = 1 << 10, // all arrows will be normalized to the same length + ImPlot3DQuiverFlags_Colored = 1 << 11 // arrow colors will be mapped to their magnitudes +}; // Flags for PlotLine enum ImPlot3DLineFlags_ { @@ -504,6 +512,10 @@ 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 standard 3D Vector Field arrow plot. It has a direction and magnitude. +IMPLOT3D_TMP void PlotQuiver(const char* label_id, const T* xs, const T* ys,const T* zs, const T* us, const T* vs,const T* ws, int count,const T scaleMin, const T scaleMax, ImPlot3DQuiverFlags 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)); @@ -618,7 +630,8 @@ IMPLOT3D_API void SetNextFillStyle(const ImVec4& col = IMPLOT3D_AUTO_COL, float // Set the marker style for the next item only IMPLOT3D_API void SetNextMarkerStyle(ImPlot3DMarker marker = IMPLOT3D_AUTO, float size = IMPLOT3D_AUTO, const ImVec4& fill = IMPLOT3D_AUTO_COL, float weight = IMPLOT3D_AUTO, const ImVec4& outline = IMPLOT3D_AUTO_COL); - +// Set the quiver style for the next item only +IMPLOT3D_API void SetNextQuiverStyle(float size, const ImVec4& col = IMPLOT3D_AUTO_COL); // Get color IMPLOT3D_API ImVec4 GetStyleColorVec4(ImPlot3DCol idx); IMPLOT3D_API ImU32 GetStyleColorU32(ImPlot3DCol idx); @@ -770,6 +783,28 @@ struct ImPlot3DPoint { #endif }; +//----------------------------------------------------------------------------- +// [SECTION] ImPlot3DQuiver - +//----------------------------------------------------------------------------- +struct ImPlot3DQuiver { + double x, y, z; + double u, v, w; + double mag2; + IMPLOT3D_API constexpr ImPlot3DQuiver(double _x, double _y, double _z, double _u, double _v, double _w) : x(_x), y(_y), z(_z), u(_u), v(_v), w(_w), mag2((_u*_u + _v*_v + _w*_w)) { } + IMPLOT3D_API constexpr ImPlot3DQuiver(double _x, double _y, double _z, double _u, double _v, double _w, double _mag2) : x(_x), y(_y), z(_z), u(_u), v(_v), w(_w), mag2(_mag2) { } + IMPLOT3D_API double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1 || idx == 2 || idx == 3 || idx == 4); return ((double*)(void*)(char*)this)[idx]; } + IMPLOT3D_API double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1 || idx == 2 || idx == 3 || idx == 4); return ((const double*)(const void*)(const char*)this)[idx]; } + + operator ImPlot3DPoint() const { //Conversion to point on the origin of the vector + return ImPlot3DPoint(x, y, z); + } +#ifdef IMPLOT3D_QUIVER_CLASS_EXTRA + IMPLOT3D_QUIVER_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math + // types and ImPlot3DQuiver +#endif +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] ImPlot3DRay //----------------------------------------------------------------------------- diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 611b70c..8db156f 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -152,6 +152,64 @@ void DemoScatterPlots() { } } +void DemoQuiverPlots(){ + static float xs[1000], ys[1000], zs[1000], us[1000], vs[1000], ws[1000]; + for (int i = 0; i < 10; ++i) { + for(int j = 0; j < 10; ++j){ + for(int k = 0; k < 10; ++k){ + int idx = i*100 + j*10 + k; + float x = ((float)i * 1.0f) - 5.0f; + float y = ((float)j * 1.0f) - 5.0f; + float z = ((float)k * 1.0f) - 5.0f; + xs[idx] = x; + ys[idx] = y; + zs[idx] = z; + + float r = sqrtf(x*x + y*y); + us[idx] = -y; + vs[idx] = x; + ws[idx] = r; + } + } + } + + + static float scale_min = 0.00f; + static float scale_max = 2.5f; + static float baseSize = 1.0f; + static ImPlot3DColormap map = ImPlot3DColormap_Viridis; + + + ImGui::Text("Vector Scale"); + ImGui::SetNextItemWidth(225); + ImGui::DragFloatRange2("Min / Max",&scale_min, &scale_max, 0.01f, -20, 20,nullptr,nullptr,ImGuiSliderFlags_AlwaysClamp); + if (scale_max <= scale_min + 0.01f) { + scale_max = scale_min + 0.01f; + } + + ImGui::SetNextItemWidth(225); + ImGui::DragFloat("Base Size",&baseSize,0.1f,0,100); + + + static ImPlot3DQuiverFlags qv_flags = ImPlot3DQuiverFlags_Colored; + ImGui::CheckboxFlags("ImPlot3DQuiverFlags_Normalize", (unsigned int*)&qv_flags, ImPlot3DQuiverFlags_Normalize); + ImGui::CheckboxFlags("ImPlot3DQuiverFlags_Colored", (unsigned int*)&qv_flags, ImPlot3DQuiverFlags_Colored); + + ImPlot3D::PushColormap(map); + if (ImPlot3D::BeginPlot("Quiver Plot", ImVec2(ImGui::GetTextLineHeight()*28, ImGui::GetTextLineHeight()*28))) { + ImPlot3D::SetupAxisTicks(ImAxis3D_X,-5.0, 5.0, 11); + ImPlot3D::SetupAxisTicks(ImAxis3D_Y,-5.0, 5.0, 11); + ImPlot3D::SetupAxisTicks(ImAxis3D_Z,-5.0, 5.0, 11); + ImPlot3D::SetNextQuiverStyle(baseSize,ImPlot3D::GetColormapColor(1)); + ImPlot3D::SetupAxes("x","y","z"); + ImPlot3D::PlotQuiver("Magnitude", xs, ys, zs, us, vs, ws, 1000, scale_min, scale_max, qv_flags); + ImPlot3D::EndPlot(); + } + ImGui::SameLine(); + ImPlot3D::PopColormap(); +} + + void DemoTrianglePlots() { // Pyramid coordinates // Apex @@ -1576,6 +1634,7 @@ void ShowAllDemos() { ImGui::SeparatorText("Plot Types"); DemoHeader("Line Plots", DemoLinePlots); DemoHeader("Scatter Plots", DemoScatterPlots); + DemoHeader("Quiver Plots", DemoQuiverPlots); DemoHeader("Triangle Plots", DemoTrianglePlots); DemoHeader("Quad Plots", DemoQuadPlots); DemoHeader("Surface Plots", DemoSurfacePlots); diff --git a/implot3d_internal.h b/implot3d_internal.h index 9d7dc49..76320ee 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -182,6 +182,7 @@ struct ImPlot3DNextItemData { ImPlot3DMarker Marker; float MarkerSize; float MarkerWeight; + float QuiverSize; float FillAlpha; bool RenderLine; bool RenderFill; diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 75930b5..316ad4e 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -301,6 +301,12 @@ void SetNextMarkerStyle(ImPlot3DMarker marker, float size, const ImVec4& fill, f n.MarkerWeight = weight; } +void SetNextQuiverStyle(float size, const ImVec4& col) { + ImPlot3DContext& gp = *GImPlot3D; + gp.NextItemData.QuiverSize = size; + gp.NextItemData.Colors[ImPlot3DCol_Fill] = col; + +} //----------------------------------------------------------------------------- // [SECTION] Draw Utils //----------------------------------------------------------------------------- @@ -441,6 +447,524 @@ template struct RendererMarkersLine : RendererBase { mutable ImVec2 UV1; }; + + +// Static arrow mesh data, I'm not sure if this should be in the _meshes file... +#define VECTOR3D_VTX_COUNT 13 +#define VECTOR3D_IDX_COUNT 66 + +ImPlot3DPoint vector3d_vtx[VECTOR3D_VTX_COUNT] = { + {0.248488f, 0.037431f, -0.029385f}, // 0 + {-0.357405f, 0.037431f, -0.029385f}, // 1 + {0.248488f, -0.037431f, -0.029385f}, // 2 + {-0.357405f, -0.037431f, -0.029385f}, // 3 + {0.248488f, 0.037431f, 0.029385f}, // 4 + {-0.357405f, 0.037431f, 0.029385f}, // 5 + {0.248488f, -0.037431f, 0.029385f}, // 6 + {-0.357405f, -0.037431f, 0.029385f}, // 7 + {0.248488f, -0.074141f, -0.072994f}, // 8 + {0.248488f, -0.074141f, 0.072994f}, // 9 + {0.248488f, 0.074141f, -0.072994f}, // 10 + {0.248488f, 0.074141f, 0.072994f}, // 11 + {0.518142f, 0.000000f, -0.000000f}, // 12 +}; + +unsigned int vector3d_idx[VECTOR3D_IDX_COUNT] = { + 6, 8, 9, + 2, 7, 3, + 6, 5, 7, + 1, 7, 5, + 0, 3, 1, + 4, 1, 5, + 10, 11, 12, + 6, 11, 4, + 0, 8, 2, + 4, 10, 0, + 8, 10, 12, + 11, 9, 12, + 9, 8, 12, + 6, 2, 8, + 2, 6, 7, + 6, 4, 5, + 1, 3, 7, + 0, 2, 3, + 4, 0, 1, + 6, 9, 11, + 0, 10, 8, + 4, 11, 10, +}; + +template +struct RendererVector3DFillColorCodedNormalized : RendererBase { + + + RendererVector3DFillColorCodedNormalized(const _Getter& getter, float size, double scaleMin, double scaleMax) + : RendererBase(getter.Count, VECTOR3D_IDX_COUNT, VECTOR3D_VTX_COUNT), + Getter(getter), Size(size), ScaleMin(scaleMin), ScaleMax(scaleMax) {} + + 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 { + ImPlot3DQuiver quiver = Getter(prim); + ImPlot3DPoint p_base(quiver.x, quiver.y, quiver.z); + + if (!cull_box.Contains(p_base)) + return false; + + // Calculate magnitude from mag2 (squared magnitude) + double mag = sqrt(quiver.mag2); + + double scale_factor = Size; + + double dir_x = quiver.u; + double dir_y = quiver.v; + double dir_z = quiver.w; + + double t = ImClamp(ImRemap01(mag, ScaleMin, ScaleMax), 0.0, 1.0); + ImVec4 color = SampleColormap((float)t); + + // I want to void dealing with rotation matrices or quaternions, so i will just use + // basic geometry to compute the rotated vertices, its basically 2D rotations applied twice + // we compute first the rotation about the z axis, then the rotation about the y axis + // and such, we have thetaZ and thetaY + // Also note that, the tip of the arrow must be pointing to +X + float len = ImSqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z); + + float nx = dir_x / len; + float ny = dir_y / len; + float nz = dir_z / len; + + float thetaZ = ImAtan2((float)ny, (float)nx); + float cosZ = ImCos(thetaZ); + float sinZ = ImSin(thetaZ); + + // Now, this is the projection of the direction vector onto the YZ plane + float xy_len = ImSqrt(nx * nx + ny * ny); + float thetaY = ImAtan2(nz, xy_len); // We dont negate it because the implot coordinate system puts +Z upwards + float cosY = ImCos(thetaY); + float sinY = ImSin(thetaY); + + + + for (int i = 0; i < VECTOR3D_VTX_COUNT; i++) { + ImPlot3DPoint vtx = vector3d_vtx[i]; + + + ImPlot3DPoint p_world; + + // This is just Rz * Ry * vtx + float rotatedX = vtx.x * cosZ - vtx.y * sinZ; + float rotatedY = vtx.x * sinZ + vtx.y * cosZ; + float rotatedZ = vtx.z; + float finalX = rotatedX * cosY - rotatedZ * sinY; + float finalY = rotatedY; + float finalZ = rotatedX * sinY + rotatedZ * cosY; + + p_world.x = p_base.x + finalX * scale_factor ; + p_world.y = p_base.y + finalY * scale_factor ; + p_world.z = p_base.z + finalZ * scale_factor ; + + + ImVec2 p_screen = PlotToPixels(p_world); + + draw_list_3d._VtxWritePtr[0].pos.x = p_screen.x; + draw_list_3d._VtxWritePtr[0].pos.y = p_screen.y; + draw_list_3d._VtxWritePtr[0].uv = UV; + draw_list_3d._VtxWritePtr[0].col = ImColor(color); // Red color for now + draw_list_3d._VtxWritePtr++; + } + + + for (int i = 0; i < VECTOR3D_IDX_COUNT / 3; i++) { + int idx0 = vector3d_idx[i * 3]; + int idx1 = vector3d_idx[i * 3 + 1]; + int idx2 = vector3d_idx[i * 3 + 2]; + + draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx0); + draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx1); + draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx2); + draw_list_3d._IdxWritePtr += 3; + + // Calculate depth for this triangle using the centroid in world space + ImPlot3DPoint v0 = vector3d_vtx[idx0]; + ImPlot3DPoint v1 = vector3d_vtx[idx1]; + ImPlot3DPoint v2 = vector3d_vtx[idx2]; + + ImPlot3DPoint centroid; + centroid.x = p_base.x + (v0.x + v1.x + v2.x) * scale_factor / 3.0; + centroid.y = p_base.y + (v0.y + v1.y + v2.y) * scale_factor / 3.0; + centroid.z = p_base.z + (v0.z + v1.z + v2.z) * scale_factor / 3.0; + + draw_list_3d._ZWritePtr[0] = GetPointDepth(centroid); + draw_list_3d._ZWritePtr++; + } + + // Update vertex count + draw_list_3d._VtxCurrentIdx += (ImDrawIdx)VECTOR3D_VTX_COUNT; + return true; + } + + const _Getter& Getter; + const float Size; + const double ScaleMin, ScaleMax; + mutable ImVec2 UV; +}; + +template +struct RendererVector3DFillColorCodedScaled : RendererBase { + + + RendererVector3DFillColorCodedScaled(const _Getter& getter, float size, double scaleMin, double scaleMax) + : RendererBase(getter.Count, VECTOR3D_IDX_COUNT, VECTOR3D_VTX_COUNT), + Getter(getter), Size(size), ScaleMin(scaleMin), ScaleMax(scaleMax) {} + + 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 { + ImPlot3DQuiver quiver = Getter(prim); + ImPlot3DPoint p_base(quiver.x, quiver.y, quiver.z); + + if (!cull_box.Contains(p_base)) + return false; + + // Calculate magnitude from mag2 (squared magnitude) + double mag = sqrt(quiver.mag2); + + + + double dir_x = quiver.u; + double dir_y = quiver.v; + double dir_z = quiver.w; + + double t = ImClamp(ImRemap01(mag, ScaleMin, ScaleMax), 0.0, 1.0); + ImVec4 color = SampleColormap((float)t); + double scale_factor = Size * t; + + // I want to void dealing with rotation matrices or quaternions, so i will just use + // basic geometry to compute the rotated vertices, its basically 2D rotations applied twice + // we compute first the rotation about the z axis, then the rotation about the y axis + // and such, we have thetaZ and thetaY + // Also note that, the tip of the arrow must be pointing to +X + float len = ImSqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z); + + float nx = dir_x / len; + float ny = dir_y / len; + float nz = dir_z / len; + + float thetaZ = ImAtan2((float)ny, (float)nx); + float cosZ = ImCos(thetaZ); + float sinZ = ImSin(thetaZ); + + // Now, this is the projection of the direction vector onto the YZ plane + float xy_len = ImSqrt(nx * nx + ny * ny); + float thetaY = ImAtan2(nz, xy_len); // We don't negate it because the implot coordinate system inverts the Z axis + float cosY = ImCos(thetaY); + float sinY = ImSin(thetaY); + + + + for (int i = 0; i < VECTOR3D_VTX_COUNT; i++) { + ImPlot3DPoint vtx = vector3d_vtx[i]; + + + ImPlot3DPoint p_world; + + // This is just Rz * Ry * vtx + float rotatedX = vtx.x * cosZ - vtx.y * sinZ; + float rotatedY = vtx.x * sinZ + vtx.y * cosZ; + float rotatedZ = vtx.z; + float finalX = rotatedX * cosY - rotatedZ * sinY; + float finalY = rotatedY; + float finalZ = rotatedX * sinY + rotatedZ * cosY; + + p_world.x = p_base.x + finalX * scale_factor; + p_world.y = p_base.y + finalY * scale_factor; + p_world.z = p_base.z + finalZ * scale_factor; + + + ImVec2 p_screen = PlotToPixels(p_world); + + draw_list_3d._VtxWritePtr[0].pos.x = p_screen.x; + draw_list_3d._VtxWritePtr[0].pos.y = p_screen.y; + draw_list_3d._VtxWritePtr[0].uv = UV; + draw_list_3d._VtxWritePtr[0].col = ImColor(color); + draw_list_3d._VtxWritePtr++; + } + + + for (int i = 0; i < VECTOR3D_IDX_COUNT / 3; i++) { + int idx0 = vector3d_idx[i * 3]; + int idx1 = vector3d_idx[i * 3 + 1]; + int idx2 = vector3d_idx[i * 3 + 2]; + + draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx0); + draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx1); + draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx2); + draw_list_3d._IdxWritePtr += 3; + + // Calculate depth for this triangle using the centroid in world space + ImPlot3DPoint v0 = vector3d_vtx[idx0]; + ImPlot3DPoint v1 = vector3d_vtx[idx1]; + ImPlot3DPoint v2 = vector3d_vtx[idx2]; + + ImPlot3DPoint centroid; + centroid.x = p_base.x + (v0.x + v1.x + v2.x) * scale_factor / 3.0; + centroid.y = p_base.y + (v0.y + v1.y + v2.y) * scale_factor / 3.0; + centroid.z = p_base.z + (v0.z + v1.z + v2.z) * scale_factor / 3.0; + + draw_list_3d._ZWritePtr[0] = GetPointDepth(centroid); + draw_list_3d._ZWritePtr++; + } + + // Update vertex count + draw_list_3d._VtxCurrentIdx += (ImDrawIdx)VECTOR3D_VTX_COUNT; + return true; + } + + const _Getter& Getter; + const float Size; + const double ScaleMin, ScaleMax; + mutable ImVec2 UV; +}; + +template +struct RendererVector3DFillNormalized : RendererBase { + + + RendererVector3DFillNormalized(const _Getter& getter, float size,ImU32 col) + : RendererBase(getter.Count, VECTOR3D_IDX_COUNT, VECTOR3D_VTX_COUNT), + Getter(getter), 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 { + ImPlot3DQuiver quiver = Getter(prim); + ImPlot3DPoint p_base(quiver.x, quiver.y, quiver.z); + + if (!cull_box.Contains(p_base)) + return false; + + + double scale_factor = Size; + + double dir_x = quiver.u; + double dir_y = quiver.v; + double dir_z = quiver.w; + + + // I want to void dealing with rotation matrices or quaternions, so i will just use + // basic geometry to compute the rotated vertices, its basically 2D rotations applied twice + // we compute first the rotation about the z axis, then the rotation about the y axis + // and such, we have thetaZ and thetaY + // Also note that, the tip of the arrow must be pointing to +X + float len = ImSqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z); + + float nx = dir_x / len; + float ny = dir_y / len; + float nz = dir_z / len; + + float thetaZ = ImAtan2((float)ny, (float)nx); + float cosZ = ImCos(thetaZ); + float sinZ = ImSin(thetaZ); + + // Now, this is the projection of the direction vector onto the YZ plane + float xy_len = ImSqrt(nx * nx + ny * ny); + float thetaY = ImAtan2(nz, xy_len); // We dont negate it because the implot coordinate system puts +Z upwards + float cosY = ImCos(thetaY); + float sinY = ImSin(thetaY); + + + + for (int i = 0; i < VECTOR3D_VTX_COUNT; i++) { + ImPlot3DPoint vtx = vector3d_vtx[i]; + + + ImPlot3DPoint p_world; + + // This is just Rz * Ry * vtx + float rotatedX = vtx.x * cosZ - vtx.y * sinZ; + float rotatedY = vtx.x * sinZ + vtx.y * cosZ; + float rotatedZ = vtx.z; + float finalX = rotatedX * cosY - rotatedZ * sinY; + float finalY = rotatedY; + float finalZ = rotatedX * sinY + rotatedZ * cosY; + + p_world.x = p_base.x + finalX * scale_factor ; + p_world.y = p_base.y + finalY * scale_factor ; + p_world.z = p_base.z + finalZ * scale_factor ; + + + ImVec2 p_screen = PlotToPixels(p_world); + + draw_list_3d._VtxWritePtr[0].pos.x = p_screen.x; + draw_list_3d._VtxWritePtr[0].pos.y = p_screen.y; + draw_list_3d._VtxWritePtr[0].uv = UV; + draw_list_3d._VtxWritePtr[0].col = Col; + draw_list_3d._VtxWritePtr++; + } + + + for (int i = 0; i < VECTOR3D_IDX_COUNT / 3; i++) { + int idx0 = vector3d_idx[i * 3]; + int idx1 = vector3d_idx[i * 3 + 1]; + int idx2 = vector3d_idx[i * 3 + 2]; + + draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx0); + draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx1); + draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx2); + draw_list_3d._IdxWritePtr += 3; + + // Calculate depth for this triangle using the centroid in world space + ImPlot3DPoint v0 = vector3d_vtx[idx0]; + ImPlot3DPoint v1 = vector3d_vtx[idx1]; + ImPlot3DPoint v2 = vector3d_vtx[idx2]; + + ImPlot3DPoint centroid; + centroid.x = p_base.x + (v0.x + v1.x + v2.x) * scale_factor / 3.0; + centroid.y = p_base.y + (v0.y + v1.y + v2.y) * scale_factor / 3.0; + centroid.z = p_base.z + (v0.z + v1.z + v2.z) * scale_factor / 3.0; + + draw_list_3d._ZWritePtr[0] = GetPointDepth(centroid); + draw_list_3d._ZWritePtr++; + } + + // Update vertex count + draw_list_3d._VtxCurrentIdx += (ImDrawIdx)VECTOR3D_VTX_COUNT; + return true; + } + + const _Getter& Getter; + const float Size; + const ImU32 Col; + + mutable ImVec2 UV; +}; + +template +struct RendererVector3DFill : RendererBase { + + + RendererVector3DFill(const _Getter& getter, float size, ImU32 col,double scaleMin, double scaleMax) + : RendererBase(getter.Count, VECTOR3D_IDX_COUNT, VECTOR3D_VTX_COUNT), + Getter(getter), Size(size), Col(col), ScaleMin(scaleMin), ScaleMax(scaleMax) {} + + 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 { + ImPlot3DQuiver quiver = Getter(prim); + ImPlot3DPoint p_base(quiver.x, quiver.y, quiver.z); + + if (!cull_box.Contains(p_base)) + return false; + + // Calculate magnitude from mag2 (squared magnitude) + double mag = sqrt(quiver.mag2); + + double t = ImClamp(ImRemap01(mag, ScaleMin, ScaleMax), 0.0, 1.0); + + double dir_x = quiver.u; + double dir_y = quiver.v; + double dir_z = quiver.w; + + + double scale_factor = Size * t; + + // I want to void dealing with rotation matrices or quaternions, so i will just use + // basic geometry to compute the rotated vertices, its basically 2D rotations applied twice + // we compute first the rotation about the z axis, then the rotation about the y axis + // and such, we have thetaZ and thetaY + // Also note that, the tip of the arrow must be pointing to +X + float len = ImSqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z); + + float nx = dir_x / len; + float ny = dir_y / len; + float nz = dir_z / len; + + float thetaZ = ImAtan2((float)ny, (float)nx); + float cosZ = ImCos(thetaZ); + float sinZ = ImSin(thetaZ); + + // Now, this is the projection of the direction vector onto the YZ plane + float xy_len = ImSqrt(nx * nx + ny * ny); + float thetaY = ImAtan2(nz, xy_len); // We don't negate it because the implot coordinate system inverts the Z axis + float cosY = ImCos(thetaY); + float sinY = ImSin(thetaY); + + + + for (int i = 0; i < VECTOR3D_VTX_COUNT; i++) { + ImPlot3DPoint vtx = vector3d_vtx[i]; + + + ImPlot3DPoint p_world; + + //This is just Rz * Ry * vtx + float rotatedX = vtx.x * cosZ - vtx.y * sinZ; + float rotatedY = vtx.x * sinZ + vtx.y * cosZ; + float rotatedZ = vtx.z; + float finalX = rotatedX * cosY - rotatedZ * sinY; + float finalY = rotatedY; + float finalZ = rotatedX * sinY + rotatedZ * cosY; + + p_world.x = p_base.x + finalX * scale_factor; + p_world.y = p_base.y + finalY * scale_factor; + p_world.z = p_base.z + finalZ * scale_factor; + + + ImVec2 p_screen = PlotToPixels(p_world); + + draw_list_3d._VtxWritePtr[0].pos.x = p_screen.x; + draw_list_3d._VtxWritePtr[0].pos.y = p_screen.y; + draw_list_3d._VtxWritePtr[0].uv = UV; + draw_list_3d._VtxWritePtr[0].col = Col; + draw_list_3d._VtxWritePtr++; + } + + + for (int i = 0; i < VECTOR3D_IDX_COUNT / 3; i++) { + int idx0 = vector3d_idx[i * 3]; + int idx1 = vector3d_idx[i * 3 + 1]; + int idx2 = vector3d_idx[i * 3 + 2]; + + draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx0); + draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx1); + draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + idx2); + draw_list_3d._IdxWritePtr += 3; + + // Calculate depth for this triangle using the centroid in world space + ImPlot3DPoint v0 = vector3d_vtx[idx0]; + ImPlot3DPoint v1 = vector3d_vtx[idx1]; + ImPlot3DPoint v2 = vector3d_vtx[idx2]; + + ImPlot3DPoint centroid; + centroid.x = p_base.x + (v0.x + v1.x + v2.x) * scale_factor / 3.0; + centroid.y = p_base.y + (v0.y + v1.y + v2.y) * scale_factor / 3.0; + centroid.z = p_base.z + (v0.z + v1.z + v2.z) * scale_factor / 3.0; + + draw_list_3d._ZWritePtr[0] = GetPointDepth(centroid); + draw_list_3d._ZWritePtr++; + } + + // Update vertex count + draw_list_3d._VtxCurrentIdx += (ImDrawIdx)VECTOR3D_VTX_COUNT; + return true; + } + + const _Getter& Getter; + const float Size; + const ImU32 Col; + const double ScaleMin, ScaleMax; + mutable ImVec2 UV; +}; + 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) { @@ -929,6 +1453,28 @@ template struct Get const int Count; }; +template +struct GetterXYZUVW { + GetterXYZUVW(_IndexerX x, _IndexerY y, _IndexerZ z, _IndexerU u, _IndexerV v, _IndexerW w, int count) + : IndxerX(x), IndxerY(y), IndxerZ(z), IndxerU(u), IndxerV(v), IndxerW(w), Count(count) { } + + template IMPLOT3D_INLINE ImPlot3DQuiver operator()(I idx) const { + double u_val = IndxerU(idx); + double v_val = IndxerV(idx); + double w_val = IndxerW(idx); + double mag = sqrt(u_val*u_val + v_val*v_val + w_val*w_val); + return ImPlot3DQuiver(IndxerX(idx), IndxerY(idx), IndxerZ(idx), u_val, v_val, w_val, mag); + } + + const _IndexerX IndxerX; + const _IndexerY IndxerY; + const _IndexerZ IndxerZ; + const _IndexerU IndxerU; + const _IndexerV IndxerV; + const _IndexerW IndxerW; + 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 +1695,40 @@ template void RenderMarkers(const _Getter& getter, ImPlot3DMa } } + + +//----------------------------------------------------------------------------- +// [SECTION] Vectors +//----------------------------------------------------------------------------- + +template +void RenderVectors(const _Getter& getter, float size, double scaleMin, double scaleMax, bool color_coded, bool normalized, ImU32 color = 0) { + + // For efficiency reasons, we have 4 different renderers depending on whether the vectors are color-coded and/or normalized + // This avoids checking the flags for each vector during rendering + if (color_coded && normalized) { + RenderPrimitives(getter, size, scaleMin, scaleMax); + + + } + else if (color_coded && !normalized) { + RenderPrimitives(getter, size, scaleMin, scaleMax); + + } + else if (!color_coded && normalized) { + RenderPrimitives(getter, size, color); + + } + else { + + RenderPrimitives(getter, size, color,scaleMin, scaleMax); + + + } + +} + + //----------------------------------------------------------------------------- // [SECTION] PlotScatter //----------------------------------------------------------------------------- @@ -1180,6 +1760,46 @@ void PlotScatter(const char* label_id, const T* xs, const T* ys, const T* zs, in CALL_INSTANTIATE_FOR_NUMERIC_TYPES() #undef INSTANTIATE_MACRO + +//----------------------------------------------------------------------------- +// [SECTION] PlotQuiver (Vector Field) +//----------------------------------------------------------------------------- +template +void PlotQuiverEx(const char* label_id, const Getter& getter, const double scaleMin, const double scaleMax, ImPlot3DQuiverFlags flags){ + if (BeginItemEx(label_id, getter, flags,ImPlot3DCol_Fill)) { + if (getter.Count <= 0) { + EndItem(); + return; + } + const ImPlot3DNextItemData& s = GetItemData(); + const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlot3DCol_Fill]); + + + RenderVectors(getter, s.QuiverSize, scaleMin, scaleMax, ImHasFlag(flags, ImPlot3DQuiverFlags_Colored), ImHasFlag(flags, ImPlot3DQuiverFlags_Normalize), col_fill); + + EndItem(); + } + +} + +template +void PlotQuiver(const char* label_id, const T* xs, const T* ys, const T* zs, const T* us, const T* vs, const T* ws, int count, const T scaleMin, const T scaleMax, ImPlot3DQuiverFlags flags, int offset, int stride) { + GetterXYZUVW,IndexerIdx,IndexerIdx,IndexerIdx,IndexerIdx,IndexerIdx> getter( + IndexerIdx(xs,count,offset,stride), + IndexerIdx(ys,count,offset,stride), + IndexerIdx(zs,count,offset,stride), + IndexerIdx(us,count,offset,stride), + IndexerIdx(vs,count,offset,stride), + IndexerIdx(ws,count,offset,stride), + count); + return PlotQuiverEx(label_id, getter, scaleMin, scaleMax, flags); +} + +#define INSTANTIATE_MACRO(T) \ + template IMPLOT3D_API void PlotQuiver(const char* label_id, const T* xs, const T* ys, const T* zs, const T* us, const T* vs, const T* ws, int count, const T scaleMin, const T scaleMax, ImPlot3DQuiverFlags flags, int offset, int stride); +CALL_INSTANTIATE_FOR_NUMERIC_TYPES() +#undef INSTANTIATE_MACRO + //----------------------------------------------------------------------------- // [SECTION] PlotLine //-----------------------------------------------------------------------------