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,7 +89,112 @@ public:
SbVec3f project(const SbVec2f &point) override SbVec3f project(const SbVec2f &point) override
{ {
return inherited::project(point); if (orbit != RoundedArcball) {
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
@@ -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};
@@ -415,7 +541,7 @@ std::shared_ptr<NavigationAnimation> NavigationStyle::setCameraOrientation(const
} }
// or set the pose directly // or set the pose directly
// Distance from rotation center to camera position in camera coordinate system // Distance from rotation center to camera position in camera coordinate system
const SbVec3f rotationCenterDistanceCam = camera->focalDistance.getValue() * SbVec3f(0, 0, 1); const SbVec3f rotationCenterDistanceCam = camera->focalDistance.getValue() * SbVec3f(0, 0, 1);
@@ -447,7 +573,7 @@ std::shared_ptr<NavigationAnimation> NavigationStyle::translateCamera(const SbVe
} }
// or set the pose directly // or set the pose directly
camera->position = camera->position.getValue() + translation; camera->position = camera->position.getValue() + translation;
return {}; return {};
@@ -580,21 +706,21 @@ void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotatio
// Fix issue with near clipping in orthogonal view // Fix issue with near clipping in orthogonal view
if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) { if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
// The center of the bounding sphere in camera coordinate system // The center of the bounding sphere in camera coordinate system
SbVec3f center; SbVec3f center;
camera->orientation.getValue().inverse().multVec(boundingSphere.getCenter() - camera->position.getValue(), center); camera->orientation.getValue().inverse().multVec(boundingSphere.getCenter() - camera->position.getValue(), center);
SbVec3f dir; SbVec3f dir;
camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), dir); camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), dir);
// Reposition the camera but keep the focal point the same // Reposition the camera but keep the focal point the same
// nearDistance is 0 and farDistance is the diameter of the bounding sphere // nearDistance is 0 and farDistance is the diameter of the bounding sphere
float repositionDistance = -center.getValue()[2] - boundingSphere.getRadius(); float repositionDistance = -center.getValue()[2] - boundingSphere.getRadius();
camera->position = camera->position.getValue() + repositionDistance * dir; camera->position = camera->position.getValue() + repositionDistance * dir;
camera->nearDistance = 0; camera->nearDistance = 0;
camera->farDistance = 2 * boundingSphere.getRadius() + 1; camera->farDistance = 2 * boundingSphere.getRadius() + 1;
camera->focalDistance = camera->focalDistance.getValue() - repositionDistance; camera->focalDistance = camera->focalDistance.getValue() - repositionDistance;
} }
#endif #endif
} }
@@ -687,13 +813,13 @@ void NavigationStyle::zoom(SoCamera * cam, float diffvalue)
// frustum (similar to glFrustum()) // frustum (similar to glFrustum())
if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) && if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) &&
tname != "FrustumCamera") { tname != "FrustumCamera") {
/* static SbBool first = true; /* static SbBool first = true;
if (first) { if (first) {
SoDebugError::postWarning("SoGuiFullViewerP::zoom", SoDebugError::postWarning("SoGuiFullViewerP::zoom",
"Unknown camera type, " "Unknown camera type, "
"will zoom by moving position, but this might not be correct."); "will zoom by moving position, but this might not be correct.");
first = false; first = false;
}*/ }*/
} }
const float oldfocaldist = cam->focalDistance.getValue(); const float oldfocaldist = cam->focalDistance.getValue();
@@ -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};
@@ -1293,23 +1421,23 @@ void NavigationStyle::startSelection(NavigationStyle::SelectionMode mode)
switch (mode) switch (mode)
{ {
case Lasso: case Lasso:
mouseSelection = new PolyPickerSelection(); mouseSelection = new PolyPickerSelection();
break; break;
case Rectangle: case Rectangle:
mouseSelection = new RectangleSelection(); mouseSelection = new RectangleSelection();
break; break;
case Rubberband: case Rubberband:
mouseSelection = new RubberbandSelection(); mouseSelection = new RubberbandSelection();
break; break;
case BoxZoom: case BoxZoom:
mouseSelection = new BoxZoomSelection(); mouseSelection = new BoxZoomSelection();
break; break;
case Clip: case Clip:
mouseSelection = new PolyClipSelection(); mouseSelection = new PolyClipSelection();
break; break;
default: default:
break; break;
} }
if (mouseSelection) if (mouseSelection)
@@ -1344,7 +1472,7 @@ SbBool NavigationStyle::isSelecting() const
const std::vector<SbVec2s>& NavigationStyle::getPolygon(SelectionRole* role) const const std::vector<SbVec2s>& NavigationStyle::getPolygon(SelectionRole* role) const
{ {
if (role) if (role)
*role = this->selectedRole; *role = this->selectedRole;
return pcPolygon; return pcPolygon;
} }
@@ -1420,59 +1548,59 @@ void NavigationStyle::setViewingMode(const ViewerMode newmode)
} }
switch (newmode) { switch (newmode) {
case DRAGGING: case DRAGGING:
// Set up initial projection point for the projector object when // Set up initial projection point for the projector object when
// first starting a drag operation. // first starting a drag operation.
animator->stop(); animator->stop();
viewer->showRotationCenter(true); viewer->showRotationCenter(true);
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403) #if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
findBoundingSphere(); findBoundingSphere();
#endif #endif
this->spinprojector->project(this->lastmouseposition); this->spinprojector->project(this->lastmouseposition);
this->interactiveCountInc(); this->interactiveCountInc();
this->clearLog(); this->clearLog();
break; break;
case SPINNING: case SPINNING:
this->interactiveCountInc(); this->interactiveCountInc();
viewer->getSoRenderManager()->scheduleRedraw(); viewer->getSoRenderManager()->scheduleRedraw();
break; break;
case PANNING: case PANNING:
animator->stop(); animator->stop();
setupPanningPlane(viewer->getSoRenderManager()->getCamera()); setupPanningPlane(viewer->getSoRenderManager()->getCamera());
this->interactiveCountInc(); this->interactiveCountInc();
break; break;
case ZOOMING: case ZOOMING:
animator->stop(); animator->stop();
this->interactiveCountInc(); this->interactiveCountInc();
break; break;
case BOXZOOM: case BOXZOOM:
animator->stop(); animator->stop();
this->interactiveCountInc(); this->interactiveCountInc();
break; break;
default: // include default to avoid compiler warnings. default: // include default to avoid compiler warnings.
break; break;
} }
switch (oldmode) { switch (oldmode) {
case SPINNING: case SPINNING:
case DRAGGING: case DRAGGING:
viewer->showRotationCenter(false); viewer->showRotationCenter(false);
[[fallthrough]]; [[fallthrough]];
case PANNING: case PANNING:
case ZOOMING: case ZOOMING:
case BOXZOOM: case BOXZOOM:
this->interactiveCountDec(); this->interactiveCountDec();
break; break;
default: default:
break; break;
} }
viewer->setCursorRepresentation(newmode); viewer->setCursorRepresentation(newmode);
@@ -1569,20 +1697,20 @@ void NavigationStyle::syncWithEvent(const SoEvent * const ev)
auto const event = static_cast<const SoKeyboardEvent *>(ev); auto const event = static_cast<const SoKeyboardEvent *>(ev);
const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
switch (event->getKey()) { switch (event->getKey()) {
case SoKeyboardEvent::LEFT_CONTROL: case SoKeyboardEvent::LEFT_CONTROL:
case SoKeyboardEvent::RIGHT_CONTROL: case SoKeyboardEvent::RIGHT_CONTROL:
this->ctrldown = press; this->ctrldown = press;
break; break;
case SoKeyboardEvent::LEFT_SHIFT: case SoKeyboardEvent::LEFT_SHIFT:
case SoKeyboardEvent::RIGHT_SHIFT: case SoKeyboardEvent::RIGHT_SHIFT:
this->shiftdown = press; this->shiftdown = press;
break; break;
case SoKeyboardEvent::LEFT_ALT: case SoKeyboardEvent::LEFT_ALT:
case SoKeyboardEvent::RIGHT_ALT: case SoKeyboardEvent::RIGHT_ALT:
this->altdown = press; this->altdown = press;
break; break;
default: default:
break; break;
} }
} }
@@ -1594,17 +1722,17 @@ void NavigationStyle::syncWithEvent(const SoEvent * const ev)
// SoDebugError::postInfo("processSoEvent", "button = %d", button); // SoDebugError::postInfo("processSoEvent", "button = %d", button);
switch (button) { switch (button) {
case SoMouseButtonEvent::BUTTON1: case SoMouseButtonEvent::BUTTON1:
this->button1down = press; this->button1down = press;
break; break;
case SoMouseButtonEvent::BUTTON2: case SoMouseButtonEvent::BUTTON2:
this->button2down = press; this->button2down = press;
break; break;
case SoMouseButtonEvent::BUTTON3: case SoMouseButtonEvent::BUTTON3:
this->button3down = press; this->button3down = press;
break; break;
default: default:
break; break;
} }
} }
} }
@@ -1645,43 +1773,43 @@ SbBool NavigationStyle::processKeyboardEvent(const SoKeyboardEvent * const event
SbBool processed = false; SbBool processed = false;
const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
switch (event->getKey()) { switch (event->getKey()) {
case SoKeyboardEvent::LEFT_CONTROL: case SoKeyboardEvent::LEFT_CONTROL:
case SoKeyboardEvent::RIGHT_CONTROL: case SoKeyboardEvent::RIGHT_CONTROL:
this->ctrldown = press; this->ctrldown = press;
break; break;
case SoKeyboardEvent::LEFT_SHIFT: case SoKeyboardEvent::LEFT_SHIFT:
case SoKeyboardEvent::RIGHT_SHIFT: case SoKeyboardEvent::RIGHT_SHIFT:
this->shiftdown = press; this->shiftdown = press;
break; break;
case SoKeyboardEvent::LEFT_ALT: case SoKeyboardEvent::LEFT_ALT:
case SoKeyboardEvent::RIGHT_ALT: case SoKeyboardEvent::RIGHT_ALT:
this->altdown = press; this->altdown = press;
break; break;
case SoKeyboardEvent::S: case SoKeyboardEvent::S:
case SoKeyboardEvent::HOME: case SoKeyboardEvent::HOME:
case SoKeyboardEvent::LEFT_ARROW: case SoKeyboardEvent::LEFT_ARROW:
case SoKeyboardEvent::UP_ARROW: case SoKeyboardEvent::UP_ARROW:
case SoKeyboardEvent::RIGHT_ARROW: case SoKeyboardEvent::RIGHT_ARROW:
case SoKeyboardEvent::DOWN_ARROW: case SoKeyboardEvent::DOWN_ARROW:
if (!this->isViewing()) if (!this->isViewing())
this->setViewing(true); this->setViewing(true);
break; break;
case SoKeyboardEvent::PAGE_UP: case SoKeyboardEvent::PAGE_UP:
{ {
processed = true; processed = true;
const SbVec2f posn = normalizePixelPos(event->getPosition()); const SbVec2f posn = normalizePixelPos(event->getPosition());
doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn); doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn);
break; break;
} }
case SoKeyboardEvent::PAGE_DOWN: case SoKeyboardEvent::PAGE_DOWN:
{ {
processed = true; processed = true;
const SbVec2f posn = normalizePixelPos(event->getPosition()); const SbVec2f posn = normalizePixelPos(event->getPosition());
doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn); doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn);
break; break;
} }
default: default:
break; break;
} }
return processed; return processed;

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)