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

View File

@@ -103,7 +103,9 @@ public:
enum OrbitStyle {
Turntable,
Trackball,
FreeTurntable
FreeTurntable,
TrackballClassic,
RoundedArcball,
};
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.
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).
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 name="currentIndex">
<number>1</number>
@@ -495,6 +497,16 @@ Free Turntable: the part will be rotated around the z-axis.</string>
<string>Free Turntable</string>
</property>
</item>
<item>
<property name="text">
<string>Trackball Classic</string>
</property>
</item>
<item>
<property name="text">
<string>Rounded Arcball</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">

View File

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