1
0
mirror of synced 2025-01-11 13:52:15 +01:00

3014 lines
116 KiB
C++
Raw Normal View History

2024-12-17 23:51:45 +01:00
//--------------------------------------------------
// ImPlot3D v0.1
// implot3d.cpp
// Date: 2024-11-16
// Author: Breno Cunha Queiroz (brenocq.com)
//
// Acknowledgments:
// ImPlot3D is heavily inspired by ImPlot
// (https://github.com/epezent/implot) by Evan Pezent,
// and follows a similar code style and structure to
// maintain consistency with ImPlot's API.
//--------------------------------------------------
// Table of Contents:
// [SECTION] Includes
// [SECTION] Macros
// [SECTION] Context
// [SECTION] Text Utils
// [SECTION] Legend Utils
// [SECTION] Mouse Position Utils
// [SECTION] Plot Box Utils
// [SECTION] Formatter
// [SECTION] Locator
// [SECTION] Context Menus
// [SECTION] Begin/End Plot
// [SECTION] Setup
// [SECTION] Plot Utils
// [SECTION] Setup Utils
// [SECTION] Miscellaneous
// [SECTION] Styles
// [SECTION] Colormaps
// [SECTION] Context Utils
// [SECTION] Style Utils
// [SECTION] ImPlot3DPoint
// [SECTION] ImPlot3DBox
// [SECTION] ImPlot3DRange
// [SECTION] ImPlot3DQuat
// [SECTION] ImDrawList3D
// [SECTION] ImPlot3DAxis
// [SECTION] ImPlot3DPlot
// [SECTION] ImPlot3DStyle
//-----------------------------------------------------------------------------
// [SECTION] Includes
//-----------------------------------------------------------------------------
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "implot3d.h"
#include "implot3d_internal.h"
#include <cmath>
2024-12-17 23:51:45 +01:00
#ifndef IMGUI_DISABLE
//-----------------------------------------------------------------------------
// [SECTION] Macros
//-----------------------------------------------------------------------------
#define IMPLOT3D_CHECK_CTX() IM_ASSERT_USER_ERROR(GImPlot3D != nullptr, "No current context. Did you call ImPlot3D::CreateContext() or ImPlot3D::SetCurrentContext()?")
#define IMPLOT3D_CHECK_PLOT() IM_ASSERT_USER_ERROR(GImPlot3D->CurrentPlot != nullptr, "No active plot. Did you call ImPlot3D::BeginPlot()?")
//-----------------------------------------------------------------------------
// [SECTION] Context
//-----------------------------------------------------------------------------
namespace ImPlot3D {
// Global ImPlot3D context
#ifndef GImPlot3D
ImPlot3DContext* GImPlot3D = nullptr;
#endif
static ImPlot3DQuat init_rotation = ImPlot3DQuat(-0.513269, -0.212596, -0.318184, 0.76819);
ImPlot3DContext* CreateContext() {
ImPlot3DContext* ctx = IM_NEW(ImPlot3DContext)();
if (GImPlot3D == nullptr)
SetCurrentContext(ctx);
InitializeContext(ctx);
return ctx;
}
void DestroyContext(ImPlot3DContext* ctx) {
if (ctx == nullptr)
ctx = GImPlot3D;
if (GImPlot3D == ctx)
SetCurrentContext(nullptr);
IM_DELETE(ctx);
}
ImPlot3DContext* GetCurrentContext() { return GImPlot3D; }
void SetCurrentContext(ImPlot3DContext* ctx) { GImPlot3D = ctx; }
//-----------------------------------------------------------------------------
// [SECTION] Text Utils
//-----------------------------------------------------------------------------
void AddTextRotated(ImDrawList* draw_list, ImVec2 pos, float angle, ImU32 col, const char* text_begin, const char* text_end) {
if (!text_end)
text_end = text_begin + strlen(text_begin);
ImGuiContext& g = *GImGui;
ImFont* font = g.Font;
// Align to be pixel perfect
pos.x = IM_FLOOR(pos.x);
pos.y = IM_FLOOR(pos.y);
const float scale = g.FontSize / font->FontSize;
// Measure the size of the text in unrotated coordinates
ImVec2 text_size = font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, text_begin, text_end, nullptr);
// Precompute sine and cosine of the angle (note: angle should be positive for rotation in ImGui)
float cos_a = cosf(-angle);
float sin_a = sinf(-angle);
const char* s = text_begin;
int chars_total = (int)(text_end - s);
int chars_rendered = 0;
const int vtx_count_max = chars_total * 4;
const int idx_count_max = chars_total * 6;
draw_list->PrimReserve(idx_count_max, vtx_count_max);
// Adjust pen position to center the text
ImVec2 pen = ImVec2(-text_size.x * 0.5f, -text_size.y * 0.5f);
while (s < text_end) {
unsigned int c = (unsigned int)*s;
if (c < 0x80) {
s += 1;
} else {
s += ImTextCharFromUtf8(&c, s, text_end);
if (c == 0) // Malformed UTF-8?
break;
}
const ImFontGlyph* glyph = font->FindGlyph((ImWchar)c);
if (glyph == nullptr) {
continue;
}
// Glyph dimensions and positions
ImVec2 glyph_offset = ImVec2(glyph->X0, glyph->Y0) * scale;
ImVec2 glyph_size = ImVec2(glyph->X1 - glyph->X0, glyph->Y1 - glyph->Y0) * scale;
// Corners of the glyph quad in unrotated space
ImVec2 corners[4];
corners[0] = pen + glyph_offset;
corners[1] = pen + glyph_offset + ImVec2(glyph_size.x, 0);
corners[2] = pen + glyph_offset + glyph_size;
corners[3] = pen + glyph_offset + ImVec2(0, glyph_size.y);
// Rotate and translate the corners
for (int i = 0; i < 4; i++) {
float x = corners[i].x;
float y = corners[i].y;
corners[i].x = x * cos_a - y * sin_a + pos.x;
corners[i].y = x * sin_a + y * cos_a + pos.y;
}
// Texture coordinates
ImVec2 uv0 = ImVec2(glyph->U0, glyph->V0);
ImVec2 uv1 = ImVec2(glyph->U1, glyph->V1);
// Render the glyph quad
draw_list->PrimQuadUV(corners[0], corners[1], corners[2], corners[3],
uv0, ImVec2(glyph->U1, glyph->V0),
uv1, ImVec2(glyph->U0, glyph->V1),
col);
// Advance the pen position
pen.x += glyph->AdvanceX * scale;
chars_rendered++;
}
// Return unused vertices
int chars_skipped = chars_total - chars_rendered;
draw_list->PrimUnreserve(chars_skipped * 6, chars_skipped * 4);
}
void AddTextCentered(ImDrawList* draw_list, ImVec2 top_center, ImU32 col, const char* text_begin) {
const char* text_end = ImGui::FindRenderedTextEnd(text_begin);
ImVec2 text_size = ImGui::CalcTextSize(text_begin, text_end, true);
draw_list->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y), col, text_begin, text_end);
}
//-----------------------------------------------------------------------------
// [SECTION] Legend Utils
//-----------------------------------------------------------------------------
ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlot3DLocation loc, const ImVec2& pad) {
ImVec2 pos;
// Legend x coordinate
if (ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_West) && !ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_East))
pos.x = outer_rect.Min.x + pad.x;
else if (!ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_West) && ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_East))
pos.x = outer_rect.Max.x - pad.x - inner_size.x;
else
pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f;
// Legend y coordinate
if (ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_North) && !ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_South))
pos.y = outer_rect.Min.y + pad.y;
else if (!ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_North) && ImPlot3D::ImHasFlag(loc, ImPlot3DLocation_South))
pos.y = outer_rect.Max.y - pad.y - inner_size.y;
else
pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f;
pos.x = IM_ROUND(pos.x);
pos.y = IM_ROUND(pos.y);
return pos;
}
ImVec2 CalcLegendSize(ImPlot3DItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) {
const int nItems = items.GetLegendCount();
const float txt_ht = ImGui::GetTextLineHeight();
const float icon_size = txt_ht;
// Get label max width
float max_label_width = 0;
float sum_label_width = 0;
for (int i = 0; i < nItems; i++) {
const char* label = items.GetLegendLabel(i);
const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
max_label_width = label_width > max_label_width ? label_width : max_label_width;
sum_label_width += label_width;
}
// Compute legend size
const ImVec2 legend_size = vertical ? ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) : ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht);
return legend_size;
}
void ShowLegendEntries(ImPlot3DItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& draw_list) {
const float txt_ht = ImGui::GetTextLineHeight();
const float icon_size = txt_ht;
const float icon_shrink = 2;
ImU32 col_txt = GetStyleColorU32(ImPlot3DCol_LegendText);
ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f);
float sum_label_width = 0;
const int num_items = items.GetLegendCount();
if (num_items == 0)
return;
ImPlot3DContext& gp = *GImPlot3D;
// Render legend items
for (int i = 0; i < num_items; i++) {
const int idx = i;
ImPlot3DItem* item = items.GetLegendItem(idx);
const char* label = items.GetLegendLabel(idx);
const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;
const ImVec2 top_left = vertical ? legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) : legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0);
sum_label_width += label_width;
ImRect icon_bb;
icon_bb.Min = top_left + ImVec2(icon_shrink, icon_shrink);
icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink);
ImRect label_bb;
label_bb.Min = top_left;
label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size);
ImU32 col_txt_hl;
ImU32 col_item = ImAlphaU32(item->Color, 1);
ImRect button_bb(icon_bb.Min, label_bb.Max);
ImGui::KeepAliveID(item->ID);
bool item_hov = false;
bool item_hld = false;
bool item_clk = ImPlot3D::ImHasFlag(items.Legend.Flags, ImPlot3DLegendFlags_NoButtons)
? false
: ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld);
if (item_clk)
item->Show = !item->Show;
const bool hovering = item_hov && !ImPlot3D::ImHasFlag(items.Legend.Flags, ImPlot3DLegendFlags_NoHighlightItem);
if (hovering) {
item->LegendHovered = true;
col_txt_hl = ImPlot3D::ImMixU32(col_txt, col_item, 64);
} else {
item->LegendHovered = false;
col_txt_hl = ImGui::GetColorU32(col_txt);
}
ImU32 col_icon;
if (item_hld)
col_icon = item->Show ? ImAlphaU32(col_item, 0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);
else if (item_hov)
col_icon = item->Show ? ImAlphaU32(col_item, 0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f);
else
col_icon = item->Show ? col_item : col_txt_dis;
draw_list.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon);
const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr);
if (label != text_display_end)
draw_list.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl : col_txt_dis, label, text_display_end);
}
}
void RenderLegend() {
ImPlot3DContext& gp = *GImPlot3D;
ImPlot3DPlot& plot = *gp.CurrentPlot;
if (ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend) || plot.Items.GetLegendCount() == 0)
return;
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImDrawList* draw_list = window->DrawList;
const ImGuiIO& IO = ImGui::GetIO();
ImPlot3DLegend& legend = plot.Items.Legend;
const bool legend_horz = ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal);
const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);
const ImVec2 legend_pos = GetLocationPos(plot.PlotRect,
legend_size,
legend.Location,
gp.Style.LegendPadding);
legend.Rect = ImRect(legend_pos, legend_pos + legend_size);
// Test hover
legend.Hovered = legend.Rect.Contains(IO.MousePos);
// Render background
ImU32 col_bg = GetStyleColorU32(ImPlot3DCol_LegendBg);
ImU32 col_bd = GetStyleColorU32(ImPlot3DCol_LegendBorder);
draw_list->AddRectFilled(legend.Rect.Min, legend.Rect.Max, col_bg);
draw_list->AddRect(legend.Rect.Min, legend.Rect.Max, col_bd);
// Render legends
ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, *draw_list);
}
//-----------------------------------------------------------------------------
// [SECTION] Mouse Position Utils
//-----------------------------------------------------------------------------
void RenderMousePos() {
ImPlot3DContext& gp = *GImPlot3D;
ImPlot3DPlot& plot = *gp.CurrentPlot;
if (ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoMouseText))
return;
ImVec2 mouse_pos = ImGui::GetMousePos();
ImPlot3DPoint mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_YZ, true);
if (mouse_plot_pos.IsNaN())
mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_XZ, true);
if (mouse_plot_pos.IsNaN())
mouse_plot_pos = PixelsToPlotPlane(mouse_pos, ImPlane3D_XY, true);
char buff[IMPLOT3D_LABEL_MAX_SIZE];
if (!mouse_plot_pos.IsNaN()) {
ImGuiTextBuffer builder;
builder.append("(");
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
if (i > 0)
builder.append(", ");
axis.Formatter(mouse_plot_pos[i], buff, IMPLOT3D_LABEL_MAX_SIZE, axis.FormatterData);
builder.append(buff);
}
builder.append(")");
const ImVec2 size = ImGui::CalcTextSize(builder.c_str());
// TODO custom location/padding
const ImVec2 pos = GetLocationPos(plot.PlotRect, size, ImPlot3DLocation_SouthEast, ImVec2(10, 10));
ImDrawList& draw_list = *ImGui::GetWindowDrawList();
draw_list.AddText(pos, GetStyleColorU32(ImPlot3DCol_InlayText), builder.c_str());
}
}
//-----------------------------------------------------------------------------
// [SECTION] Plot Box Utils
//-----------------------------------------------------------------------------
// Faces of the box (defined by 4 corner indices)
static const int faces[6][4] = {
{0, 3, 7, 4}, // X-min face
{0, 4, 5, 1}, // Y-min face
{0, 1, 2, 3}, // Z-min face
{1, 2, 6, 5}, // X-max face
{3, 7, 6, 2}, // Y-max face
{4, 5, 6, 7}, // Z-max face
};
// Edges of the box (defined by 2 corner indices)
static const int edges[12][2] = {
// Bottom face edges
{0, 1},
{1, 2},
{2, 3},
{3, 0},
// Top face edges
{4, 5},
{5, 6},
{6, 7},
{7, 4},
// Vertical edges
{0, 4},
{1, 5},
{2, 6},
{3, 7},
};
// Face edges (4 edge indices for each face)
static const int face_edges[6][4] = {
{3, 11, 8, 7}, // X-min face
{0, 8, 4, 9}, // Y-min face
{0, 1, 2, 3}, // Z-min face
{1, 9, 5, 10}, // X-max face
{2, 10, 6, 11}, // Y-max face
{4, 5, 6, 7}, // Z-max face
};
// Lookup table for axis_corners based on active_faces (3D plot)
static const int axis_corners_lookup_3d[8][3][2] = {
// Index 0: active_faces = {0, 0, 0}
{{3, 2}, {1, 2}, {1, 5}},
// Index 1: active_faces = {0, 0, 1}
{{7, 6}, {5, 6}, {1, 5}},
// Index 2: active_faces = {0, 1, 0}
{{0, 1}, {1, 2}, {2, 6}},
// Index 3: active_faces = {0, 1, 1}
{{4, 5}, {5, 6}, {2, 6}},
// Index 4: active_faces = {1, 0, 0}
{{3, 2}, {0, 3}, {0, 4}},
// Index 5: active_faces = {1, 0, 1}
{{7, 6}, {4, 7}, {0, 4}},
// Index 6: active_faces = {1, 1, 0}
{{0, 1}, {0, 3}, {3, 7}},
// Index 7: active_faces = {1, 1, 1}
{{4, 5}, {4, 7}, {3, 7}},
};
int GetMouseOverPlane(const ImPlot3DPlot& plot, const bool* active_faces, const ImVec2* corners_pix, int* plane_out = nullptr) {
ImGuiIO& io = ImGui::GetIO();
ImVec2 mouse_pos = io.MousePos;
if (plane_out)
*plane_out = -1;
// Check each active face
for (int a = 0; a < 3; a++) {
int face_idx = a + 3 * active_faces[a];
ImVec2 p0 = corners_pix[faces[face_idx][0]];
ImVec2 p1 = corners_pix[faces[face_idx][1]];
ImVec2 p2 = corners_pix[faces[face_idx][2]];
ImVec2 p3 = corners_pix[faces[face_idx][3]];
// Check if the mouse is inside the face's quad (using a triangle check)
if (ImTriangleContainsPoint(p0, p1, p2, mouse_pos) || ImTriangleContainsPoint(p2, p3, p0, mouse_pos)) {
if (plane_out)
*plane_out = a;
return a; // Return the plane index: 0 -> YZ, 1 -> XZ, 2 -> XY
}
}
return -1; // Not over any active plane
}
int GetMouseOverAxis(const ImPlot3DPlot& plot, const bool* active_faces, const ImVec2* corners_pix, const int plane_2d, int* edge_out = nullptr) {
const float axis_proximity_threshold = 15.0f; // Distance in pixels to consider the mouse "close" to an axis
ImGuiIO& io = ImGui::GetIO();
ImVec2 mouse_pos = io.MousePos;
if (edge_out)
*edge_out = -1;
bool visible_edges[12];
for (int i = 0; i < 12; i++)
visible_edges[i] = false;
for (int a = 0; a < 3; a++) {
int face_idx = a + 3 * active_faces[a];
if (plane_2d != -1 && a != plane_2d)
continue;
for (size_t i = 0; i < 4; i++)
visible_edges[face_edges[face_idx][i]] = true;
}
// Check each edge for proximity to the mouse
for (int edge = 0; edge < 12; edge++) {
if (!visible_edges[edge])
continue;
ImVec2 p0 = corners_pix[edges[edge][0]];
ImVec2 p1 = corners_pix[edges[edge][1]];
// Check distance to the edge
ImVec2 closest_point = ImLineClosestPoint(p0, p1, mouse_pos);
float dist = ImLengthSqr(mouse_pos - closest_point);
if (dist <= axis_proximity_threshold) {
if (edge_out)
*edge_out = edge;
// Determine which axis the edge belongs to
if (edge == 0 || edge == 2 || edge == 4 || edge == 6)
return 0; // X-axis
else if (edge == 1 || edge == 3 || edge == 5 || edge == 7)
return 1; // Y-axis
else
return 2; // Z-axis
}
}
return -1; // Not over any axis
}
void RenderPlotBackground(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImVec2* corners_pix, const bool* active_faces, const int plane_2d) {
const ImVec4 col_bg = GetStyleColorVec4(ImPlot3DCol_PlotBg);
const ImVec4 col_bg_hov = col_bg + ImVec4(0.03, 0.03, 0.03, 0.0);
int hovered_plane = -1;
if (!plot.Held) {
// If the mouse is not held, highlight plane hovering when mouse over it
hovered_plane = GetMouseOverPlane(plot, active_faces, corners_pix);
if (GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d) != -1)
hovered_plane = -1;
} else {
// If the mouse is held, highlight the held plane
hovered_plane = plot.HeldPlaneIdx;
}
for (int a = 0; a < 3; a++) {
int idx[4]; // Corner indices
for (int i = 0; i < 4; i++)
idx[i] = faces[a + 3 * active_faces[a]][i];
const ImU32 col = ImGui::ColorConvertFloat4ToU32((hovered_plane == a) ? col_bg_hov : col_bg);
draw_list->AddQuadFilled(corners_pix[idx[0]], corners_pix[idx[1]], corners_pix[idx[2]], corners_pix[idx[3]], col);
}
}
void RenderPlotBorder(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImVec2* corners_pix, const bool* active_faces, const int plane_2d) {
ImGuiIO& io = ImGui::GetIO();
int hovered_edge = -1;
if (!plot.Held)
GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d, &hovered_edge);
else
hovered_edge = plot.HeldEdgeIdx;
bool render_edge[12];
for (int i = 0; i < 12; i++)
render_edge[i] = false;
for (int a = 0; a < 3; a++) {
int face_idx = a + 3 * active_faces[a];
if (plane_2d != -1 && a != plane_2d)
continue;
for (size_t i = 0; i < 4; i++)
render_edge[face_edges[face_idx][i]] = true;
}
ImU32 col_bd = GetStyleColorU32(ImPlot3DCol_PlotBorder);
for (int i = 0; i < 12; i++) {
if (render_edge[i]) {
int idx0 = edges[i][0];
int idx1 = edges[i][1];
float thickness = i == hovered_edge ? 3.0f : 1.0f;
draw_list->AddLine(corners_pix[idx0], corners_pix[idx1], col_bd, thickness);
}
}
}
void RenderGrid(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const bool* active_faces, const int plane_2d) {
ImVec4 col_grid = GetStyleColorVec4(ImPlot3DCol_AxisGrid);
ImU32 col_grid_minor = ImGui::GetColorU32(col_grid * ImVec4(1, 1, 1, 0.3f));
ImU32 col_grid_major = ImGui::GetColorU32(col_grid * ImVec4(1, 1, 1, 0.6f));
for (int face = 0; face < 3; face++) {
if (plane_2d != -1 && face != plane_2d)
continue;
int face_idx = face + 3 * active_faces[face];
const ImPlot3DAxis& axis_u = plot.Axes[(face + 1) % 3];
const ImPlot3DAxis& axis_v = plot.Axes[(face + 2) % 3];
// Get the two axes (u and v) that define the face plane
int idx0 = faces[face_idx][0];
int idx1 = faces[face_idx][1];
int idx2 = faces[face_idx][2];
int idx3 = faces[face_idx][3];
// Corners of the face in plot space
ImPlot3DPoint p0 = corners[idx0];
ImPlot3DPoint p1 = corners[idx1];
ImPlot3DPoint p2 = corners[idx2];
ImPlot3DPoint p3 = corners[idx3];
// Vectors along the edges
ImPlot3DPoint u_vec = p1 - p0;
ImPlot3DPoint v_vec = p3 - p0;
// Render grid lines along u axis (axis_u)
if (!ImPlot3D::ImHasFlag(axis_u.Flags, ImPlot3DAxisFlags_NoGridLines))
for (int t = 0; t < axis_u.Ticker.TickCount(); ++t) {
const ImPlot3DTick& tick = axis_u.Ticker.Ticks[t];
// Compute position along u
float t_u = (tick.PlotPos - axis_u.Range.Min) / (axis_u.Range.Max - axis_u.Range.Min);
ImPlot3DPoint p_start = p0 + u_vec * t_u;
ImPlot3DPoint p_end = p3 + u_vec * t_u;
// Convert to pixel coordinates
ImVec2 p_start_pix = PlotToPixels(p_start);
ImVec2 p_end_pix = PlotToPixels(p_end);
// Get color
ImU32 col_line = tick.Major ? col_grid_major : col_grid_minor;
// Draw the grid line
draw_list->AddLine(p_start_pix, p_end_pix, col_line);
}
// Render grid lines along v axis (axis_v)
if (!ImPlot3D::ImHasFlag(axis_v.Flags, ImPlot3DAxisFlags_NoGridLines))
for (int t = 0; t < axis_v.Ticker.TickCount(); ++t) {
const ImPlot3DTick& tick = axis_v.Ticker.Ticks[t];
// Compute position along v
float t_v = (tick.PlotPos - axis_v.Range.Min) / (axis_v.Range.Max - axis_v.Range.Min);
ImPlot3DPoint p_start = p0 + v_vec * t_v;
ImPlot3DPoint p_end = p1 + v_vec * t_v;
// Convert to pixel coordinates
ImVec2 p_start_pix = PlotToPixels(p_start);
ImVec2 p_end_pix = PlotToPixels(p_end);
// Get color
ImU32 col_line = tick.Major ? col_grid_major : col_grid_minor;
// Draw the grid line
draw_list->AddLine(p_start_pix, p_end_pix, col_line);
}
}
}
void RenderTickMarks(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2], const int plane_2d) {
ImU32 col_tick = GetStyleColorU32(ImPlot3DCol_AxisTick);
auto DeterminePlaneForAxis = [&](int axis_idx) {
if (plane_2d != -1)
return plane_2d;
// If no plane chosen (-1), use:
// X or Y axis -> XY plane (2)
// Z axis -> YZ plane (0)
if (axis_idx == 2)
return 1; // Z-axis use XZ plane
else
return 2; // X or Y-axis use XY plane
};
for (int a = 0; a < 3; a++) {
const ImPlot3DAxis& axis = plot.Axes[a];
if (ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_NoTickMarks))
continue;
int idx0 = axis_corners[a][0];
int idx1 = axis_corners[a][1];
if (idx0 == idx1) // axis not visible or invalid
continue;
ImPlot3DPoint axis_start = corners[idx0];
ImPlot3DPoint axis_end = corners[idx1];
ImPlot3DPoint axis_dir = axis_end - axis_start;
float axis_len = axis_dir.Length();
if (axis_len < 1e-12f)
continue;
axis_dir /= axis_len;
// Draw axis line
ImVec2 axis_start_pix = corners_pix[idx0];
ImVec2 axis_end_pix = corners_pix[idx1];
draw_list->AddLine(axis_start_pix, axis_end_pix, col_tick);
// Choose plane
int chosen_plane = DeterminePlaneForAxis(a);
// Project axis_dir onto chosen plane
ImPlot3DPoint proj_dir = axis_dir;
if (chosen_plane == 0) {
// YZ plane: zero out x
proj_dir.x = 0.0f;
} else if (chosen_plane == 1) {
// XZ plane: zero out y
proj_dir.y = 0.0f;
} else if (chosen_plane == 2) {
// XY plane: zero out z
proj_dir.z = 0.0f;
}
float proj_len = proj_dir.Length();
if (proj_len < 1e-12f) {
// Axis is parallel to plane normal or something degenerate, skip ticks
continue;
}
proj_dir /= proj_len;
// Rotate 90 degrees in chosen plane
ImPlot3DPoint tick_dir;
if (chosen_plane == 0) {
// YZ plane
// proj_dir=(0,py,pz), rotate 90°: (py,pz) -> (-pz,py)
tick_dir = ImPlot3DPoint(0, -proj_dir.z, proj_dir.y);
} else if (chosen_plane == 1) {
// XZ plane (plane=1)
// proj_dir=(px,0,pz), rotate 90°: (px,pz) -> (-pz,px)
tick_dir = ImPlot3DPoint(-proj_dir.z, 0, proj_dir.x);
} else {
// XY plane
// proj_dir=(px,py,0), rotate by 90°: (px,py) -> (-py,px)
tick_dir = ImPlot3DPoint(-proj_dir.y, proj_dir.x, 0);
}
tick_dir.Normalize();
// Tick lengths in NDC units
const float major_size_ndc = 0.06f;
const float minor_size_ndc = 0.03f;
for (int t = 0; t < axis.Ticker.TickCount(); ++t) {
const ImPlot3DTick& tick = axis.Ticker.Ticks[t];
float v = (tick.PlotPos - axis.Range.Min) / (axis.Range.Max - axis.Range.Min);
ImPlot3DPoint tick_pos_ndc = PlotToNDC(axis_start + axis_dir * (v * axis_len));
// Half tick on each side of the axis line
float size_tick_ndc = tick.Major ? major_size_ndc : minor_size_ndc;
ImPlot3DPoint half_tick_ndc = tick_dir * (size_tick_ndc * 0.5f);
ImPlot3DPoint T1_ndc = tick_pos_ndc - half_tick_ndc;
ImPlot3DPoint T2_ndc = tick_pos_ndc + half_tick_ndc;
ImVec2 T1_screen = NDCToPixels(T1_ndc);
ImVec2 T2_screen = NDCToPixels(T2_ndc);
draw_list->AddLine(T1_screen, T2_screen, col_tick);
}
}
}
void RenderTickLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2]) {
ImVec2 box_center_pix = PlotToPixels(plot.RangeCenter());
ImU32 col_tick_txt = GetStyleColorU32(ImPlot3DCol_AxisText);
for (int a = 0; a < 3; a++) {
const ImPlot3DAxis& axis = plot.Axes[a];
if (ImPlot3D::ImHasFlag(axis.Flags, ImPlot3DAxisFlags_NoTickLabels))
continue;
// Corner indices for this axis
int idx0 = axis_corners[a][0];
int idx1 = axis_corners[a][1];
// If normal to the 2D plot, ignore the ticks
if (idx0 == idx1)
continue;
// Start and end points of the axis in plot space
ImPlot3DPoint axis_start = corners[idx0];
ImPlot3DPoint axis_end = corners[idx1];
// Direction vector along the axis
ImPlot3DPoint axis_dir = axis_end - axis_start;
// Convert axis start and end to screen space
ImVec2 axis_start_pix = corners_pix[idx0];
ImVec2 axis_end_pix = corners_pix[idx1];
// Screen space axis direction
ImVec2 axis_screen_dir = axis_end_pix - axis_start_pix;
float axis_length = ImSqrt(ImLengthSqr(axis_screen_dir));
if (axis_length != 0.0f)
axis_screen_dir /= axis_length;
else
axis_screen_dir = ImVec2(1.0f, 0.0f); // Default direction if length is zero
// Perpendicular direction in screen space
ImVec2 offset_dir_pix = ImVec2(-axis_screen_dir.y, axis_screen_dir.x);
// Make sure direction points away from cube center
ImVec2 box_center_pix = PlotToPixels(plot.RangeCenter());
ImVec2 axis_center_pix = (axis_start_pix + axis_end_pix) * 0.5f;
ImVec2 center_to_axis_pix = axis_center_pix - box_center_pix;
center_to_axis_pix /= ImSqrt(ImLengthSqr(center_to_axis_pix));
if (ImDot(offset_dir_pix, center_to_axis_pix) < 0.0f)
offset_dir_pix = -offset_dir_pix;
// Adjust the offset magnitude
float offset_magnitude = 20.0f; // TODO Calculate based on label size
ImVec2 offset_pix = offset_dir_pix * offset_magnitude;
// Compute angle perpendicular to axis in screen space
float angle = atan2f(-axis_screen_dir.y, axis_screen_dir.x) + IM_PI * 0.5f;
// Normalize angle to be between -π and π
if (angle > IM_PI)
angle -= 2 * IM_PI;
if (angle < -IM_PI)
angle += 2 * IM_PI;
// Adjust angle to keep labels upright
if (angle > IM_PI * 0.5f)
angle -= IM_PI;
if (angle < -IM_PI * 0.5f)
angle += IM_PI;
// Loop over ticks
for (int t = 0; t < axis.Ticker.TickCount(); ++t) {
const ImPlot3DTick& tick = axis.Ticker.Ticks[t];
if (!tick.ShowLabel)
continue;
// Compute position along the axis
float t_axis = (tick.PlotPos - axis.Range.Min) / (axis.Range.Max - axis.Range.Min);
ImPlot3DPoint tick_pos = axis_start + axis_dir * t_axis;
// Convert to pixel coordinates
ImVec2 tick_pos_pix = PlotToPixels(tick_pos);
// Get the tick label text
const char* label = axis.Ticker.GetText(tick);
// Adjust label position by offset
ImVec2 label_pos_pix = tick_pos_pix + offset_pix;
// Render the tick label
AddTextRotated(draw_list, label_pos_pix, angle, col_tick_txt, label);
}
}
}
void RenderAxisLabels(ImDrawList* draw_list, const ImPlot3DPlot& plot, const ImPlot3DPoint* corners, const ImVec2* corners_pix, const int axis_corners[3][2]) {
ImPlot3DPoint range_center = plot.RangeCenter();
for (int a = 0; a < 3; a++) {
const ImPlot3DAxis& axis = plot.Axes[a];
if (!axis.HasLabel())
continue;
const char* label = axis.GetLabel();
// Corner indices
int idx0 = axis_corners[a][0];
int idx1 = axis_corners[a][1];
// If normal to the 2D plot, ignore axis label
if (idx0 == idx1)
continue;
// Position at the end of the axis
ImPlot3DPoint label_pos = (corners[idx0] + corners[idx1]) * 0.5f;
// Add offset
label_pos += (label_pos - range_center) * 0.4f;
// Convert to pixel coordinates
ImVec2 label_pos_pix = PlotToPixels(label_pos);
// Adjust label position and angle
ImU32 col_ax_txt = GetStyleColorU32(ImPlot3DCol_AxisText);
// Compute text angle
ImVec2 screen_delta = corners_pix[idx1] - corners_pix[idx0];
float angle = atan2f(-screen_delta.y, screen_delta.x);
if (angle > IM_PI * 0.5f)
angle -= IM_PI;
if (angle < -IM_PI * 0.5f)
angle += IM_PI;
AddTextRotated(draw_list, label_pos_pix, angle, col_ax_txt, label);
}
}
// Function to compute active faces based on the rotation
// If the plot is close to 2D, plane_2d is set to the plane index (0 -> YZ, 1 -> XZ, 2 -> XY)
// plane_2d is set to -1 otherwise
void ComputeActiveFaces(bool* active_faces, const ImPlot3DQuat& rotation, int* plane_2d = nullptr) {
if (plane_2d)
*plane_2d = -1;
ImPlot3DPoint rot_face_n[3] = {
rotation * ImPlot3DPoint(1.0f, 0.0f, 0.0f),
rotation * ImPlot3DPoint(0.0f, 1.0f, 0.0f),
rotation * ImPlot3DPoint(0.0f, 0.0f, 1.0f),
};
int num_deg = 0; // Check number of planes that are degenerate (seen as a line)
for (int i = 0; i < 3; ++i) {
// Determine the active face based on the Z component
if (fabs(rot_face_n[i].z) < 0.025) {
// If aligned with the plane, choose the min face for bottom/left
active_faces[i] = rot_face_n[i].x + rot_face_n[i].y < 0.0f;
num_deg++;
} else {
// Otherwise, determine based on the Z component
active_faces[i] = rot_face_n[i].z < 0.0f;
// Set this plane as possible 2d plane
if (plane_2d)
*plane_2d = i;
}
}
// Only return 2d plane if there are exactly 2 degenerate planes
if (num_deg != 2 && plane_2d)
*plane_2d = -1;
}
// Function to compute the box corners in plot space
void ComputeBoxCorners(ImPlot3DPoint* corners, const ImPlot3DPoint& range_min, const ImPlot3DPoint& range_max) {
corners[0] = ImPlot3DPoint(range_min.x, range_min.y, range_min.z); // 0
corners[1] = ImPlot3DPoint(range_max.x, range_min.y, range_min.z); // 1
corners[2] = ImPlot3DPoint(range_max.x, range_max.y, range_min.z); // 2
corners[3] = ImPlot3DPoint(range_min.x, range_max.y, range_min.z); // 3
corners[4] = ImPlot3DPoint(range_min.x, range_min.y, range_max.z); // 4
corners[5] = ImPlot3DPoint(range_max.x, range_min.y, range_max.z); // 5
corners[6] = ImPlot3DPoint(range_max.x, range_max.y, range_max.z); // 6
corners[7] = ImPlot3DPoint(range_min.x, range_max.y, range_max.z); // 7
}
// Function to compute the box corners in pixel space
void ComputeBoxCornersPix(ImVec2* corners_pix, const ImPlot3DPoint* corners) {
for (int i = 0; i < 8; i++) {
corners_pix[i] = PlotToPixels(corners[i]);
}
}
void RenderPlotBox(ImDrawList* draw_list, const ImPlot3DPlot& plot) {
// Get plot parameters
const ImRect& plot_area = plot.PlotRect;
const ImPlot3DQuat& rotation = plot.Rotation;
ImPlot3DPoint range_min = plot.RangeMin();
ImPlot3DPoint range_max = plot.RangeMax();
ImPlot3DPoint range_center = plot.RangeCenter();
// Compute active faces
bool active_faces[3];
int plane_2d = -1;
ComputeActiveFaces(active_faces, rotation, &plane_2d);
bool is_2d = plane_2d != -1;
// Compute box corners in plot space
ImPlot3DPoint corners[8];
ComputeBoxCorners(corners, range_min, range_max);
// Compute box corners in pixel space
ImVec2 corners_pix[8];
ComputeBoxCornersPix(corners_pix, corners);
// Compute axes start and end corners (given current rotation)
int axis_corners[3][2];
if (is_2d) {
int face = plane_2d + 3 * active_faces[plane_2d]; // Face of the 2D plot
int common_edges[2] = {-1, -1}; // Edges shared by the 3 faces
// Find the common edges between the 3 faces
for (int i = 0; i < 4; i++) {
int edge = face_edges[face][i];
for (int j = 0; j < 2; j++) {
int axis = (plane_2d + 1 + j) % 3;
int face_idx = axis + active_faces[axis] * 3;
for (int k = 0; k < 4; k++) {
if (face_edges[face_idx][k] == edge) {
common_edges[j] = edge;
break;
}
}
}
}
// Get corners from 2 edges (origin is the corner in common)
int origin_corner = -1;
int x_corner = -1;
int y_corner = -1;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
if (edges[common_edges[0]][i] == edges[common_edges[1]][j]) {
origin_corner = edges[common_edges[0]][i];
x_corner = edges[common_edges[0]][!i];
y_corner = edges[common_edges[1]][!j];
}
// Swap x and y if they are flipped
ImVec2 x_vec = corners_pix[x_corner] - corners_pix[origin_corner];
ImVec2 y_vec = corners_pix[y_corner] - corners_pix[origin_corner];
if (y_vec.x > x_vec.x)
ImSwap(x_corner, y_corner);
// Check which 3d axis the 2d axis refers to
ImPlot3DPoint origin_3d = corners[origin_corner];
ImPlot3DPoint x_3d = (corners[x_corner] - origin_3d).Normalized();
ImPlot3DPoint y_3d = (corners[y_corner] - origin_3d).Normalized();
int x_axis = -1;
bool x_inverted = false;
int y_axis = -1;
bool y_inverted = false;
for (int i = 0; i < 2; i++) {
int axis_i = (plane_2d + 1 + i) % 3;
if (y_axis != -1 || (ImAbs(x_3d[axis_i]) > 1e-8f && x_axis == -1)) {
x_axis = axis_i;
x_inverted = x_3d[axis_i] < 0.0f;
} else {
y_axis = axis_i;
y_inverted = y_3d[axis_i] < 0.0f;
}
}
// Set the 3d axis corners based on the 2d axis corners
axis_corners[plane_2d][0] = -1;
axis_corners[plane_2d][1] = -1;
if (x_inverted) {
axis_corners[x_axis][0] = x_corner;
axis_corners[x_axis][1] = origin_corner;
} else {
axis_corners[x_axis][0] = origin_corner;
axis_corners[x_axis][1] = x_corner;
}
if (y_inverted) {
axis_corners[y_axis][0] = y_corner;
axis_corners[y_axis][1] = origin_corner;
} else {
axis_corners[y_axis][0] = origin_corner;
axis_corners[y_axis][1] = y_corner;
}
} else {
int index = (active_faces[0] << 2) | (active_faces[1] << 1) | (active_faces[2]);
for (int a = 0; a < 3; a++) {
axis_corners[a][0] = axis_corners_lookup_3d[index][a][0];
axis_corners[a][1] = axis_corners_lookup_3d[index][a][1];
}
}
// Render components
RenderPlotBackground(draw_list, plot, corners_pix, active_faces, plane_2d);
RenderPlotBorder(draw_list, plot, corners_pix, active_faces, plane_2d);
RenderGrid(draw_list, plot, corners, active_faces, plane_2d);
RenderTickMarks(draw_list, plot, corners, corners_pix, axis_corners, plane_2d);
RenderTickLabels(draw_list, plot, corners, corners_pix, axis_corners);
RenderAxisLabels(draw_list, plot, corners, corners_pix, axis_corners);
}
//-----------------------------------------------------------------------------
// [SECTION] Formatter
//-----------------------------------------------------------------------------
int Formatter_Default(float value, char* buff, int size, void* data) {
char* fmt = (char*)data;
return ImFormatString(buff, size, fmt, value);
}
//------------------------------------------------------------------------------
// [SECTION] Locator
//------------------------------------------------------------------------------
double NiceNum(double x, bool round) {
double f;
double nf;
int expv = (int)floor(ImLog10(x));
f = x / ImPow(10.0, (double)expv);
if (round)
if (f < 1.5)
nf = 1;
else if (f < 3)
nf = 2;
else if (f < 7)
nf = 5;
else
nf = 10;
else if (f <= 1)
nf = 1;
else if (f <= 2)
nf = 2;
else if (f <= 5)
nf = 5;
else
nf = 10;
return nf * ImPow(10.0, expv);
}
void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, ImPlot3DFormatter formatter, void* formatter_data) {
if (range.Min == range.Max)
return;
const int nMinor = 5;
const int nMajor = 3;
const int max_ticks_labels = 7;
const double nice_range = NiceNum(range.Size() * 0.99, false);
const double interval = NiceNum(nice_range / (nMajor - 1), true);
const double graphmin = floor(range.Min / interval) * interval;
const double graphmax = ceil(range.Max / interval) * interval;
bool first_major_set = false;
int first_major_idx = 0;
const int idx0 = ticker.TickCount(); // ticker may have user custom ticks
ImVec2 total_size(0, 0);
for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
// is this zero? combat zero formatting issues
if (major - interval < 0 && major + interval > 0)
major = 0;
if (range.Contains(major)) {
if (!first_major_set) {
first_major_idx = ticker.TickCount();
first_major_set = true;
}
total_size += ticker.AddTick(major, true, true, formatter, formatter_data).LabelSize;
}
for (int i = 1; i < nMinor; ++i) {
double minor = major + i * interval / nMinor;
if (range.Contains(minor)) {
total_size += ticker.AddTick(minor, false, true, formatter, formatter_data).LabelSize;
}
}
}
// Prune tick labels
if (ticker.TickCount() > max_ticks_labels) {
for (int i = first_major_idx - 1; i >= idx0; i -= 2)
ticker.Ticks[i].ShowLabel = false;
for (int i = first_major_idx + 1; i < ticker.TickCount(); i += 2)
ticker.Ticks[i].ShowLabel = false;
}
}
//------------------------------------------------------------------------------
// [SECTION] Context Menus
//------------------------------------------------------------------------------
bool ShowLegendContextMenu(ImPlot3DLegend& legend, bool visible) {
const float s = ImGui::GetFrameHeight();
bool ret = false;
if (ImGui::Checkbox("Show", &visible))
ret = true;
if (ImGui::RadioButton("H", ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal)))
legend.Flags |= ImPlot3DLegendFlags_Horizontal;
ImGui::SameLine();
if (ImGui::RadioButton("V", !ImPlot3D::ImHasFlag(legend.Flags, ImPlot3DLegendFlags_Horizontal)))
legend.Flags &= ~ImPlot3DLegendFlags_Horizontal;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
// clang-format off
if (ImGui::Button("NW",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_NorthWest; } ImGui::SameLine();
if (ImGui::Button("N", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_North; } ImGui::SameLine();
if (ImGui::Button("NE",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_NorthEast; }
if (ImGui::Button("W", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_West; } ImGui::SameLine();
if (ImGui::InvisibleButton("C", ImVec2(1.5f*s,s))) { } ImGui::SameLine();
if (ImGui::Button("E", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_East; }
if (ImGui::Button("SW",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_SouthWest; } ImGui::SameLine();
if (ImGui::Button("S", ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_South; } ImGui::SameLine();
if (ImGui::Button("SE",ImVec2(1.5f*s,s))) { legend.Location = ImPlot3DLocation_SouthEast; }
// clang-format on
ImGui::PopStyleVar();
return ret;
}
void ShowAxisContextMenu(ImPlot3DAxis& axis) {
ImGui::PushItemWidth(75);
bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting();
bool label = axis.HasLabel();
bool grid = axis.HasGridLines();
bool ticks = axis.HasTickMarks();
bool labels = axis.HasTickLabels();
double drag_speed = (axis.Range.Size() <= FLT_EPSILON) ? FLT_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits.
ImGui::BeginDisabled(always_locked);
ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_LockMin);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(axis.IsLockedMin() || always_locked);
float temp_min = axis.Range.Min;
if (ImGui::DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - FLT_EPSILON)) {
axis.SetMin(temp_min, true);
}
ImGui::EndDisabled();
ImGui::BeginDisabled(always_locked);
ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_LockMax);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(axis.IsLockedMax() || always_locked);
float temp_max = axis.Range.Max;
if (ImGui::DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + FLT_EPSILON, HUGE_VAL)) {
axis.SetMax(temp_max, true);
}
ImGui::EndDisabled();
ImGui::Separator();
// Flags
ImGui::CheckboxFlags("Auto-Fit", (unsigned int*)&axis.Flags, ImPlot3DAxisFlags_AutoFit);
ImGui::Separator();
ImGui::BeginDisabled(axis.Label.empty());
if (ImGui::Checkbox("Label", &label))
ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoLabel);
ImGui::EndDisabled();
if (ImGui::Checkbox("Grid Lines", &grid))
ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoGridLines);
if (ImGui::Checkbox("Tick Marks", &ticks))
ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoTickMarks);
if (ImGui::Checkbox("Tick Labels", &labels))
ImFlipFlag(axis.Flags, ImPlot3DAxisFlags_NoTickLabels);
}
void ShowPlotContextMenu(ImPlot3DPlot& plot) {
ImPlot3DContext& gp = *GImPlot3D;
const bool owns_legend = gp.CurrentItems == &plot.Items;
char buf[16] = {};
const char* axis_labels[3] = {"X-Axis", "Y-Axis", "Z-Axis"};
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
ImGui::PushID(i);
ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "X-Axis" : "X-Axis %d", i + 1);
if (ImGui::BeginMenu(axis.HasLabel() ? axis.GetLabel() : axis_labels[i])) {
ShowAxisContextMenu(axis);
ImGui::EndMenu();
}
ImGui::PopID();
}
ImGui::Separator();
if ((ImGui::BeginMenu("Legend"))) {
if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend)))
ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend);
ImGui::EndMenu();
}
if ((ImGui::BeginMenu("Settings"))) {
ImGui::BeginDisabled(plot.Title.empty());
if (ImGui::MenuItem("Title", nullptr, plot.HasTitle()))
ImFlipFlag(plot.Flags, ImPlot3DFlags_NoTitle);
ImGui::EndDisabled();
ImGui::EndMenu();
}
}
//-----------------------------------------------------------------------------
// [SECTION] Begin/End Plot
//-----------------------------------------------------------------------------
bool BeginPlot(const char* title_id, const ImVec2& size, ImPlot3DFlags flags) {
IMPLOT3D_CHECK_CTX();
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Mismatched BeginPlot()/EndPlot()!");
// Get window
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
// Skip if needed
if (window->SkipItems)
return false;
// Get or create plot
const ImGuiID ID = window->GetID(title_id);
const bool just_created = gp.Plots.GetByKey(ID) == nullptr;
gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID);
gp.CurrentItems = &gp.CurrentPlot->Items;
ImPlot3DPlot& plot = *gp.CurrentPlot;
// Populate plot
plot.ID = ID;
plot.JustCreated = just_created;
if (just_created) {
plot.Rotation = init_rotation;
plot.FitThisFrame = true;
for (int i = 0; i < 3; i++) {
plot.Axes[i] = ImPlot3DAxis();
plot.Axes[i].FitThisFrame = true;
}
}
if (plot.PreviousFlags != flags)
plot.Flags = flags;
plot.PreviousFlags = flags;
plot.SetupLocked = false;
plot.OpenContextThisFrame = false;
// Populate title
plot.SetTitle(title_id);
// Calculate frame size
ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);
if (frame_size.x < gp.Style.PlotMinSize.x && size.x < 0.0f)
frame_size.x = gp.Style.PlotMinSize.x;
if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f)
frame_size.y = gp.Style.PlotMinSize.y;
// Create child window to capture scroll
ImGui::BeginChild(title_id, frame_size, false, ImGuiWindowFlags_NoScrollbar);
window = ImGui::GetCurrentWindow();
window->ScrollMax.y = 1.0f;
plot.FrameRect = ImRect(window->DC.CursorPos, window->DC.CursorPos + frame_size);
ImGui::ItemSize(plot.FrameRect);
if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect)) {
gp.CurrentPlot = nullptr;
gp.CurrentItems = nullptr;
ImGui::EndChild();
return false;
}
// Reset legend
plot.Items.Legend.Reset();
// Push frame rect clipping
ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
plot.DrawList._Flags = window->DrawList->Flags;
plot.DrawList._SharedData = ImGui::GetDrawListSharedData();
return true;
}
void EndPlot() {
IMPLOT3D_CHECK_CTX();
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
// Move triangles from 3D draw list to ImGui draw list
plot.DrawList.SortedMoveToImGuiDrawList();
// Handle data fitting
if (plot.FitThisFrame) {
plot.FitThisFrame = false;
for (int i = 0; i < 3; i++) {
if (plot.Axes[i].FitThisFrame) {
plot.Axes[i].FitThisFrame = false;
plot.Axes[i].ApplyFit();
}
}
}
// Lock setup if not already done
SetupLock();
// Reset legend hover
plot.Items.Legend.Hovered = false;
// Render legend
RenderLegend();
// Render mouse position
RenderMousePos();
// Legend context menu
if (ImGui::BeginPopup("##LegendContext")) {
ImGui::Text("Legend");
ImGui::Separator();
if (ShowLegendContextMenu(plot.Items.Legend, !ImPlot3D::ImHasFlag(plot.Flags, ImPlot3DFlags_NoLegend)))
ImFlipFlag(plot.Flags, ImPlot3DFlags_NoLegend);
ImGui::EndPopup();
}
// Axis context menus
static const char* axis_contexts[3] = {"##XAxisContext", "##YAxisContext", "##ZAxisContext"};
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
if (ImGui::BeginPopup(axis_contexts[i])) {
ImGui::Text(axis.HasLabel() ? axis.GetLabel() : "%c-Axis", 'X' + i);
ImGui::Separator();
ShowAxisContextMenu(axis);
ImGui::EndPopup();
}
}
// Plot context menu
if (ImGui::BeginPopup("##PlotContext")) {
ShowPlotContextMenu(plot);
ImGui::EndPopup();
}
// Pop frame rect clipping
ImGui::PopClipRect();
// End child window
ImGui::EndChild();
// Reset current plot
gp.CurrentPlot = nullptr;
gp.CurrentItems = nullptr;
// Reset the plot items for the next frame
for (int i = 0; i < plot.Items.GetItemCount(); i++)
plot.Items.GetItemByIndex(i)->SeenThisFrame = false;
}
//-----------------------------------------------------------------------------
// [SECTION] Setup
//-----------------------------------------------------------------------------
void SetupAxis(ImAxis3D idx, const char* label, ImPlot3DAxisFlags flags) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
"SetupAxis() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!");
// Get plot and axis
ImPlot3DPlot& plot = *gp.CurrentPlot;
ImPlot3DAxis& axis = plot.Axes[idx];
if (axis.PreviousFlags != flags)
axis.Flags = flags;
axis.PreviousFlags = flags;
axis.SetLabel(label);
}
void SetupAxisLimits(ImAxis3D idx, double min_lim, double max_lim, ImPlot3DCond cond) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
"SetupAxisLimits() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis
ImPlot3DPlot& plot = *gp.CurrentPlot;
ImPlot3DAxis& axis = plot.Axes[idx];
if (!plot.Initialized || cond == ImPlot3DCond_Always) {
axis.SetRange(min_lim, max_lim);
axis.RangeCond = cond;
axis.FitThisFrame = false;
}
}
void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags, ImPlot3DAxisFlags y_flags, ImPlot3DAxisFlags z_flags) {
SetupAxis(ImAxis3D_X, x_label, x_flags);
SetupAxis(ImAxis3D_Y, y_label, y_flags);
SetupAxis(ImAxis3D_Z, z_label, z_flags);
}
void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, ImPlot3DCond cond) {
SetupAxisLimits(ImAxis3D_X, x_min, x_max, cond);
SetupAxisLimits(ImAxis3D_Y, y_min, y_max, cond);
SetupAxisLimits(ImAxis3D_Z, z_min, z_max, cond);
if (cond == ImPlot3DCond_Once)
GImPlot3D->CurrentPlot->FitThisFrame = false;
}
void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
"SetupLegend() needs to be called after BeginPlot() and before any setup locking functions (e.g. PlotX)!");
IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr,
"SetupLegend() needs to be called within an itemized context!");
ImPlot3DLegend& legend = gp.CurrentItems->Legend;
if (legend.PreviousLocation != location)
legend.Location = location;
legend.PreviousLocation = location;
if (legend.PreviousFlags != flags)
legend.Flags = flags;
legend.PreviousFlags = flags;
}
//-----------------------------------------------------------------------------
// [SECTION] Plot Utils
//-----------------------------------------------------------------------------
ImPlot3DPlot* GetCurrentPlot() {
return GImPlot3D->CurrentPlot;
}
void BustPlotCache() {
ImPlot3DContext& gp = *GImPlot3D;
gp.Plots.Clear();
}
ImVec2 PlotToPixels(const ImPlot3DPoint& point) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!");
return NDCToPixels(PlotToNDC(point));
}
ImVec2 PlotToPixels(double x, double y, double z) {
return PlotToPixels(ImPlot3DPoint(x, y, z));
}
ImPlot3DRay PixelsToPlotRay(const ImVec2& pix) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlotRay() needs to be called between BeginPlot() and EndPlot()!");
return NDCRayToPlotRay(PixelsToNDCRay(pix));
}
ImPlot3DRay PixelsToPlotRay(double x, double y) {
return PixelsToPlotRay(ImVec2(x, y));
}
ImPlot3DPoint PixelsToPlotPlane(const ImVec2& pix, ImPlane3D plane, bool mask) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlotPlane() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
ImPlot3DRay ray = PixelsToNDCRay(pix);
const ImPlot3DPoint& O = ray.Origin;
const ImPlot3DPoint& D = ray.Direction;
// Helper lambda to check intersection with a given coordinate and return intersection point if valid.
auto IntersectPlane = [&](float coord) -> ImPlot3DPoint {
// Solve for t in O[axis] + D[axis]*t = coord
float denom = 0.0f;
float numer = 0.0f;
if (plane == ImPlane3D_YZ) {
denom = D.x;
numer = coord - O.x;
} else if (plane == ImPlane3D_XZ) {
denom = D.y;
numer = coord - O.y;
} else if (plane == ImPlane3D_XY) {
denom = D.z;
numer = coord - O.z;
}
if (ImAbs(denom) < 1e-12f) {
// Ray is parallel or nearly parallel to the plane
return ImPlot3DPoint(NAN, NAN, NAN);
}
float t = numer / denom;
if (t < 0.0f) {
// Intersection behind the ray origin
return ImPlot3DPoint(NAN, NAN, NAN);
}
return O + D * t;
};
// Helper lambda to check if point P is within the plot box
auto InRange = [&](const ImPlot3DPoint& P) {
return P.x >= -0.5 && P.x <= 0.5 &&
P.y >= -0.5 && P.y <= 0.5 &&
P.z >= -0.5 && P.z <= 0.5;
};
// Compute which plane to intersect with
bool active_faces[3];
ComputeActiveFaces(active_faces, plot.Rotation);
// Calculate intersection point with the planes
ImPlot3DPoint P = IntersectPlane(active_faces[plane] ? 0.5 : -0.5);
if (P.IsNaN())
return P;
// Handle mask (if one of the intersections is out of range, set it to NAN)
if (mask) {
switch (plane) {
case ImPlane3D_YZ:
if (!InRange(ImPlot3DPoint(0.0, P.y, P.z)))
return ImPlot3DPoint(NAN, NAN, NAN);
break;
case ImPlane3D_XZ:
if (!InRange(ImPlot3DPoint(P.x, 0.0, P.z)))
return ImPlot3DPoint(NAN, NAN, NAN);
break;
case ImPlane3D_XY:
if (!InRange(ImPlot3DPoint(P.x, P.y, 0.0)))
return ImPlot3DPoint(NAN, NAN, NAN);
break;
}
}
return NDCToPlot(P);
}
ImPlot3DPoint PixelsToPlotPlane(double x, double y, ImPlane3D plane, bool mask) {
return PixelsToPlotPlane(ImVec2(x, y), plane, mask);
}
ImVec2 GetPlotPos() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!");
SetupLock();
return gp.CurrentPlot->PlotRect.Min;
}
ImVec2 GetPlotSize() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!");
SetupLock();
return gp.CurrentPlot->PlotRect.GetSize();
}
ImVec2 GetFramePos() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetFramePos() needs to be called between BeginPlot() and EndPlot()!");
return gp.CurrentPlot->FrameRect.Min;
}
ImVec2 GetFrameSize() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetFrameSize() needs to be called between BeginPlot() and EndPlot()!");
return gp.CurrentPlot->FrameRect.GetSize();
}
ImPlot3DPoint PlotToNDC(const ImPlot3DPoint& point) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToNDC() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();
ImPlot3DPoint ndc_point;
for (int i = 0; i < 3; i++)
ndc_point[i] = plot.Axes[i].PlotToNDC(point[i]);
return ndc_point;
}
ImPlot3DPoint NDCToPlot(const ImPlot3DPoint& point) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCToPlot() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();
ImPlot3DPoint plot_point;
for (int i = 0; i < 3; i++)
plot_point[i] = plot.Axes[i].NDCToPlot(point[i]);
return plot_point;
}
ImVec2 NDCToPixels(const ImPlot3DPoint& point) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCToPixels() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();
float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
ImVec2 center = plot.PlotRect.GetCenter();
ImPlot3DPoint point_pix = zoom * (plot.Rotation * point);
point_pix.y *= -1.0f; // Invert y-axis
point_pix.x += center.x;
point_pix.y += center.y;
return {point_pix.x, point_pix.y};
}
ImPlot3DRay PixelsToNDCRay(const ImVec2& pix) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToNDCRay() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();
// Calculate zoom factor and plot center
float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
ImVec2 center = plot.PlotRect.GetCenter();
// Undo screen transformations to get back to NDC space
float x = (pix.x - center.x) / zoom;
float y = -(pix.y - center.y) / zoom; // Invert y-axis
// Define near and far points in NDC space along the z-axis
ImPlot3DPoint ndc_near = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, -10.0f);
ImPlot3DPoint ndc_far = plot.Rotation.Inverse() * ImPlot3DPoint(x, y, 10.0f);
// Create the ray in NDC space
ImPlot3DRay ndc_ray;
ndc_ray.Origin = ndc_near;
ndc_ray.Direction = (ndc_far - ndc_near).Normalized();
return ndc_ray;
}
ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "NDCRayToPlotRay() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
SetupLock();
// Convert NDC origin and a point along the ray to plot coordinates
ImPlot3DPoint plot_origin = NDCToPlot(ray.Origin);
ImPlot3DPoint ndc_point_along_ray = ray.Origin + ray.Direction;
ImPlot3DPoint plot_point_along_ray = NDCToPlot(ndc_point_along_ray);
// Compute the direction in plot coordinates
ImPlot3DPoint plot_direction = (plot_point_along_ray - plot_origin).Normalized();
// Create the ray in plot coordinates
ImPlot3DRay plot_ray;
plot_ray.Origin = plot_origin;
plot_ray.Direction = plot_direction;
return plot_ray;
}
//-----------------------------------------------------------------------------
// [SECTION] Setup Utils
//-----------------------------------------------------------------------------
static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f;
static const float ANIMATION_ANGULAR_VELOCITY = 2 * 3.1415f;
void HandleInput(ImPlot3DPlot& plot) {
ImGuiIO& IO = ImGui::GetIO();
// clang-format off
const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowOverlap
| ImGuiButtonFlags_PressedOnClick
| ImGuiButtonFlags_PressedOnDoubleClick
| ImGuiButtonFlags_MouseButtonLeft
| ImGuiButtonFlags_MouseButtonRight
| ImGuiButtonFlags_MouseButtonMiddle;
// clang-format on
const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect, plot.ID, &plot.Hovered, &plot.Held, plot_button_flags);
#if (IMGUI_VERSION_NUM < 18966)
ImGui::SetItemAllowOverlap(); // Handled by ButtonBehavior()
#endif
// State
const ImVec2 rot_drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
const bool rotating = ImLengthSqr(rot_drag) > MOUSE_CURSOR_DRAG_THRESHOLD;
// Check if any axis/plane is hovered
const ImPlot3DQuat& rotation = plot.Rotation;
ImPlot3DPoint range_min = plot.RangeMin();
ImPlot3DPoint range_max = plot.RangeMax();
bool active_faces[3];
int plane_2d = -1;
ComputeActiveFaces(active_faces, rotation, &plane_2d);
ImPlot3DPoint corners[8];
ComputeBoxCorners(corners, range_min, range_max);
ImVec2 corners_pix[8];
ComputeBoxCornersPix(corners_pix, corners);
int hovered_plane_idx = -1;
int hovered_plane = GetMouseOverPlane(plot, active_faces, corners_pix, &hovered_plane_idx);
int hovered_edge_idx = -1;
int hovered_axis = GetMouseOverAxis(plot, active_faces, corners_pix, plane_2d, &hovered_edge_idx);
if (hovered_axis != -1) {
hovered_plane_idx = -1;
hovered_plane = -1;
}
// If the user is no longer pressing the translation/zoom buttons, set axes as not held
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseDown(ImGuiMouseButton_Middle)) {
for (int i = 0; i < 3; i++)
plot.Axes[i].Held = false;
}
// Reset held edge/plane indices (it will be set if mouse button is down)
if (!plot.Held) {
plot.HeldEdgeIdx = -1;
plot.HeldPlaneIdx = -1;
}
// Check which axes should be transformed (fit/zoom/translate)
bool any_axis_held = plot.Axes[0].Held || plot.Axes[1].Held || plot.Axes[2].Held;
static bool transform_axis[3] = {false, false, false};
if (!any_axis_held) {
// Only update the transformation axes if the user is not already performing a transformation
transform_axis[0] = transform_axis[1] = transform_axis[2] = false;
if (hovered_axis != -1) {
transform_axis[hovered_axis] = true;
} else if (hovered_plane != -1) {
transform_axis[(hovered_plane + 1) % 3] = true;
transform_axis[(hovered_plane + 2) % 3] = true;
} else {
transform_axis[0] = transform_axis[1] = transform_axis[2] = true;
}
}
// Handle translation/zoom fit with double click
if (plot_clicked && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
plot.FitThisFrame = true;
for (int i = 0; i < 3; i++)
plot.Axes[i].FitThisFrame = transform_axis[i];
}
// Handle auto fit
for (int i = 0; i < 3; i++)
if (plot.Axes[i].IsAutoFitting()) {
plot.FitThisFrame = true;
plot.Axes[i].FitThisFrame = true;
}
// Handle translation with right mouse button
if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y);
if (transform_axis[0] && transform_axis[1] && transform_axis[2]) {
// Perform unconstrained translation (translate on the viewer plane)
// Compute delta_pixels in 3D (invert y-axis)
ImPlot3DPoint delta_pixels(delta.x, -delta.y, 0.0f);
// Convert delta to NDC space
float zoom = ImMin(plot.PlotRect.GetWidth(), plot.PlotRect.GetHeight()) / 1.8f;
ImPlot3DPoint delta_NDC = plot.Rotation.Inverse() * (delta_pixels / zoom);
// Convert delta to plot space
ImPlot3DPoint delta_plot = delta_NDC * (plot.RangeMax() - plot.RangeMin());
// Adjust plot range to translate the plot
for (int i = 0; i < 3; i++) {
if (transform_axis[i]) {
plot.Axes[i].SetRange(plot.Axes[i].Range.Min - delta_plot[i], plot.Axes[i].Range.Max - delta_plot[i]);
plot.Axes[i].Held = true;
}
// If no axis was held before (user started translating in this frame), set the held edge/plane indices
if (!any_axis_held) {
plot.HeldEdgeIdx = hovered_edge_idx;
plot.HeldPlaneIdx = hovered_plane_idx;
}
}
} else if (transform_axis[0] || transform_axis[1] || transform_axis[2]) {
// Translate along plane/axis
// Mouse delta in pixels
ImVec2 mouse_pos = ImGui::GetMousePos();
ImVec2 mouse_delta(IO.MouseDelta.x, IO.MouseDelta.y);
// TODO Choose best plane given transform_axis and current view
// For now it crashes when transforming only one axis in the 2D view
ImPlane3D plane = ImPlane3D_XY;
if (transform_axis[1] && transform_axis[2])
plane = ImPlane3D_YZ;
else if (transform_axis[0] && transform_axis[2])
plane = ImPlane3D_XZ;
else if (transform_axis[2])
plane = ImPlane3D_YZ;
ImPlot3DPoint mouse_plot = PixelsToPlotPlane(mouse_pos, plane, false);
ImPlot3DPoint mouse_delta_plot = PixelsToPlotPlane(mouse_pos + mouse_delta, plane, false);
ImPlot3DPoint delta_plot = mouse_delta_plot - mouse_plot;
// Apply translation to the selected axes
for (int i = 0; i < 3; i++) {
if (transform_axis[i]) {
plot.Axes[i].SetRange(plot.Axes[i].Range.Min - delta_plot[i],
plot.Axes[i].Range.Max - delta_plot[i]);
plot.Axes[i].Held = true;
}
if (!any_axis_held) {
plot.HeldEdgeIdx = hovered_edge_idx;
plot.HeldPlaneIdx = hovered_plane_idx;
}
}
}
}
// Handle context click with right mouse button
if (plot.Held && ImGui::IsMouseClicked(ImGuiMouseButton_Right))
plot.ContextClick = true;
if (rotating || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right))
plot.ContextClick = false;
// Handle reset rotation with left mouse double click
if (plot.Held && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
plot.RotationAnimationEnd = plot.Rotation;
// Calculate rotation to align the z-axis with the camera direction
if (hovered_plane == -1) {
plot.RotationAnimationEnd = init_rotation;
} else {
// Compute plane normal
ImPlot3DPoint axis_normal = ImPlot3DPoint(0.0f, 0.0f, 0.0f);
axis_normal[hovered_plane] = active_faces[hovered_plane] ? -1.0f : 1.0f;
// Compute rotation to align the plane normal with the z-axis
ImPlot3DQuat align_normal = ImPlot3DQuat::FromTwoVectors(plot.RotationAnimationEnd * axis_normal, ImPlot3DPoint(0.0f, 0.0f, 1.0f));
plot.RotationAnimationEnd = align_normal * plot.RotationAnimationEnd;
if (hovered_plane != 2) {
// Compute rotation to point z-axis up
ImPlot3DQuat align_up = ImPlot3DQuat::FromTwoVectors(plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, 0.0f, 1.0f), ImPlot3DPoint(0.0f, 1.0f, 0.0f));
plot.RotationAnimationEnd = align_up * plot.RotationAnimationEnd;
} else {
// Find the axis most aligned with the up direction
ImPlot3DPoint up(0.0f, 1.0f, 0.0f);
ImPlot3DPoint x_axis = plot.RotationAnimationEnd * ImPlot3DPoint(1.0f, 0.0f, 0.0f);
ImPlot3DPoint y_axis = plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, 1.0f, 0.0f);
ImPlot3DPoint neg_x_axis = plot.RotationAnimationEnd * ImPlot3DPoint(-1.0f, 0.0f, 0.0f);
ImPlot3DPoint neg_y_axis = plot.RotationAnimationEnd * ImPlot3DPoint(0.0f, -1.0f, 0.0f);
struct AxisAlignment {
ImPlot3DPoint axis;
float dot;
};
AxisAlignment candidates[] = {
{x_axis, x_axis.Dot(up)},
{y_axis, y_axis.Dot(up)},
{neg_x_axis, neg_x_axis.Dot(up)},
{neg_y_axis, neg_y_axis.Dot(up)},
};
// Find the candidate with the maximum dot product
AxisAlignment* best_candidate = &candidates[0];
for (int i = 1; i < 4; ++i) {
if (candidates[i].dot > best_candidate->dot) {
best_candidate = &candidates[i];
}
}
// Compute the rotation to align the best candidate with the up direction
ImPlot3DQuat align_up = ImPlot3DQuat::FromTwoVectors(best_candidate->axis, up);
plot.RotationAnimationEnd = align_up * plot.RotationAnimationEnd;
}
}
// Compute the angular distance between current and target rotation
float dot_product = ImClamp(plot.Rotation.Dot(plot.RotationAnimationEnd), -1.0f, 1.0f);
float angle = 2.0f * acosf(fabsf(dot_product));
// Calculate animation time for constant the angular velocity
plot.AnimationTime = angle / ANIMATION_ANGULAR_VELOCITY;
}
// Handle rotation with left mouse dragging
if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y);
// Map delta to rotation angles (in radians)
float angle_x = delta.x * (3.1415f / 180.0f);
float angle_y = delta.y * (3.1415f / 180.0f);
// Create quaternions for the rotations
ImPlot3DQuat quat_x(angle_y, ImPlot3DPoint(1.0f, 0.0f, 0.0f));
ImPlot3DQuat quat_z(angle_x, ImPlot3DPoint(0.0f, 0.0f, 1.0f));
// Combine the new rotations with the current rotation
plot.Rotation = quat_x * plot.Rotation * quat_z;
plot.Rotation.Normalize();
}
// Handle zoom with mouse wheel
if (plot.Hovered && (ImGui::IsMouseDown(ImGuiMouseButton_Middle) || IO.MouseWheel != 0)) {
float delta = ImGui::IsMouseDown(ImGuiMouseButton_Middle) ? (-0.01f * IO.MouseDelta.y) : (-0.1f * IO.MouseWheel);
float zoom = 1.0f + delta;
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
float center = (axis.Range.Min + axis.Range.Max) * 0.5f;
float size = axis.Range.Max - axis.Range.Min;
size *= zoom;
if (transform_axis[i]) {
plot.Axes[i].SetRange(center - size * 0.5f, center + size * 0.5f);
plot.Axes[i].Held = true;
}
// If no axis was held before (user started zoom in this frame), set the held edge/plane indices
if (!any_axis_held) {
plot.HeldEdgeIdx = hovered_edge_idx;
plot.HeldPlaneIdx = hovered_plane_idx;
}
}
}
// Handle context menu (should not happen if it is not a double click action)
bool not_double_click = (float)(ImGui::GetTime() - IO.MouseClickedTime[ImGuiMouseButton_Right]) > IO.MouseDoubleClickTime;
if (plot.Hovered && plot.ContextClick && not_double_click && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
plot.ContextClick = false;
plot.OpenContextThisFrame = true;
}
// TODO Only open context menu if the mouse is not in the middle of double click action
const char* axis_contexts[3] = {"##XAxisContext", "##YAxisContext", "##ZAxisContext"};
if (plot.OpenContextThisFrame) {
if (plot.Items.Legend.Hovered)
ImGui::OpenPopup("##LegendContext");
else if (hovered_axis != -1) {
ImGui::OpenPopup(axis_contexts[hovered_axis]);
} else if (hovered_plane != -1) {
ImGui::OpenPopup(axis_contexts[hovered_plane]);
} else if (plot.Hovered) {
ImGui::OpenPopup("##PlotContext");
}
}
}
void SetupLock() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetupLock() needs to be called between BeginPlot() and EndPlot()!");
ImPlot3DPlot& plot = *gp.CurrentPlot;
if (plot.SetupLocked)
return;
// Lock setup
plot.SetupLocked = true;
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImDrawList* draw_list = window->DrawList;
ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
// Set default formatter/locator
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
// Set formatter
if (axis.Formatter == nullptr) {
axis.Formatter = Formatter_Default;
if (axis.FormatterData == nullptr)
axis.FormatterData = (void*)IMPLOT3D_LABEL_FORMAT;
}
// Set locator
if (axis.Locator == nullptr)
axis.Locator = Locator_Default;
}
// Draw frame background
ImU32 f_bg_color = GetStyleColorU32(ImPlot3DCol_FrameBg);
draw_list->AddRectFilled(plot.FrameRect.Min, plot.FrameRect.Max, f_bg_color);
// Compute canvas/canvas rectangle
plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding);
plot.PlotRect = plot.CanvasRect;
// Compute ticks
for (int i = 0; i < 3; i++) {
ImPlot3DAxis& axis = plot.Axes[i];
axis.Ticker.Reset();
axis.Locator(axis.Ticker, axis.Range, axis.Formatter, axis.FormatterData);
}
// Render title
if (plot.HasTitle()) {
ImU32 col = GetStyleColorU32(ImPlot3DCol_TitleText);
ImVec2 top_center = ImVec2(plot.FrameRect.GetCenter().x, plot.CanvasRect.Min.y);
AddTextCentered(draw_list, top_center, col, plot.GetTitle());
plot.PlotRect.Min.y += ImGui::GetTextLineHeight() + gp.Style.LabelPadding.y;
}
// Handle animation
if (plot.AnimationTime > 0.0f) {
float dt = ImGui::GetIO().DeltaTime;
float t = ImClamp(dt / plot.AnimationTime, 0.0f, 1.0f);
plot.AnimationTime -= dt;
if (plot.AnimationTime < 0.0f)
plot.AnimationTime = 0.0f;
plot.Rotation = ImPlot3DQuat::Slerp(plot.Rotation, plot.RotationAnimationEnd, t);
}
plot.Initialized = true;
// Handle user input
HandleInput(plot);
// Render plot box
RenderPlotBox(draw_list, plot);
ImGui::PopClipRect();
}
//-----------------------------------------------------------------------------
// [SECTION] Miscellaneous
//-----------------------------------------------------------------------------
ImDrawList* GetPlotDrawList() {
return ImGui::GetWindowDrawList();
}
//-----------------------------------------------------------------------------
// [SECTION] Styles
//-----------------------------------------------------------------------------
struct ImPlot3DStyleVarInfo {
ImGuiDataType Type;
ImU32 Count;
ImU32 Offset;
void* GetVarPtr(ImPlot3DStyle* style) const { return (void*)((unsigned char*)style + Offset); }
};
static const ImPlot3DStyleVarInfo GPlot3DStyleVarInfo[] =
{
// Item style
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LineWeight)}, // ImPlot3DStyleVar_LineWeight
{ImGuiDataType_S32, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, Marker)}, // ImPlot3DStyleVar_Marker
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, MarkerSize)}, // ImPlot3DStyleVar_MarkerSize
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, MarkerWeight)}, // ImPlot3DStyleVar_MarkerWeight
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImPlot3DStyle, FillAlpha)}, // ImPlot3DStyleVar_FillAlpha
// Plot style
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotDefaultSize)}, // ImPlot3DStyleVar_Plot3DDefaultSize
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotMinSize)}, // ImPlot3DStyleVar_Plot3DMinSize
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, PlotPadding)}, // ImPlot3DStyleVar_Plot3DPadding
// Label style
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LabelPadding)}, // ImPlot3DStyleVar_LabelPaddine
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendPadding)}, // ImPlot3DStyleVar_LegendPadding
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendInnerPadding)}, // ImPlot3DStyleVar_LegendInnerPadding
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImPlot3DStyle, LegendSpacing)}, // ImPlot3DStyleVar_LegendSpacing
};
static const ImPlot3DStyleVarInfo* GetPlotStyleVarInfo(ImPlot3DStyleVar idx) {
IM_ASSERT(idx >= 0 && idx < ImPlot3DStyleVar_COUNT);
IM_ASSERT(IM_ARRAYSIZE(GPlot3DStyleVarInfo) == ImPlot3DStyleVar_COUNT);
return &GPlot3DStyleVarInfo[idx];
}
ImPlot3DStyle& GetStyle() { return GImPlot3D->Style; }
void StyleColorsAuto(ImPlot3DStyle* dst) {
ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
ImVec4* colors = style->Colors;
colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_TitleText] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_InlayText] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_FrameBg] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_PlotBg] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_PlotBorder] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_LegendBg] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_LegendBorder] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_LegendText] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_AxisText] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_AxisGrid] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
}
void StyleColorsDark(ImPlot3DStyle* dst) {
ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
ImVec4* colors = style->Colors;
colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_TitleText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_InlayText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
colors[ImPlot3DCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
colors[ImPlot3DCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImPlot3DCol_LegendBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImPlot3DCol_LegendBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImPlot3DCol_LegendText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_AxisText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);
colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
}
void StyleColorsLight(ImPlot3DStyle* dst) {
ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
ImVec4* colors = style->Colors;
colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImPlot3DCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImPlot3DCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f);
colors[ImPlot3DCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImPlot3DCol_LegendBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);
colors[ImPlot3DCol_LegendBorder] = ImVec4(0.82f, 0.82f, 0.82f, 0.80f);
colors[ImPlot3DCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImPlot3DCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImPlot3DCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
}
void StyleColorsClassic(ImPlot3DStyle* dst) {
ImPlot3DStyle* style = dst ? dst : &ImPlot3D::GetStyle();
ImVec4* colors = style->Colors;
colors[ImPlot3DCol_Line] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_Fill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerOutline] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_MarkerFill] = IMPLOT3D_AUTO_COL;
colors[ImPlot3DCol_TitleText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImPlot3DCol_InlayText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImPlot3DCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);
colors[ImPlot3DCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f);
colors[ImPlot3DCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
colors[ImPlot3DCol_LegendBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
colors[ImPlot3DCol_LegendBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
colors[ImPlot3DCol_LegendText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImPlot3DCol_AxisText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
colors[ImPlot3DCol_AxisGrid] = ImVec4(0.90f, 0.90f, 0.90f, 0.25f);
colors[ImPlot3DCol_AxisTick] = IMPLOT3D_AUTO_COL;
}
void PushStyleColor(ImPlot3DCol idx, ImU32 col) {
ImPlot3DContext& gp = *GImPlot3D;
ImGuiColorMod backup;
backup.Col = (ImGuiCol)idx;
backup.BackupValue = gp.Style.Colors[idx];
gp.ColorModifiers.push_back(backup);
gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col);
}
void PushStyleColor(ImPlot3DCol idx, const ImVec4& col) {
ImPlot3DContext& gp = *GImPlot3D;
ImGuiColorMod backup;
backup.Col = (ImGuiCol)idx;
backup.BackupValue = gp.Style.Colors[idx];
gp.ColorModifiers.push_back(backup);
gp.Style.Colors[idx] = col;
}
void PopStyleColor(int count) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!");
while (count > 0) {
ImGuiColorMod& backup = gp.ColorModifiers.back();
gp.Style.Colors[backup.Col] = backup.BackupValue;
gp.ColorModifiers.pop_back();
count--;
}
}
void PushStyleVar(ImPlot3DStyleVar idx, float val) {
ImPlot3DContext& gp = *GImPlot3D;
const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
*pvar = val;
return;
}
IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
}
void PushStyleVar(ImPlot3DStyleVar idx, int val) {
ImPlot3DContext& gp = *GImPlot3D;
const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) {
int* pvar = (int*)var_info->GetVarPtr(&gp.Style);
gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
*pvar = val;
return;
} else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {
float* pvar = (float*)var_info->GetVarPtr(&gp.Style);
gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
*pvar = (float)val;
return;
}
IM_ASSERT(0 && "Called PushStyleVar() int variant but variable is not a int!");
}
void PushStyleVar(ImPlot3DStyleVar idx, const ImVec2& val) {
ImPlot3DContext& gp = *GImPlot3D;
const ImPlot3DStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);
if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) {
ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style);
gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));
*pvar = val;
return;
}
IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
}
void PopStyleVar(int count) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!");
while (count > 0) {
ImGuiStyleMod& backup = gp.StyleModifiers.back();
const ImPlot3DStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx);
void* data = info->GetVarPtr(&gp.Style);
if (info->Type == ImGuiDataType_Float && info->Count == 1) {
((float*)data)[0] = backup.BackupFloat[0];
} else if (info->Type == ImGuiDataType_Float && info->Count == 2) {
((float*)data)[0] = backup.BackupFloat[0];
((float*)data)[1] = backup.BackupFloat[1];
} else if (info->Type == ImGuiDataType_S32 && info->Count == 1) {
((int*)data)[0] = backup.BackupInt[0];
}
gp.StyleModifiers.pop_back();
count--;
}
}
ImVec4 GetStyleColorVec4(ImPlot3DCol idx) {
return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot3D->Style.Colors[idx];
}
ImU32 GetStyleColorU32(ImPlot3DCol idx) {
return ImGui::ColorConvertFloat4ToU32(ImPlot3D::GetStyleColorVec4(idx));
}
//------------------------------------------------------------------------------
// [SECTION] Colormaps
//------------------------------------------------------------------------------
ImPlot3DColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!");
ImVector<ImU32> buffer;
buffer.resize(size);
for (int i = 0; i < size; ++i)
buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]);
return gp.ColormapData.Append(name, buffer.Data, size, qual);
}
ImPlot3DColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!");
IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!");
return gp.ColormapData.Append(name, colormap, size, qual);
}
int GetColormapCount() {
ImPlot3DContext& gp = *GImPlot3D;
return gp.ColormapData.Count;
}
const char* GetColormapName(ImPlot3DColormap colormap) {
ImPlot3DContext& gp = *GImPlot3D;
return gp.ColormapData.GetName(colormap);
}
ImPlot3DColormap GetColormapIndex(const char* name) {
ImPlot3DContext& gp = *GImPlot3D;
return gp.ColormapData.GetIndex(name);
}
void PushColormap(ImPlot3DColormap colormap) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!");
gp.ColormapModifiers.push_back(gp.Style.Colormap);
gp.Style.Colormap = colormap;
}
void PushColormap(const char* name) {
ImPlot3DContext& gp = *GImPlot3D;
ImPlot3DColormap idx = gp.ColormapData.GetIndex(name);
IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!");
PushColormap(idx);
}
void PopColormap(int count) {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!");
while (count > 0) {
const ImPlot3DColormap& backup = gp.ColormapModifiers.back();
gp.Style.Colormap = backup;
gp.ColormapModifiers.pop_back();
count--;
}
}
ImU32 NextColormapColorU32() {
ImPlot3DContext& gp = *GImPlot3D;
IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!");
int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap);
ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx);
gp.CurrentItems->ColormapIdx++;
return col;
}
ImVec4 NextColormapColor() {
return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32());
}
int GetColormapSize(ImPlot3DColormap cmap) {
ImPlot3DContext& gp = *GImPlot3D;
cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
return gp.ColormapData.GetKeyCount(cmap);
}
ImU32 GetColormapColorU32(int idx, ImPlot3DColormap cmap) {
ImPlot3DContext& gp = *GImPlot3D;
cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
idx = idx % gp.ColormapData.GetKeyCount(cmap);
return gp.ColormapData.GetKeyColor(cmap, idx);
}
ImVec4 GetColormapColor(int idx, ImPlot3DColormap cmap) {
return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx, cmap));
}
ImU32 SampleColormapU32(float t, ImPlot3DColormap cmap) {
ImPlot3DContext& gp = *GImPlot3D;
cmap = cmap == IMPLOT3D_AUTO ? gp.Style.Colormap : cmap;
IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!");
return gp.ColormapData.LerpTable(cmap, t);
}
ImVec4 SampleColormap(float t, ImPlot3DColormap cmap) {
return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t, cmap));
}
//-----------------------------------------------------------------------------
// [SECTION] Context Utils
//-----------------------------------------------------------------------------
#define IMPLOT3D_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name) / sizeof(ImU32), qual)
#define IM_RGB(r, g, b) IM_COL32(r, g, b, 255)
void InitializeContext(ImPlot3DContext* ctx) {
ResetContext(ctx);
const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396};
const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409};
const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986};
const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481};
const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301};
const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752};
const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295};
const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015};
const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295};
const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335};
const ImU32 Twilight[] = {IM_RGB(226, 217, 226), IM_RGB(166, 191, 202), IM_RGB(109, 144, 192), IM_RGB(95, 88, 176), IM_RGB(83, 30, 124), IM_RGB(47, 20, 54), IM_RGB(100, 25, 75), IM_RGB(159, 60, 80), IM_RGB(192, 117, 94), IM_RGB(208, 179, 158), IM_RGB(226, 217, 226)};
const ImU32 RdBu[] = {IM_RGB(103, 0, 31), IM_RGB(178, 24, 43), IM_RGB(214, 96, 77), IM_RGB(244, 165, 130), IM_RGB(253, 219, 199), IM_RGB(247, 247, 247), IM_RGB(209, 229, 240), IM_RGB(146, 197, 222), IM_RGB(67, 147, 195), IM_RGB(33, 102, 172), IM_RGB(5, 48, 97)};
const ImU32 BrBG[] = {IM_RGB(84, 48, 5), IM_RGB(140, 81, 10), IM_RGB(191, 129, 45), IM_RGB(223, 194, 125), IM_RGB(246, 232, 195), IM_RGB(245, 245, 245), IM_RGB(199, 234, 229), IM_RGB(128, 205, 193), IM_RGB(53, 151, 143), IM_RGB(1, 102, 94), IM_RGB(0, 60, 48)};
const ImU32 PiYG[] = {IM_RGB(142, 1, 82), IM_RGB(197, 27, 125), IM_RGB(222, 119, 174), IM_RGB(241, 182, 218), IM_RGB(253, 224, 239), IM_RGB(247, 247, 247), IM_RGB(230, 245, 208), IM_RGB(184, 225, 134), IM_RGB(127, 188, 65), IM_RGB(77, 146, 33), IM_RGB(39, 100, 25)};
const ImU32 Spectral[] = {IM_RGB(158, 1, 66), IM_RGB(213, 62, 79), IM_RGB(244, 109, 67), IM_RGB(253, 174, 97), IM_RGB(254, 224, 139), IM_RGB(255, 255, 191), IM_RGB(230, 245, 152), IM_RGB(171, 221, 164), IM_RGB(102, 194, 165), IM_RGB(50, 136, 189), IM_RGB(94, 79, 162)};
const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK};
IMPLOT3D_APPEND_CMAP(Deep, true);
IMPLOT3D_APPEND_CMAP(Dark, true);
IMPLOT3D_APPEND_CMAP(Pastel, true);
IMPLOT3D_APPEND_CMAP(Paired, true);
IMPLOT3D_APPEND_CMAP(Viridis, false);
IMPLOT3D_APPEND_CMAP(Plasma, false);
IMPLOT3D_APPEND_CMAP(Hot, false);
IMPLOT3D_APPEND_CMAP(Cool, false);
IMPLOT3D_APPEND_CMAP(Pink, false);
IMPLOT3D_APPEND_CMAP(Jet, false);
IMPLOT3D_APPEND_CMAP(Twilight, false);
IMPLOT3D_APPEND_CMAP(RdBu, false);
IMPLOT3D_APPEND_CMAP(BrBG, false);
IMPLOT3D_APPEND_CMAP(PiYG, false);
IMPLOT3D_APPEND_CMAP(Spectral, false);
IMPLOT3D_APPEND_CMAP(Greys, false);
}
void ResetContext(ImPlot3DContext* ctx) {
ctx->Plots.Clear();
ctx->CurrentPlot = nullptr;
ctx->CurrentItems = nullptr;
ctx->NextItemData.Reset();
ctx->Style = ImPlot3DStyle();
}
//-----------------------------------------------------------------------------
// [SECTION] Style Utils
//-----------------------------------------------------------------------------
bool IsColorAuto(const ImVec4& col) {
return col.w == -1.0f;
}
bool IsColorAuto(ImPlot3DCol idx) {
return IsColorAuto(GImPlot3D->Style.Colors[idx]);
}
ImVec4 GetAutoColor(ImPlot3DCol idx) {
switch (idx) {
case ImPlot3DCol_Line: return IMPLOT3D_AUTO_COL; // Plot dependent
case ImPlot3DCol_Fill: return IMPLOT3D_AUTO_COL; // Plot dependent
case ImPlot3DCol_MarkerOutline: return IMPLOT3D_AUTO_COL; // Plot dependent
case ImPlot3DCol_MarkerFill: return IMPLOT3D_AUTO_COL; // Plot dependent
case ImPlot3DCol_TitleText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
case ImPlot3DCol_InlayText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
case ImPlot3DCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);
case ImPlot3DCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
case ImPlot3DCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border);
case ImPlot3DCol_LegendBg: return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg);
case ImPlot3DCol_LegendBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border);
case ImPlot3DCol_LegendText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
case ImPlot3DCol_AxisText: return ImGui::GetStyleColorVec4(ImGuiCol_Text);
case ImPlot3DCol_AxisGrid: return ImGui::GetStyleColorVec4(ImGuiCol_Text) * ImVec4(1, 1, 1, 0.25f);
case ImPlot3DCol_AxisTick: return GetStyleColorVec4(ImPlot3DCol_AxisGrid);
default: return IMPLOT3D_AUTO_COL;
}
}
const char* GetStyleColorName(ImPlot3DCol idx) {
static const char* color_names[ImPlot3DCol_COUNT] = {
"Line",
"Fill",
"MarkerOutline",
"MarkerFill",
"TitleText",
"InlayText",
"FrameBg",
"PlotBg",
"PlotBorder",
"LegendBg",
"LegendBorder",
"LegendText",
"AxisText",
"AxisGrid",
"AxisTick",
};
return color_names[idx];
}
const ImPlot3DNextItemData& GetItemData() { return GImPlot3D->NextItemData; }
} // namespace ImPlot3D
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DPoint
//-----------------------------------------------------------------------------
ImPlot3DPoint ImPlot3DPoint::operator*(float rhs) const { return ImPlot3DPoint(x * rhs, y * rhs, z * rhs); }
ImPlot3DPoint ImPlot3DPoint::operator/(float rhs) const { return ImPlot3DPoint(x / rhs, y / rhs, z / rhs); }
ImPlot3DPoint ImPlot3DPoint::operator+(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x + rhs.x, y + rhs.y, z + rhs.z); }
ImPlot3DPoint ImPlot3DPoint::operator-(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x - rhs.x, y - rhs.y, z - rhs.z); }
ImPlot3DPoint ImPlot3DPoint::operator*(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x * rhs.x, y * rhs.y, z * rhs.z); }
ImPlot3DPoint ImPlot3DPoint::operator/(const ImPlot3DPoint& rhs) const { return ImPlot3DPoint(x / rhs.x, y / rhs.y, z / rhs.z); }
ImPlot3DPoint ImPlot3DPoint::operator-() const { return ImPlot3DPoint(-x, -y, -z); }
ImPlot3DPoint& ImPlot3DPoint::operator*=(float rhs) {
x *= rhs;
y *= rhs;
z *= rhs;
return *this;
}
ImPlot3DPoint& ImPlot3DPoint::operator/=(float rhs) {
x /= rhs;
y /= rhs;
z /= rhs;
return *this;
}
ImPlot3DPoint& ImPlot3DPoint::operator+=(const ImPlot3DPoint& rhs) {
x += rhs.x;
y += rhs.y;
z += rhs.z;
return *this;
}
ImPlot3DPoint& ImPlot3DPoint::operator-=(const ImPlot3DPoint& rhs) {
x -= rhs.x;
y -= rhs.y;
z -= rhs.z;
return *this;
}
ImPlot3DPoint& ImPlot3DPoint::operator*=(const ImPlot3DPoint& rhs) {
x *= rhs.x;
y *= rhs.y;
z *= rhs.z;
return *this;
}
ImPlot3DPoint& ImPlot3DPoint::operator/=(const ImPlot3DPoint& rhs) {
x /= rhs.x;
y /= rhs.y;
z /= rhs.z;
return *this;
}
bool ImPlot3DPoint::operator==(const ImPlot3DPoint& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; }
bool ImPlot3DPoint::operator!=(const ImPlot3DPoint& rhs) const { return !(*this == rhs); }
float ImPlot3DPoint::Dot(const ImPlot3DPoint& rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; }
ImPlot3DPoint ImPlot3DPoint::Cross(const ImPlot3DPoint& rhs) const {
return ImPlot3DPoint(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, x * rhs.y - y * rhs.x);
}
float ImPlot3DPoint::Length() const { return ImSqrt(x * x + y * y + z * z); }
float ImPlot3DPoint::LengthSquared() const { return x * x + y * y + z * z; }
void ImPlot3DPoint::Normalize() {
float l = Length();
x /= l;
y /= l;
z /= l;
}
ImPlot3DPoint ImPlot3DPoint::Normalized() const {
float l = Length();
return ImPlot3DPoint(x / l, y / l, z / l);
}
ImPlot3DPoint operator*(float lhs, const ImPlot3DPoint& rhs) {
return ImPlot3DPoint(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
}
bool ImPlot3DPoint::IsNaN() const {
return ImPlot3D::ImNan(x) || ImPlot3D::ImNan(y) || ImPlot3D::ImNan(z);
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DBox
//-----------------------------------------------------------------------------
void ImPlot3DBox::Expand(const ImPlot3DPoint& point) {
Min.x = ImMin(Min.x, point.x);
Min.y = ImMin(Min.y, point.y);
Min.z = ImMin(Min.z, point.z);
Max.x = ImMax(Max.x, point.x);
Max.y = ImMax(Max.y, point.y);
Max.z = ImMax(Max.z, point.z);
}
bool ImPlot3DBox::Contains(const ImPlot3DPoint& point) const {
return (point.x >= Min.x && point.x <= Max.x) &&
(point.y >= Min.y && point.y <= Max.y) &&
(point.z >= Min.z && point.z <= Max.z);
}
bool ImPlot3DBox::ClipLineSegment(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, ImPlot3DPoint& p0_clipped, ImPlot3DPoint& p1_clipped) const {
// Check if the line segment is completely inside the box
if (Contains(p0) && Contains(p1)) {
p0_clipped = p0;
p1_clipped = p1;
return true;
}
// Perform Liang-Barsky 3D clipping
double t0 = 0.0;
double t1 = 1.0;
ImPlot3DPoint d = p1 - p0;
// Define the clipping boundaries
const double xmin = Min.x, xmax = Max.x;
const double ymin = Min.y, ymax = Max.y;
const double zmin = Min.z, zmax = Max.z;
// Lambda function to update t0 and t1
auto update = [&](double p, double q) -> bool {
if (p == 0.0) {
if (q < 0.0)
return false; // Line is parallel and outside the boundary
else
return true; // Line is parallel and inside or coincident with boundary
}
double r = q / p;
if (p < 0.0) {
if (r > t1)
return false; // Line is outside
if (r > t0)
t0 = r; // Move up t0
} else {
if (r < t0)
return false; // Line is outside
if (r < t1)
t1 = r; // Move down t1
}
return true;
};
// Clip against each boundary
if (!update(-d.x, p0.x - xmin))
return false; // Left
if (!update(d.x, xmax - p0.x))
return false; // Right
if (!update(-d.y, p0.y - ymin))
return false; // Bottom
if (!update(d.y, ymax - p0.y))
return false; // Top
if (!update(-d.z, p0.z - zmin))
return false; // Near
if (!update(d.z, zmax - p0.z))
return false; // Far
// Compute clipped points
p0_clipped = p0 + d * t0;
p1_clipped = p0 + d * t1;
return true;
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DRange
//-----------------------------------------------------------------------------
void ImPlot3DRange::Expand(float value) {
Min = ImMin(Min, value);
Max = ImMax(Max, value);
}
bool ImPlot3DRange::Contains(float value) const {
return value >= Min && value <= Max;
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DQuat
//-----------------------------------------------------------------------------
ImPlot3DQuat::ImPlot3DQuat(float _angle, const ImPlot3DPoint& _axis) {
float half_angle = _angle * 0.5f;
float s = std::sin(half_angle);
x = s * _axis.x;
y = s * _axis.y;
z = s * _axis.z;
w = std::cos(half_angle);
}
ImPlot3DQuat ImPlot3DQuat::FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3DPoint& v1) {
ImPlot3DQuat q;
// Compute the dot product and lengths of the vectors
float dot = v0.Dot(v1);
float length_v0 = v0.Length();
float length_v1 = v1.Length();
// Normalize the dot product
float normalized_dot = dot / (length_v0 * length_v1);
// Handle edge cases: if vectors are very close or identical
const float epsilon = 1e-6f;
if (std::fabs(normalized_dot - 1.0f) < epsilon) {
// v0 and v1 are nearly identical; return an identity quaternion
q.x = 0.0f;
q.y = 0.0f;
q.z = 0.0f;
q.w = 1.0f;
return q;
}
// Handle edge case: if vectors are opposite
if (std::fabs(normalized_dot + 1.0f) < epsilon) {
// v0 and v1 are opposite; choose an arbitrary orthogonal axis
ImPlot3DPoint arbitrary_axis = std::fabs(v0.x) > std::fabs(v0.z) ? ImPlot3DPoint(-v0.y, v0.x, 0.0f)
: ImPlot3DPoint(0.0f, -v0.z, v0.y);
arbitrary_axis.Normalize();
q.x = arbitrary_axis.x;
q.y = arbitrary_axis.y;
q.z = arbitrary_axis.z;
q.w = 0.0f;
return q;
}
// General case
ImPlot3DPoint axis = v0.Cross(v1);
axis.Normalize();
float angle = std::acos(normalized_dot);
float half_angle = angle * 0.5f;
float s = std::sin(half_angle);
q.x = s * axis.x;
q.y = s * axis.y;
q.z = s * axis.z;
q.w = std::cos(half_angle);
return q;
}
float ImPlot3DQuat::Length() const {
return std::sqrt(x * x + y * y + z * z + w * w);
}
ImPlot3DQuat ImPlot3DQuat::Normalized() const {
float l = Length();
return ImPlot3DQuat(x / l, y / l, z / l, w / l);
}
ImPlot3DQuat ImPlot3DQuat::Conjugate() const {
return ImPlot3DQuat(-x, -y, -z, w);
}
ImPlot3DQuat ImPlot3DQuat::Inverse() const {
float l_squared = x * x + y * y + z * z + w * w;
return ImPlot3DQuat(-x / l_squared, -y / l_squared, -z / l_squared, w / l_squared);
}
ImPlot3DQuat ImPlot3DQuat::operator*(const ImPlot3DQuat& rhs) const {
return ImPlot3DQuat(
w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y,
w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x,
w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w,
w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z);
}
ImPlot3DQuat& ImPlot3DQuat::Normalize() {
float l = Length();
x /= l;
y /= l;
z /= l;
w /= l;
return *this;
}
ImPlot3DPoint ImPlot3DQuat::operator*(const ImPlot3DPoint& point) const {
// Extract vector part of the quaternion
ImPlot3DPoint qv(x, y, z);
// Compute the cross products needed for rotation
ImPlot3DPoint uv = qv.Cross(point); // uv = qv x point
ImPlot3DPoint uuv = qv.Cross(uv); // uuv = qv x uv
// Compute the rotated vector
return point + (uv * w * 2.0f) + (uuv * 2.0f);
}
bool ImPlot3DQuat::operator==(const ImPlot3DQuat& rhs) const {
return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w;
}
bool ImPlot3DQuat::operator!=(const ImPlot3DQuat& rhs) const {
return !(*this == rhs);
}
ImPlot3DQuat ImPlot3DQuat::Slerp(const ImPlot3DQuat& q1, const ImPlot3DQuat& q2, float t) {
// Clamp t to [0, 1]
t = ImClamp(t, 0.0f, 1.0f);
// Compute the dot product (cosine of the angle between quaternions)
float dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w;
// If the dot product is negative, negate one quaternion to take the shorter path
ImPlot3DQuat q2_ = q2;
if (dot < 0.0f) {
q2_ = ImPlot3DQuat(-q2.x, -q2.y, -q2.z, -q2.w);
dot = -dot;
}
// If the quaternions are very close, use linear interpolation to avoid numerical instability
if (dot > 0.9995f) {
return ImPlot3DQuat(
q1.x + t * (q2_.x - q1.x),
q1.y + t * (q2_.y - q1.y),
q1.z + t * (q2_.z - q1.z),
q1.w + t * (q2_.w - q1.w))
.Normalized();
}
// Compute the angle and the interpolation factors
float theta_0 = std::acos(dot); // Angle between input quaternions
float theta = theta_0 * t; // Interpolated angle
float sin_theta = std::sin(theta); // Sine of interpolated angle
float sin_theta_0 = std::sin(theta_0); // Sine of original angle
float s1 = std::cos(theta) - dot * sin_theta / sin_theta_0;
float s2 = sin_theta / sin_theta_0;
// Interpolate and return the result
return ImPlot3DQuat(
s1 * q1.x + s2 * q2_.x,
s1 * q1.y + s2 * q2_.y,
s1 * q1.z + s2 * q2_.z,
s1 * q1.w + s2 * q2_.w);
}
float ImPlot3DQuat::Dot(const ImPlot3DQuat& rhs) const {
return x * rhs.x + y * rhs.y + z * rhs.z + w * rhs.w;
}
//-----------------------------------------------------------------------------
// [SECTION] ImDrawList3D
//-----------------------------------------------------------------------------
void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
int vtx_buffer_old_size = VtxBuffer.Size;
VtxBuffer.resize(vtx_buffer_old_size + vtx_count);
_VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size;
int idx_buffer_old_size = IdxBuffer.Size;
IdxBuffer.resize(idx_buffer_old_size + idx_count);
_IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size;
int z_buffer_old_size = ZBuffer.Size;
ZBuffer.resize(z_buffer_old_size + idx_count / 3);
_ZWritePtr = ZBuffer.Data + z_buffer_old_size;
}
void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count) {
IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0);
VtxBuffer.shrink(VtxBuffer.Size - vtx_count);
IdxBuffer.shrink(IdxBuffer.Size - idx_count);
ZBuffer.shrink(ZBuffer.Size - idx_count / 3);
}
void ImDrawList3D::SortedMoveToImGuiDrawList() {
ImDrawList& draw_list = *ImGui::GetWindowDrawList();
const int tri_count = ZBuffer.Size;
if (tri_count == 0) {
// No triangles, just clear and return
VtxBuffer.clear();
IdxBuffer.clear();
ZBuffer.clear();
_VtxCurrentIdx = 0;
_VtxWritePtr = VtxBuffer.Data;
_IdxWritePtr = IdxBuffer.Data;
_ZWritePtr = ZBuffer.Data;
return;
}
// Build an array of (z, tri_idx)
struct TriRef {
float z;
int tri_idx;
};
TriRef* tris = (TriRef*)IM_ALLOC(sizeof(TriRef) * tri_count);
for (int i = 0; i < tri_count; i++) {
tris[i].z = ZBuffer[i];
tris[i].tri_idx = i;
}
// Sort by z (distance from viewer)
ImQsort(tris, (size_t)tri_count, sizeof(TriRef),
[](const void* a, const void* b) {
float za = ((const TriRef*)a)->z;
float zb = ((const TriRef*)b)->z;
return (za < zb) ? -1 : (za > zb) ? 1
: 0;
});
// Reserve space in the ImGui draw list
draw_list.PrimReserve(IdxBuffer.Size, VtxBuffer.Size);
// Copy vertices (no reordering needed)
memcpy(draw_list._VtxWritePtr, VtxBuffer.Data, VtxBuffer.Size * sizeof(ImDrawVert));
unsigned int idx_offset = draw_list._VtxCurrentIdx;
draw_list._VtxWritePtr += VtxBuffer.Size;
draw_list._VtxCurrentIdx += (unsigned int)VtxBuffer.Size;
// Maximum index allowed to not overflow ImDrawIdx
unsigned int max_index_allowed = MaxIdx() - idx_offset;
// Copy indices with triangle sorting based on distance from viewer
ImDrawIdx* idx_out = draw_list._IdxWritePtr;
ImDrawIdx* idx_in = IdxBuffer.Data;
int triangles_added = 0;
for (int i = 0; i < tri_count; i++) {
int tri_i = tris[i].tri_idx;
int base_idx = tri_i * 3;
unsigned int i0 = (unsigned int)idx_in[base_idx + 0];
unsigned int i1 = (unsigned int)idx_in[base_idx + 1];
unsigned int i2 = (unsigned int)idx_in[base_idx + 2];
// Check if after adding offset any of these indices exceed max_index_allowed
if (i0 > max_index_allowed || i1 > max_index_allowed || i2 > max_index_allowed)
break;
idx_out[0] = (ImDrawIdx)(i0 + idx_offset);
idx_out[1] = (ImDrawIdx)(i1 + idx_offset);
idx_out[2] = (ImDrawIdx)(i2 + idx_offset);
idx_out += 3;
triangles_added++;
}
draw_list._IdxWritePtr = idx_out;
// Clear local buffers since we've moved them
VtxBuffer.clear();
IdxBuffer.clear();
ZBuffer.clear();
_VtxCurrentIdx = 0;
_VtxWritePtr = VtxBuffer.Data;
_IdxWritePtr = IdxBuffer.Data;
_ZWritePtr = ZBuffer.Data;
IM_FREE(tris);
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DAxis
//-----------------------------------------------------------------------------
bool ImPlot3DAxis::HasLabel() const { return !Label.empty() && !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoLabel); }
bool ImPlot3DAxis::HasGridLines() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoGridLines); }
bool ImPlot3DAxis::HasTickLabels() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoTickLabels); }
bool ImPlot3DAxis::HasTickMarks() const { return !ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_NoTickMarks); }
bool ImPlot3DAxis::IsAutoFitting() const { return ImPlot3D::ImHasFlag(Flags, ImPlot3DAxisFlags_AutoFit); }
void ImPlot3DAxis::ExtendFit(float value) {
FitExtents.Min = ImMin(FitExtents.Min, value);
FitExtents.Max = ImMax(FitExtents.Max, value);
}
void ImPlot3DAxis::ApplyFit() {
if (!IsLockedMin() && !ImPlot3D::ImNanOrInf(FitExtents.Min))
Range.Min = FitExtents.Min;
if (!IsLockedMax() && !ImPlot3D::ImNanOrInf(FitExtents.Max))
Range.Max = FitExtents.Max;
if (ImPlot3D::ImAlmostEqual(Range.Min, Range.Max)) {
Range.Max += 0.5;
Range.Min -= 0.5;
}
FitExtents.Min = HUGE_VAL;
FitExtents.Max = -HUGE_VAL;
}
float ImPlot3DAxis::PlotToNDC(float value) const {
return (value - Range.Min) / (Range.Max - Range.Min) - 0.5f;
}
float ImPlot3DAxis::NDCToPlot(float value) const {
return Range.Min + (value + 0.5f) * (Range.Max - Range.Min);
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DPlot
//-----------------------------------------------------------------------------
void ImPlot3DPlot::ExtendFit(const ImPlot3DPoint& point) {
for (int i = 0; i < 3; i++) {
if (!ImPlot3D::ImNanOrInf(point[i]) && Axes[i].FitThisFrame)
Axes[i].ExtendFit(point[i]);
}
}
ImPlot3DPoint ImPlot3DPlot::RangeMin() const {
return ImPlot3DPoint(Axes[0].Range.Min, Axes[1].Range.Min, Axes[2].Range.Min);
}
ImPlot3DPoint ImPlot3DPlot::RangeMax() const {
return ImPlot3DPoint(Axes[0].Range.Max, Axes[1].Range.Max, Axes[2].Range.Max);
}
ImPlot3DPoint ImPlot3DPlot::RangeCenter() const {
return ImPlot3DPoint(
(Axes[0].Range.Min + Axes[0].Range.Max) * 0.5f,
(Axes[1].Range.Min + Axes[1].Range.Max) * 0.5f,
(Axes[2].Range.Min + Axes[2].Range.Max) * 0.5f);
}
void ImPlot3DPlot::SetRange(const ImPlot3DPoint& min, const ImPlot3DPoint& max) {
Axes[0].SetRange(min.x, max.x);
Axes[1].SetRange(min.y, max.y);
Axes[2].SetRange(min.z, max.z);
}
//-----------------------------------------------------------------------------
// [SECTION] ImPlot3DStyle
//-----------------------------------------------------------------------------
ImPlot3DStyle::ImPlot3DStyle() {
// Item style
LineWeight = 1.0f;
Marker = ImPlot3DMarker_None;
MarkerSize = 4.0f;
MarkerWeight = 1.0f;
FillAlpha = 1.0f;
// Plot style
PlotDefaultSize = ImVec2(400, 400);
PlotMinSize = ImVec2(200, 200);
PlotPadding = ImVec2(10, 10);
LabelPadding = ImVec2(5, 5);
// Legend style
LegendPadding = ImVec2(10, 10);
LegendInnerPadding = ImVec2(5, 5);
LegendSpacing = ImVec2(5, 0);
// Colors
ImPlot3D::StyleColorsAuto(this);
Colormap = ImPlot3DColormap_Deep;
};
#endif // #ifndef IMGUI_DISABLE