Skip to content

Commit 4dcaf03

Browse files
authored
merge: pull request #61 from brenocq/setup-api
Setup rotation API
2 parents 661bd21 + 4395b88 commit 4dcaf03

File tree

4 files changed

+152
-25
lines changed

4 files changed

+152
-25
lines changed

implot3d.cpp

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ namespace ImPlot3D {
7676
ImPlot3DContext* GImPlot3D = nullptr;
7777
#endif
7878

79-
static ImPlot3DQuat init_rotation = ImPlot3DQuat(-0.513269f, -0.212596f, -0.318184f, 0.76819f);
80-
8179
ImPlot3DContext* CreateContext() {
8280
ImPlot3DContext* ctx = IM_NEW(ImPlot3DContext)();
8381
if (GImPlot3D == nullptr)
@@ -893,7 +891,7 @@ void ComputeActiveFaces(bool* active_faces, const ImPlot3DQuat& rotation, const
893891
};
894892

895893
int num_deg = 0; // Check number of planes that are degenerate (seen as a line)
896-
for (int i = 0; i < 3; ++i) {
894+
for (int i = 0; i < 3; i++) {
897895
// Determine the active face based on the Z component
898896
if (fabs(rot_face_n[i].z) < 0.025) {
899897
// If aligned with the plane, choose the min face for bottom/left
@@ -1106,7 +1104,7 @@ void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float p
11061104
}
11071105
ticker.AddTick(major, true, true, formatter, formatter_data);
11081106
}
1109-
for (int i = 1; i < nMinor; ++i) {
1107+
for (int i = 1; i < nMinor; i++) {
11101108
double minor = major + i * interval / nMinor;
11111109
if (range.Contains((float)minor)) {
11121110
ticker.AddTick(minor, false, true, formatter, formatter_data);
@@ -1124,7 +1122,7 @@ void Locator_Default(ImPlot3DTicker& ticker, const ImPlot3DRange& range, float p
11241122
}
11251123

11261124
void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlot3DTicker& ticker, ImPlot3DFormatter formatter, void* data) {
1127-
for (int i = 0; i < n; ++i) {
1125+
for (int i = 0; i < n; i++) {
11281126
if (labels != nullptr)
11291127
ticker.AddTick(values[i], false, true, labels[i]);
11301128
else
@@ -1315,7 +1313,7 @@ bool BeginPlot(const char* title_id, const ImVec2& size, ImPlot3DFlags flags) {
13151313
plot.ID = ID;
13161314
plot.JustCreated = just_created;
13171315
if (just_created) {
1318-
plot.Rotation = init_rotation;
1316+
plot.Rotation = plot.InitialRotation;
13191317
plot.FitThisFrame = true;
13201318
for (int i = 0; i < 3; i++) {
13211319
plot.Axes[i] = ImPlot3DAxis();
@@ -1327,6 +1325,7 @@ bool BeginPlot(const char* title_id, const ImVec2& size, ImPlot3DFlags flags) {
13271325
plot.PreviousFlags = flags;
13281326
plot.SetupLocked = false;
13291327
plot.OpenContextThisFrame = false;
1328+
plot.RotationCond = ImPlot3DCond_None;
13301329

13311330
// Populate title
13321331
plot.SetTitle(title_id);
@@ -1356,9 +1355,8 @@ bool BeginPlot(const char* title_id, const ImVec2& size, ImPlot3DFlags flags) {
13561355
plot.Items.Legend.Reset();
13571356

13581357
// Reset axes
1359-
for (int i = 0; i < ImAxis3D_COUNT; ++i) {
1358+
for (int i = 0; i < ImAxis3D_COUNT; i++)
13601359
plot.Axes[i].Reset();
1361-
}
13621360

13631361
// Push frame rect clipping
13641362
ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);
@@ -1455,6 +1453,17 @@ void EndPlot() {
14551453
// [SECTION] Setup
14561454
//-----------------------------------------------------------------------------
14571455

1456+
static const float ANIMATION_ANGULAR_VELOCITY = 2 * 3.1415f;
1457+
1458+
float CalcAnimationTime(ImPlot3DQuat q0, ImPlot3DQuat q1) {
1459+
// Compute the angular distance between orientations
1460+
float dot_product = ImClamp(q0.Dot(q1), -1.0f, 1.0f);
1461+
float angle = 2.0f * acosf(fabsf(dot_product));
1462+
1463+
// Calculate animation time for constant the angular velocity
1464+
return angle / ANIMATION_ANGULAR_VELOCITY;
1465+
}
1466+
14581467
void SetupAxis(ImAxis3D idx, const char* label, ImPlot3DAxisFlags flags) {
14591468
ImPlot3DContext& gp = *GImPlot3D;
14601469
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
@@ -1472,7 +1481,7 @@ void SetupAxis(ImAxis3D idx, const char* label, ImPlot3DAxisFlags flags) {
14721481
void SetupAxisLimits(ImAxis3D idx, double min_lim, double max_lim, ImPlot3DCond cond) {
14731482
ImPlot3DContext& gp = *GImPlot3D;
14741483
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
1475-
"SetupAxisLimits() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis
1484+
"SetupAxisLimits() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
14761485
ImPlot3DPlot& plot = *gp.CurrentPlot;
14771486
ImPlot3DAxis& axis = plot.Axes[idx];
14781487
if (!plot.Initialized || cond == ImPlot3DCond_Always) {
@@ -1531,6 +1540,51 @@ void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, dou
15311540
GImPlot3D->CurrentPlot->FitThisFrame = false;
15321541
}
15331542

1543+
void SetupBoxRotation(float elevation, float azimuth, bool animate, ImPlot3DCond cond) {
1544+
// Convert angles from degrees to radians
1545+
float elev_rad = elevation * IM_PI / 180.0f;
1546+
float azim_rad = azimuth * IM_PI / 180.0f;
1547+
1548+
// Call quaternion SetupBoxRotation
1549+
SetupBoxRotation(ImPlot3DQuat::FromElAz(elev_rad, azim_rad), animate, cond);
1550+
}
1551+
1552+
void SetupBoxRotation(ImPlot3DQuat rotation, bool animate, ImPlot3DCond cond) {
1553+
ImPlot3DContext& gp = *GImPlot3D;
1554+
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
1555+
"SetupBoxRotation() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
1556+
ImPlot3DPlot& plot = *gp.CurrentPlot;
1557+
1558+
if (!plot.Initialized || cond == ImPlot3DCond_Always) {
1559+
if (!animate) {
1560+
plot.Rotation = rotation;
1561+
plot.AnimationTime = 0.0f; // Force any running rotation animation to stop
1562+
} else {
1563+
plot.RotationAnimationEnd = rotation;
1564+
plot.AnimationTime = CalcAnimationTime(plot.Rotation, plot.RotationAnimationEnd);
1565+
}
1566+
plot.RotationCond = cond;
1567+
}
1568+
}
1569+
1570+
void SetupBoxInitialRotation(float elevation, float azimuth) {
1571+
// Convert angles from degrees to radians
1572+
float elev_rad = elevation * IM_PI / 180.0f;
1573+
float azim_rad = azimuth * IM_PI / 180.0f;
1574+
1575+
// Call quaternion SetupBoxInitialRotation
1576+
SetupBoxInitialRotation(ImPlot3DQuat::FromElAz(elev_rad, azim_rad));
1577+
}
1578+
1579+
void SetupBoxInitialRotation(ImPlot3DQuat rotation) {
1580+
ImPlot3DContext& gp = *GImPlot3D;
1581+
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
1582+
"SetupBoxInitialRotation() needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!");
1583+
ImPlot3DPlot& plot = *gp.CurrentPlot;
1584+
1585+
plot.InitialRotation = rotation;
1586+
}
1587+
15341588
void SetupBoxScale(float x, float y, float z) {
15351589
ImPlot3DContext& gp = *GImPlot3D;
15361590
IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,
@@ -1795,7 +1849,6 @@ ImPlot3DRay NDCRayToPlotRay(const ImPlot3DRay& ray) {
17951849
//-----------------------------------------------------------------------------
17961850

17971851
static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f;
1798-
static const float ANIMATION_ANGULAR_VELOCITY = 2 * 3.1415f;
17991852

18001853
void HandleInput(ImPlot3DPlot& plot) {
18011854
ImGuiIO& IO = ImGui::GetIO();
@@ -1962,12 +2015,12 @@ void HandleInput(ImPlot3DPlot& plot) {
19622015
plot.ContextClick = false;
19632016

19642017
// Handle reset rotation with left mouse double click
1965-
if (plot.Held && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
2018+
if (plot.Held && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right) && !plot.IsRotationLocked()) {
19662019
plot.RotationAnimationEnd = plot.Rotation;
19672020

19682021
// Calculate rotation to align the z-axis with the camera direction
19692022
if (hovered_plane == -1) {
1970-
plot.RotationAnimationEnd = init_rotation;
2023+
plot.RotationAnimationEnd = plot.InitialRotation;
19712024
} else {
19722025
// Compute plane normal
19732026
ImPlot3DPoint axis_normal = ImPlot3DPoint(0.0f, 0.0f, 0.0f);
@@ -2005,7 +2058,7 @@ void HandleInput(ImPlot3DPlot& plot) {
20052058

20062059
// Find the candidate with the maximum dot product
20072060
AxisAlignment* best_candidate = &candidates[0];
2008-
for (int i = 1; i < 4; ++i) {
2061+
for (int i = 1; i < 4; i++) {
20092062
if (candidates[i].dot > best_candidate->dot) {
20102063
best_candidate = &candidates[i];
20112064
}
@@ -2017,16 +2070,12 @@ void HandleInput(ImPlot3DPlot& plot) {
20172070
}
20182071
}
20192072

2020-
// Compute the angular distance between current and target rotation
2021-
float dot_product = ImClamp(plot.Rotation.Dot(plot.RotationAnimationEnd), -1.0f, 1.0f);
2022-
float angle = 2.0f * acosf(fabsf(dot_product));
2023-
2024-
// Calculate animation time for constant the angular velocity
2025-
plot.AnimationTime = angle / ANIMATION_ANGULAR_VELOCITY;
2073+
// Calculate animation time
2074+
plot.AnimationTime = CalcAnimationTime(plot.Rotation, plot.RotationAnimationEnd);
20262075
}
20272076

20282077
// Handle rotation with left mouse dragging
2029-
if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
2078+
if (plot.Held && ImGui::IsMouseDown(ImGuiMouseButton_Right) && !plot.IsRotationLocked()) {
20302079
ImVec2 delta(IO.MouseDelta.x, IO.MouseDelta.y);
20312080

20322081
// Map delta to rotation angles (in radians)
@@ -2424,7 +2473,7 @@ ImPlot3DColormap AddColormap(const char* name, const ImVec4* colormap, int size,
24242473
IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!");
24252474
ImVector<ImU32> buffer;
24262475
buffer.resize(size);
2427-
for (int i = 0; i < size; ++i)
2476+
for (int i = 0; i < size; i++)
24282477
buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]);
24292478
return gp.ColormapData.Append(name, buffer.Data, size, qual);
24302479
}
@@ -2868,6 +2917,16 @@ ImPlot3DQuat ImPlot3DQuat::FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3
28682917
return q;
28692918
}
28702919

2920+
ImPlot3DQuat ImPlot3DQuat::FromElAz(float elevation, float azimuth) {
2921+
// Create quaternions for azimuth and elevation
2922+
ImPlot3DQuat azimuth_quat(azimuth, ImPlot3DPoint(0.0f, 0.0f, 1.0f)); // Rotate around Z-axis
2923+
ImPlot3DQuat elevation_quat(elevation, ImPlot3DPoint(1.0f, 0.0f, 0.0f)); // Rotate around X-axis
2924+
ImPlot3DQuat zero_quat(-IM_PI / 2, ImPlot3DPoint(1.0f, 0.0f, 0.0f)); // Rotate to zero azimuth/elevation orientation
2925+
2926+
// Combine rotations
2927+
return elevation_quat * zero_quat * azimuth_quat;
2928+
}
2929+
28712930
float ImPlot3DQuat::Length() const {
28722931
return ImSqrt(x * x + y * y + z * z + w * w);
28732932
}

implot3d.h

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,22 @@ IMPLOT3D_API void SetupAxisTicks(ImAxis3D axis, double v_min, double v_max, int
373373
// Sets the label and/or flags for primary X/Y/Z axes (shorthand for three calls to SetupAxis)
374374
IMPLOT3D_API void SetupAxes(const char* x_label, const char* y_label, const char* z_label, ImPlot3DAxisFlags x_flags = 0, ImPlot3DAxisFlags y_flags = 0, ImPlot3DAxisFlags z_flags = 0);
375375

376-
// Sets the X/Y/Z axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits)
376+
// Sets the X/Y/Z axes range limits. If ImPlot3DCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits)
377377
IMPLOT3D_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, ImPlot3DCond cond = ImPlot3DCond_Once);
378378

379-
// Sets the plot box X/Y/Z scale. A scale of 1.0 is the default. Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it.
379+
// Sets the plot box rotation given the elevation and azimuth angles in degrees. If ImPlot3DCond_Always is used, the rotation will be locked
380+
IMPLOT3D_API void SetupBoxRotation(float elevation, float azimuth, bool animate = false, ImPlot3DCond cond = ImPlot3DCond_Once);
381+
382+
// Sets the plot box rotation given a quaternion. If ImPlot3DCond_Always is used, the rotation will be locked
383+
IMPLOT3D_API void SetupBoxRotation(ImPlot3DQuat rotation, bool animate = false, ImPlot3DCond cond = ImPlot3DCond_Once);
384+
385+
// Sets the plot box initial rotation given the elevation and azimuth angles in degrees. The initial rotation is the rotation the plot goes back to when a left mouse button double click happens
386+
IMPLOT3D_API void SetupBoxInitialRotation(float elevation, float azimuth);
387+
388+
// Sets the plot box initial rotation given a quaternion. The initial rotation is the rotation the plot goes back to when a left mouse button double click happens
389+
IMPLOT3D_API void SetupBoxInitialRotation(ImPlot3DQuat rotation);
390+
391+
// Sets the plot box X/Y/Z scale. A scale of 1.0 is the default. Values greater than 1.0 enlarge the plot, while values between 0.0 and 1.0 shrink it
380392
IMPLOT3D_API void SetupBoxScale(float x, float y, float z);
381393

382394
IMPLOT3D_API void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags = 0);
@@ -393,7 +405,7 @@ IMPLOT3D_TMP void PlotTriangle(const char* label_id, const T* xs, const T* ys, c
393405

394406
IMPLOT3D_TMP void PlotQuad(const char* label_id, const T* xs, const T* ys, const T* zs, int count, ImPlot3DQuadFlags flags = 0, int offset = 0, int stride = sizeof(T));
395407

396-
// Plot the surface defined by a grid of vertices. The grid is defined by the x and y arrays, and the z array contains the height of each vertex. A total of x_count * y_count vertices are expected for each array. Leave #scale_min and #scale_max both at 0 for automatic color scaling, or set them to a predefined range.
408+
// Plot the surface defined by a grid of vertices. The grid is defined by the x and y arrays, and the z array contains the height of each vertex. A total of x_count * y_count vertices are expected for each array. Leave #scale_min and #scale_max both at 0 for automatic color scaling, or set them to a predefined range
397409
IMPLOT3D_TMP void PlotSurface(const char* label_id, const T* xs, const T* ys, const T* zs, int x_count, int y_count, double scale_min = 0.0, double scale_max = 0.0, ImPlot3DSurfaceFlags flags = 0, int offset = 0, int stride = sizeof(T));
398410

399411
IMPLOT3D_API void PlotMesh(const char* label_id, const ImPlot3DPoint* vtx, const unsigned int* idx, int vtx_count, int idx_count, ImPlot3DMeshFlags flags = 0);
@@ -669,6 +681,9 @@ struct ImPlot3DQuat {
669681
// Set quaternion from two vectors
670682
IMPLOT3D_API static ImPlot3DQuat FromTwoVectors(const ImPlot3DPoint& v0, const ImPlot3DPoint& v1);
671683

684+
// Set quaternion given elevation and azimuth angles in radians
685+
IMPLOT3D_API static ImPlot3DQuat FromElAz(float elevation, float azimuth);
686+
672687
// Get quaternion length
673688
IMPLOT3D_API float Length() const;
674689

implot3d_demo.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,52 @@ void DemoBoxScale() {
565565
}
566566
}
567567

568+
void DemoBoxRotation() {
569+
float origin[2] = {0.0f, 0.0f};
570+
float axis[2] = {0.0f, 1.0f};
571+
572+
// Sliders for rotation angles
573+
static float elevation = 45.0f;
574+
static float azimuth = -135.0f;
575+
static bool animate = false;
576+
ImGui::Text("Rotation");
577+
bool changed = false;
578+
if (ImGui::SliderFloat("Elevation", &elevation, -90.0f, 90.0f, "%.1f degrees"))
579+
changed = true;
580+
if (ImGui::SliderFloat("Azimuth", &azimuth, -180.0f, 180.0f, "%.1f degrees"))
581+
changed = true;
582+
ImGui::Checkbox("Animate", &animate);
583+
584+
ImGui::Text("Initial Rotation");
585+
ImGui::SameLine();
586+
HelpMarker("The rotation will be reset to the initial rotation when you double right-click");
587+
static float init_elevation = 45.0f;
588+
static float init_azimuth = -135.0f;
589+
ImGui::SliderFloat("Initial Elevation", &init_elevation, -90.0f, 90.0f, "%.1f degrees");
590+
ImGui::SliderFloat("Initial Azimuth", &init_azimuth, -180.0f, 180.0f, "%.1f degrees");
591+
592+
if (ImPlot3D::BeginPlot("##BoxRotation")) {
593+
ImPlot3D::SetupAxesLimits(-1, 1, -1, 1, -1, 1, ImPlot3DCond_Always);
594+
595+
// Set initial rotation
596+
ImPlot3D::SetupBoxInitialRotation(init_elevation, init_azimuth);
597+
598+
// Set the rotation using the specified elevation and azimuth
599+
if (changed)
600+
ImPlot3D::SetupBoxRotation(elevation, azimuth, animate, ImPlot3DCond_Always);
601+
602+
// Plot axis lines
603+
ImPlot3D::SetNextLineStyle(ImVec4(0.8f, 0.2f, 0.2f, 1));
604+
ImPlot3D::PlotLine("X-Axis", axis, origin, origin, 2);
605+
ImPlot3D::SetNextLineStyle(ImVec4(0.2f, 0.8f, 0.2f, 1));
606+
ImPlot3D::PlotLine("Y-Axis", origin, axis, origin, 2);
607+
ImPlot3D::SetNextLineStyle(ImVec4(0.2f, 0.2f, 0.8f, 1));
608+
ImPlot3D::PlotLine("Z-Axis", origin, origin, axis, 2);
609+
610+
ImPlot3D::EndPlot();
611+
}
612+
}
613+
568614
void DemoTickLabels() {
569615
static bool custom_ticks = false;
570616
static bool custom_labels = true;
@@ -776,6 +822,7 @@ void ShowDemoWindow(bool* p_open) {
776822
}
777823
if (ImGui::BeginTabItem("Axes")) {
778824
DemoHeader("Box Scale", DemoBoxScale);
825+
DemoHeader("Box Rotation", DemoBoxRotation);
779826
DemoHeader("Tick Labels", DemoTickLabels);
780827
ImGui::EndTabItem();
781828
}

implot3d_internal.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ struct ImPlot3DAxis {
486486
ShowDefaultTicks = true;
487487
FitExtents.Min = HUGE_VAL;
488488
FitExtents.Max = -HUGE_VAL;
489+
RangeCond = ImPlot3DCond_None;
489490
Ticker.Reset();
490491
}
491492

@@ -552,7 +553,9 @@ struct ImPlot3DPlot {
552553
ImRect CanvasRect; // Frame rectangle reduced by padding
553554
ImRect PlotRect; // Bounding rectangle for the actual plot area
554555
// Rotation & axes & box
555-
ImPlot3DQuat Rotation; // Current rotation quaternion
556+
ImPlot3DQuat InitialRotation; // Initial rotation quaternion
557+
ImPlot3DQuat Rotation; // Current rotation quaternion
558+
ImPlot3DCond RotationCond;
556559
ImPlot3DAxis Axes[3]; // X, Y, Z axes
557560
ImPlot3DPoint BoxScale; // Scale factor for plot box X, Y, Z axes
558561
// Animation
@@ -579,7 +582,9 @@ struct ImPlot3DPlot {
579582
PreviousFlags = Flags = ImPlot3DFlags_None;
580583
JustCreated = true;
581584
Initialized = false;
585+
InitialRotation = ImPlot3DQuat(-0.513269f, -0.212596f, -0.318184f, 0.76819f);
582586
Rotation = ImPlot3DQuat(0.0f, 0.0f, 0.0f, 1.0f);
587+
RotationCond = ImPlot3DCond_None;
583588
for (int i = 0; i < 3; i++)
584589
Axes[i] = ImPlot3DAxis();
585590
BoxScale = ImPlot3DPoint(1.0f, 1.0f, 1.0f);
@@ -602,6 +607,7 @@ struct ImPlot3DPlot {
602607
}
603608
inline bool HasTitle() const { return !Title.empty() && !ImPlot3D::ImHasFlag(Flags, ImPlot3DFlags_NoTitle); }
604609
inline const char* GetTitle() const { return Title.Buf.Data; }
610+
inline bool IsRotationLocked() const { return RotationCond == ImPlot3DCond_Always; }
605611

606612
void ExtendFit(const ImPlot3DPoint& point);
607613
ImPlot3DPoint RangeMin() const;

0 commit comments

Comments
 (0)