Gui: Added classic trackball orbit style (#20535)

* Gui: add classic trackball orbit

* Gui: add rounded arcball orbit
This commit is contained in:
Graic
2025-04-28 12:12:53 -04:00
committed by GitHub
parent 350a416708
commit 8d2cb99712
4 changed files with 298 additions and 138 deletions

View File

@@ -64,7 +64,9 @@ public:
enum OrbitStyle { enum OrbitStyle {
Turntable, Turntable,
Trackball, Trackball,
FreeTurntable FreeTurntable,
TrackballClassic,
RoundedArcball
}; };
static constexpr float defaultSphereRadius = 0.8F; static constexpr float defaultSphereRadius = 0.8F;
@@ -87,9 +89,114 @@ public:
SbVec3f project(const SbVec2f &point) override SbVec3f project(const SbVec2f &point) override
{ {
if (orbit != RoundedArcball) {
return inherited::project(point); return inherited::project(point);
} }
// Rounded Arcball implementation
// based on SbSphereSheetProjector in Open Inventor
SbVec3f result;
SbLine workingLine = getWorkingLine(point);
if (needSetup) {
setupPlane();
}
SbVec3f planeIntersection;
SbVec3f sphereIntersection, dontCare;
SbBool hitSphere;
if (intersectFront == TRUE) {
hitSphere = sphere.intersect(workingLine, sphereIntersection, dontCare);
}
else {
hitSphere = sphere.intersect(workingLine, dontCare, sphereIntersection);
}
if (hitSphere) {
// drop the sphere intersection onto the tolerance plane
SbLine projectLine(sphereIntersection, sphereIntersection + planeDir);
if (!tolPlane.intersect(projectLine, planeIntersection))
#ifdef DEBUG
SoDebugError::post("SbSphereSheetProjector::project",
"Couldn't intersect working line with plane");
#else
/* Do nothing */;
#endif
}
else if (!tolPlane.intersect(workingLine, planeIntersection))
#ifdef DEBUG
SoDebugError::post("SbSphereSheetProjector::project", "Couldn't intersect with plane");
#else
/* Do nothing */;
#endif
// Three possibilities:
// (1) Intersection is on the sphere inside where the fillet
// hits it
// (2) Intersection is on the fillet
// (3) Intersection is on the plane
float distance = (planeIntersection - planePoint).length();
// Amount of filleting
// 0 = no fillet (just sphere and plane)
// infinity = only fillet
float border = 0.5;
// Radius where the fillet meets the plane in "squished space"
float r_a = 1.0F + border;
// Radius where the sphere meets the fillet in "squished space"
float r_i = 2.0 / (r_a + 1.0 / r_a);
// Distance squared in "squished space"
float d_2 = (distance * distance) * r_a * r_a;
// Distance in "squished space"
float d = std::sqrt(d_2);
// Compute how far off the plane we are
float offsetDist = 0.0;
if (d > r_a) {
// On the plane
offsetDist = 0.0;
}
else if (d < r_i) {
// On the sphere inside the fillet
offsetDist = std::sqrt(1.0 - d_2);
}
else {
// On the fillet
float d_r = r_a - d;
float a = border * (1.0 + border / 2.0);
offsetDist = a - std::sqrt((a + d_r) * (a - d_r));
}
SbVec3f offset;
if (orientToEye) {
if (viewVol.getProjectionType() == SbViewVolume::PERSPECTIVE) {
offset = workingProjPoint - planeIntersection;
}
else {
worldToWorking.multDirMatrix(viewVol.zVector(), offset);
}
offset.normalize();
}
else {
offset.setValue(0, 0, 1);
}
if (intersectFront == FALSE) {
offset *= -1.0;
}
offset *= offsetDist;
result = planeIntersection + offset;
lastPoint = result;
return result;
}
SbRotation getRotation(const SbVec3f &point1, const SbVec3f &point2) override SbRotation getRotation(const SbVec3f &point1, const SbVec3f &point2) override
{ {
SbRotation rot = inherited::getRotation(point1, point2); SbRotation rot = inherited::getRotation(point1, point2);
@@ -99,6 +206,9 @@ public:
if (orbit == FreeTurntable) { if (orbit == FreeTurntable) {
return getFreeTurntable(point1, point2); return getFreeTurntable(point1, point2);
} }
if (orbit == TrackballClassic) {
return getTrackballClassic(point1, point2);
}
return rot; return rot;
} }
@@ -164,6 +274,22 @@ private:
return zrot * xrot; return zrot * xrot;
} }
SbRotation getTrackballClassic(const SbVec3f &point1, const SbVec3f &point2) const
{
// Classic trackball
SbRotation zrot;
SbRotation yrot;
SbVec3f dif = point1 - point2;
SbVec3f zaxis(1,0,0);
zrot.setValue(zaxis, dif[1]);
SbVec3f yaxis(0,1,0);
yrot.setValue(yaxis, -dif[0]);
return zrot * yrot;
}
private: private:
SbMatrix worldToScreen; SbMatrix worldToScreen;
OrbitStyle orbit{Trackball}; OrbitStyle orbit{Trackball};
@@ -888,7 +1014,9 @@ void NavigationStyle::spin(const SbVec2f & pointerpos)
float sensitivity = getSensitivity(); float sensitivity = getSensitivity();
// Adjust the spin projector sphere to the screen position of the rotation center when the mouse intersects an object // Adjust the spin projector sphere to the screen position of the rotation center when the mouse intersects an object
if (getOrbitStyle() == Trackball && rotationCenterMode & RotationCenterMode::ScenePointAtCursor && rotationCenterFound && rotationCenterIsScenePointAtCursor) { if ((getOrbitStyle() == Trackball || getOrbitStyle() == TrackballClassic || getOrbitStyle() == RoundedArcball)
&& rotationCenterMode & RotationCenterMode::ScenePointAtCursor && rotationCenterFound
&& rotationCenterIsScenePointAtCursor) {
const auto pointOnScreen = viewer->getPointOnViewport(rotationCenter); const auto pointOnScreen = viewer->getPointOnViewport(rotationCenter);
const auto sphereCenter = 2 * normalizePixelPos(pointOnScreen) - SbVec2f {1, 1}; const auto sphereCenter = 2 * normalizePixelPos(pointOnScreen) - SbVec2f {1, 1};

View File

@@ -103,7 +103,9 @@ public:
enum OrbitStyle { enum OrbitStyle {
Turntable, Turntable,
Trackball, Trackball,
FreeTurntable FreeTurntable,
TrackballClassic,
RoundedArcball,
}; };
enum class RotationCenterMode { enum class RotationCenterMode {

View File

@@ -475,7 +475,9 @@ Select a set and then press the button to view said configurations.</string>
<string>Rotation orbit style. <string>Rotation orbit style.
Trackball: moving the mouse horizontally will rotate the part around the y-axis Trackball: moving the mouse horizontally will rotate the part around the y-axis
Turntable: the part will be rotated around the z-axis (with constrained axes). Turntable: the part will be rotated around the z-axis (with constrained axes).
Free Turntable: the part will be rotated around the z-axis.</string> Free Turntable: the part will be rotated around the z-axis.
Trackball Classic: moving the mouse will rotate the part allowing precession
Rounded Arcball: moving the mouse in the corners of the screen will only roll the part.</string>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>1</number>
@@ -495,6 +497,16 @@ Free Turntable: the part will be rotated around the z-axis.</string>
<string>Free Turntable</string> <string>Free Turntable</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Trackball Classic</string>
</property>
</item>
<item>
<property name="text">
<string>Rounded Arcball</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">

View File

@@ -612,6 +612,8 @@ def retranslateUi():
aTurntable.setText(translate("NavigationIndicator", "Turntable")) aTurntable.setText(translate("NavigationIndicator", "Turntable"))
aFreeTurntable.setText(translate("NavigationIndicator", "Free Turntable")) aFreeTurntable.setText(translate("NavigationIndicator", "Free Turntable"))
aTrackball.setText(translate("NavigationIndicator", "Trackball")) aTrackball.setText(translate("NavigationIndicator", "Trackball"))
aTrackballClassic.setText(translate("NavigationIndicator", "Trackball Classic"))
aRoundedArcball.setText(translate("NavigationIndicator", "Rounded Arcball"))
a0.setText(translate("NavigationIndicator", "Undefined")) a0.setText(translate("NavigationIndicator", "Undefined"))
@@ -648,10 +650,18 @@ aTrackball.setCheckable(True)
aFreeTurntable = QtGui.QAction(gOrbit) aFreeTurntable = QtGui.QAction(gOrbit)
aFreeTurntable.setObjectName("NavigationIndicator_FreeTurntable") aFreeTurntable.setObjectName("NavigationIndicator_FreeTurntable")
aFreeTurntable.setCheckable(True) aFreeTurntable.setCheckable(True)
aTrackballClassic = QtGui.QAction(gOrbit)
aTrackballClassic.setObjectName("NavigationIndicator_TrackballClassic")
aTrackballClassic.setCheckable(True)
aRoundedArcball = QtGui.QAction(gOrbit)
aRoundedArcball.setObjectName("NavigationIndicator_RoundedArcball")
aRoundedArcball.setCheckable(True)
menuOrbit.addAction(aTurntable) menuOrbit.addAction(aTurntable)
menuOrbit.addAction(aTrackball) menuOrbit.addAction(aTrackball)
menuOrbit.addAction(aFreeTurntable) menuOrbit.addAction(aFreeTurntable)
menuOrbit.addAction(aTrackballClassic)
menuOrbit.addAction(aRoundedArcball)
menuSettings.addMenu(menuOrbit) menuSettings.addMenu(menuOrbit)
menuSettings.addSeparator() menuSettings.addSeparator()
@@ -795,6 +805,10 @@ def onOrbit():
pView.SetInt("OrbitStyle", 1) pView.SetInt("OrbitStyle", 1)
elif aFreeTurntable.isChecked(): elif aFreeTurntable.isChecked():
pView.SetInt("OrbitStyle", 2) pView.SetInt("OrbitStyle", 2)
elif aTrackballClassic.isChecked():
pView.SetInt("OrbitStyle", 3)
elif aRoundedArcball.isChecked():
pView.SetInt("OrbitStyle", 4)
def onOrbitShow(): def onOrbitShow():
@@ -808,6 +822,10 @@ def onOrbitShow():
aTrackball.setChecked(True) aTrackball.setChecked(True)
elif OrbitStyle == 2: elif OrbitStyle == 2:
aFreeTurntable.setChecked(True) aFreeTurntable.setChecked(True)
elif OrbitStyle == 3:
aTrackballClassic.setChecked(True)
elif OrbitStyle == 4:
aRoundedArcball.setChecked(True)
gOrbit.blockSignals(False) gOrbit.blockSignals(False)