diff --git a/src/Gui/Navigation/NavigationStyle.cpp b/src/Gui/Navigation/NavigationStyle.cpp index 9a0e3fca08..b24fce9ff0 100644 --- a/src/Gui/Navigation/NavigationStyle.cpp +++ b/src/Gui/Navigation/NavigationStyle.cpp @@ -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 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 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& 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(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; diff --git a/src/Gui/Navigation/NavigationStyle.h b/src/Gui/Navigation/NavigationStyle.h index 2b918039a1..74e698d482 100644 --- a/src/Gui/Navigation/NavigationStyle.h +++ b/src/Gui/Navigation/NavigationStyle.h @@ -103,7 +103,9 @@ public: enum OrbitStyle { Turntable, Trackball, - FreeTurntable + FreeTurntable, + TrackballClassic, + RoundedArcball, }; enum class RotationCenterMode { diff --git a/src/Gui/PreferencePages/DlgSettingsNavigation.ui b/src/Gui/PreferencePages/DlgSettingsNavigation.ui index 2ca0f98b9e..b081328995 100644 --- a/src/Gui/PreferencePages/DlgSettingsNavigation.ui +++ b/src/Gui/PreferencePages/DlgSettingsNavigation.ui @@ -475,7 +475,9 @@ Select a set and then press the button to view said configurations. 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. +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. 1 @@ -495,6 +497,16 @@ Free Turntable: the part will be rotated around the z-axis. Free Turntable + + + Trackball Classic + + + + + Rounded Arcball + + diff --git a/src/Mod/Tux/NavigationIndicatorGui.py b/src/Mod/Tux/NavigationIndicatorGui.py index 38a9111afc..a5fde4439f 100644 --- a/src/Mod/Tux/NavigationIndicatorGui.py +++ b/src/Mod/Tux/NavigationIndicatorGui.py @@ -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)