Download src/Gui/Navigation/NavigationStyle.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 72.7 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/Navigation/NavigationStyle.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Gui/Navigation/NavigationStyle.cpp
-
curl -L -o NavigationStyle.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/Navigation/NavigationStyle.cpp
72.7 kB
| // SPDX-License-Identifier: LGPL-2.1-or-later | |
| /*************************************************************************** | |
| * Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net> * | |
| * * | |
| * This file is part of the FreeCAD CAx development system. * | |
| * * | |
| * This library is free software; you can redistribute it and/or * | |
| * modify it under the terms of the GNU Library General Public * | |
| * License as published by the Free Software Foundation; either * | |
| * version 2 of the License, or (at your option) any later version. * | |
| * * | |
| * This library is distributed in the hope that it will be useful, * | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| * GNU Library General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Library General Public * | |
| * License along with this library; see the file COPYING.LIB. If not, * | |
| * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| * Suite 330, Boston, MA 02111-1307, USA * | |
| * * | |
| ***************************************************************************/ | |
| using namespace Gui; | |
| class FCSphereSheetProjector: public SbSphereSheetProjector | |
| { | |
| using inherited = SbSphereSheetProjector; | |
| public: | |
| enum OrbitStyle | |
| { | |
| Turntable, | |
| Trackball, | |
| FreeTurntable, | |
| TrackballClassic, | |
| RoundedArcball | |
| }; | |
| static constexpr float defaultSphereRadius = 0.8F; | |
| FCSphereSheetProjector(const SbSphere& sph, const SbBool orienttoeye = true) | |
| : SbSphereSheetProjector(sph, orienttoeye) | |
| {} | |
| void setViewVolume(const SbViewVolume& vol) override | |
| { | |
| inherited::setViewVolume(vol); | |
| } | |
| void setWorkingSpace(const SbMatrix& space) override | |
| { | |
| // inherited::setWorkingSpace(space); | |
| this->worldToScreen = space.inverse(); | |
| } | |
| SbVec3f project(const SbVec2f& point) override | |
| { | |
| 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)) { | |
| SoDebugError::post( | |
| "SbSphereSheetProjector::project", | |
| "Could not intersect working line with plane" | |
| ); | |
| } | |
| } | |
| else if (!tolPlane.intersect(workingLine, planeIntersection)) { | |
| SoDebugError::post("SbSphereSheetProjector::project", "Could not intersect with plane"); | |
| } | |
| // 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 rot = inherited::getRotation(point1, point2); | |
| if (orbit == Turntable || orbit == FreeTurntable) { | |
| return getTurntable(rot, point1, point2); | |
| } | |
| if (orbit == TrackballClassic) { | |
| return getTrackballClassic(point1, point2); | |
| } | |
| return rot; | |
| } | |
| void setOrbitStyle(OrbitStyle style) | |
| { | |
| this->orbit = style; | |
| } | |
| OrbitStyle getOrbitStyle() const | |
| { | |
| return this->orbit; | |
| } | |
| private: | |
| SbRotation getTurntable(SbRotation rot, const SbVec3f& point1, const SbVec3f& point2) const | |
| { | |
| // 0000333: Turntable camera rotation | |
| SbVec3f axis; | |
| float angle {}; | |
| rot.getValue(axis, angle); | |
| SbVec3f dif = point1 - point2; | |
| if (fabs(dif[1]) > fabs(dif[0])) { | |
| SbVec3f xaxis(1, 0, 0); | |
| if (dif[1] < 0) { | |
| angle = -angle; | |
| } | |
| rot.setValue(xaxis, angle); | |
| } | |
| else { | |
| SbVec3f zaxis(0, 0, 1); | |
| this->worldToScreen.multDirMatrix(zaxis, zaxis); | |
| if (zaxis[1] < 0) { | |
| if (dif[0] < 0) { | |
| angle = -angle; | |
| } | |
| } | |
| else { | |
| if (dif[0] > 0) { | |
| angle = -angle; | |
| } | |
| } | |
| rot.setValue(zaxis, angle); | |
| } | |
| return rot; | |
| } | |
| 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 {RoundedArcball}; | |
| }; | |
| NavigationStyleEvent::NavigationStyleEvent(const Base::Type& s) | |
| : QEvent(QEvent::User) | |
| , t(s) | |
| {} | |
| NavigationStyleEvent::~NavigationStyleEvent() = default; | |
| const Base::Type& NavigationStyleEvent::style() const | |
| { | |
| return t; | |
| } | |
| TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStyle, Base::BaseClass) | |
| NavigationStyle::NavigationStyle() | |
| : viewer(nullptr) | |
| , mouseSelection(nullptr) | |
| , pythonObject(nullptr) | |
| { | |
| this->rotationCenterMode = NavigationStyle::RotationCenterMode::ScenePointAtCursor | |
| | NavigationStyle::RotationCenterMode::FocalPointAtCursor; | |
| initialize(); | |
| } | |
| NavigationStyle::~NavigationStyle() | |
| { | |
| finalize(); | |
| delete this->animator; | |
| if (!pythonObject.is(nullptr)) { | |
| Base::PyGILStateLocker lock; | |
| Base::PyObjectBase* obj = static_cast<Base::PyObjectBase*>(pythonObject.ptr()); | |
| obj->setInvalid(); | |
| } | |
| } | |
| NavigationStyle& NavigationStyle::operator=(const NavigationStyle& ns) | |
| { | |
| this->panningplane = ns.panningplane; | |
| this->menuenabled = ns.menuenabled; | |
| this->animationEnabled = ns.animationEnabled; | |
| this->spinningAnimationEnabled = ns.spinningAnimationEnabled; | |
| static_cast<FCSphereSheetProjector*>(this->spinprojector) | |
| ->setOrbitStyle(static_cast<FCSphereSheetProjector*>(ns.spinprojector)->getOrbitStyle()); | |
| return *this; | |
| } | |
| void NavigationStyle::setViewer(View3DInventorViewer* view) | |
| { | |
| this->viewer = view; | |
| } | |
| void NavigationStyle::initialize() | |
| { | |
| this->animator = new NavigationAnimator(); | |
| this->sensitivity = 2.0f; | |
| this->resetcursorpos = false; | |
| this->currentmode = NavigationStyle::IDLE; | |
| this->animationEnabled = true; | |
| this->spinningAnimationEnabled = false; | |
| this->spinsamplecounter = 0; | |
| this->spinincrement = SbRotation::identity(); | |
| this->rotationCenterFound = false; | |
| this->rotationCenterIsScenePointAtCursor = false; | |
| // FIXME: use a smaller sphere than the default one to have a larger | |
| // area close to the borders that gives us "z-axis rotation"? | |
| // 19990425 mortene. | |
| this->spinprojector = new FCSphereSheetProjector( | |
| SbSphere(SbVec3f(0, 0, 0), FCSphereSheetProjector::defaultSphereRadius) | |
| ); | |
| SbViewVolume volume; | |
| volume.ortho(-1, 1, -1, 1, -1, 1); | |
| this->spinprojector->setViewVolume(volume); | |
| this->log.size = 16; | |
| this->log.position = new SbVec2s[16]; | |
| this->log.time = new SbTime[16]; | |
| this->log.historysize = 0; | |
| this->menuenabled = true; | |
| this->button1down = false; | |
| this->button2down = false; | |
| this->button3down = false; | |
| this->ctrldown = false; | |
| this->shiftdown = false; | |
| this->altdown = false; | |
| this->invertZoom = App::GetApplication() | |
| .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") | |
| ->GetBool("InvertZoom", true); | |
| this->zoomAtCursor = App::GetApplication() | |
| .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") | |
| ->GetBool("ZoomAtCursor", true); | |
| this->zoomStep = App::GetApplication() | |
| .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") | |
| ->GetFloat("ZoomStep", 0.2f); | |
| long mode = App::GetApplication() | |
| .GetParameterGroupByPath("User parameter:BaseApp/Preferences/View") | |
| ->GetInt("RotationMode", 0); | |
| if (mode == 0) { | |
| setRotationCenterMode(NavigationStyle::RotationCenterMode::WindowCenter); | |
| } | |
| else if (mode == 1) { | |
| setRotationCenterMode( | |
| NavigationStyle::RotationCenterMode::ScenePointAtCursor | |
| | NavigationStyle::RotationCenterMode::FocalPointAtCursor | |
| ); | |
| } | |
| else if (mode == 2) { | |
| setRotationCenterMode( | |
| NavigationStyle::RotationCenterMode::ScenePointAtCursor | |
| | NavigationStyle::RotationCenterMode::BoundingBoxCenter | |
| ); | |
| } | |
| this->hasDragged = false; | |
| this->hasPanned = false; | |
| this->hasZoomed = false; | |
| } | |
| void NavigationStyle::finalize() | |
| { | |
| delete this->spinprojector; | |
| delete[] this->log.position; | |
| delete[] this->log.time; | |
| } | |
| void NavigationStyle::interactiveCountInc() | |
| { | |
| viewer->interactiveCountInc(); | |
| } | |
| void NavigationStyle::interactiveCountDec() | |
| { | |
| viewer->interactiveCountDec(); | |
| } | |
| int NavigationStyle::getInteractiveCount() const | |
| { | |
| return viewer->getInteractiveCount(); | |
| } | |
| void NavigationStyle::setOrbitStyle(NavigationStyle::OrbitStyle style) | |
| { | |
| auto projector = static_cast<FCSphereSheetProjector*>(this->spinprojector); | |
| projector->setOrbitStyle(FCSphereSheetProjector::OrbitStyle(style)); | |
| } | |
| NavigationStyle::OrbitStyle NavigationStyle::getOrbitStyle() const | |
| { | |
| auto projector = static_cast<FCSphereSheetProjector*>(this->spinprojector); | |
| return NavigationStyle::OrbitStyle(projector->getOrbitStyle()); | |
| } | |
| SbBool NavigationStyle::isViewing() const | |
| { | |
| return viewer->isViewing(); | |
| } | |
| void NavigationStyle::setViewing(SbBool enable) | |
| { | |
| viewer->setViewing(enable); | |
| } | |
| SbBool NavigationStyle::isSeekMode() const | |
| { | |
| return viewer->isSeekMode(); | |
| } | |
| void NavigationStyle::setSeekMode(SbBool enable) | |
| { | |
| viewer->setSeekMode(enable); | |
| } | |
| SbBool NavigationStyle::seekToPoint(const SbVec2s screenpos) | |
| { | |
| return viewer->seekToPoint(screenpos); | |
| } | |
| void NavigationStyle::seekToPoint(const SbVec3f& scenepos) | |
| { | |
| viewer->seekToPoint(scenepos); | |
| } | |
| void NavigationStyle::lookAtPoint(const SbVec2s screenpos) | |
| { | |
| const SoCamera* camera = viewer->getCamera(); | |
| if (!camera) { | |
| return; | |
| } | |
| SoRayPickAction rpaction(viewer->getViewportRegion()); | |
| rpaction.setPoint(screenpos); | |
| rpaction.setRadius(viewer->getPickRadius()); | |
| rpaction.apply(viewer->getSoRenderManager()->getSceneGraph()); | |
| const SoPickedPoint* picked = rpaction.getPickedPoint(); | |
| // Point is either the hitpoint or the projected point on the panning plane | |
| SbVec3f point; | |
| if (picked) { | |
| point = picked->getPoint(); | |
| } | |
| else { | |
| const SbViewportRegion& vp = viewer->getViewportRegion(); | |
| const float aspectratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(aspectratio); | |
| // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping | |
| if (aspectratio < 1.0) { | |
| vv.scale(1.0 / aspectratio); | |
| } | |
| SbLine line; | |
| vv.projectPointToLine(normalizePixelPos(screenpos), line); | |
| panningplane.intersect(line, point); | |
| } | |
| lookAtPoint(point); | |
| } | |
| void NavigationStyle::lookAtPoint(const SbVec3f& position) | |
| { | |
| this->rotationCenterFound = false; | |
| translateCamera(position - getFocalPoint()); | |
| } | |
| SoCamera* NavigationStyle::getCamera() const | |
| { | |
| return this->viewer->getCamera(); | |
| } | |
| std::shared_ptr<NavigationAnimation> NavigationStyle::setCameraOrientation( | |
| const SbRotation& orientation, | |
| const SbBool moveToCenter | |
| ) const | |
| { | |
| SoCamera* camera = getCamera(); | |
| if (!camera) { | |
| return {}; | |
| } | |
| animator->stop(); | |
| const SbVec3f focalPoint = getFocalPoint(); | |
| SbVec3f translation(0, 0, 0); | |
| if (moveToCenter) { | |
| SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); | |
| action.apply(viewer->getSceneGraph()); | |
| SbBox3f box = action.getBoundingBox(); | |
| if (!box.isEmpty()) { | |
| translation = box.getCenter() - focalPoint; | |
| } | |
| } | |
| // Start an animation and return it | |
| if (isAnimationEnabled()) { | |
| return viewer->startAnimation(orientation, focalPoint, translation); | |
| } | |
| // 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); | |
| // Set to the given orientation | |
| camera->orientation = orientation; | |
| // Distance from rotation center to new camera position in global coordinate system | |
| SbVec3f newRotationCenterDistance; | |
| camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance); | |
| // Reposition camera so the rotation center stays in the same place | |
| // Optionally add translation to move to center | |
| camera->position = focalPoint + newRotationCenterDistance + translation; | |
| return {}; | |
| } | |
| std::shared_ptr<NavigationAnimation> NavigationStyle::translateCamera(const SbVec3f& translation) const | |
| { | |
| SoCamera* camera = getCamera(); | |
| if (!camera) { | |
| return {}; | |
| } | |
| animator->stop(); | |
| // Start an animation and return it | |
| if (isAnimationEnabled()) { | |
| return viewer->startAnimation(camera->orientation.getValue(), SbVec3f(0, 0, 0), translation); | |
| } | |
| // or set the pose directly | |
| camera->position = camera->position.getValue() + translation; | |
| return {}; | |
| } | |
| void NavigationStyle::boxZoom(const SbBox2s& box) | |
| { | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { // no camera | |
| return; | |
| } | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| SbViewVolume vv = cam->getViewVolume(vp.getViewportAspectRatio()); | |
| short sizeX {}, sizeY {}; | |
| box.getSize(sizeX, sizeY); | |
| SbVec2s size = vp.getViewportSizePixels(); | |
| // The bbox must not be empty i.e. width and length is zero, but it is possible that | |
| // either width or length is zero | |
| if (sizeX == 0 && sizeY == 0) { | |
| return; | |
| } | |
| // Get the new center in normalized pixel coordinates | |
| short xmin {}, xmax {}, ymin {}, ymax {}; | |
| box.getBounds(xmin, ymin, xmax, ymax); | |
| const SbVec2f center( | |
| (float)((xmin + xmax) / 2) / (float)std::max((int)(size[0] - 1), 1), | |
| (float)(size[1] - (ymin + ymax) / 2) / (float)std::max((int)(size[1] - 1), 1) | |
| ); | |
| SbPlane plane = vv.getPlane(cam->focalDistance.getValue()); | |
| panCamera(cam, vp.getViewportAspectRatio(), plane, SbVec2f(0.5, 0.5), center); | |
| // Set height or height angle of the camera | |
| float scaleX = (float)sizeX / (float)size[0]; | |
| float scaleY = (float)sizeY / (float)size[1]; | |
| float scaleFactor = std::max<float>(scaleX, scaleY); | |
| doScale(cam, scaleFactor); | |
| } | |
| void NavigationStyle::scale(float factor) | |
| { | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { // no camera | |
| return; | |
| } | |
| // Find the current center of the screen | |
| SbVec3f direction; | |
| cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction); | |
| SbVec3f initCenter = cam->position.getValue() + cam->focalDistance.getValue() * direction; | |
| // Move the camera to the origin for scaling | |
| cam->position = cam->position.getValue() - initCenter; | |
| // Scale the view | |
| doScale(cam, factor); | |
| // Move the camera back to it's initial position scaled | |
| cam->position = cam->position.getValue() + initCenter * factor; | |
| } | |
| void NavigationStyle::viewAll() | |
| { | |
| // Get the bounding box of the scene | |
| SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); | |
| action.apply(viewer->getSceneGraph()); | |
| SbBox3f box = action.getBoundingBox(); | |
| if (box.isEmpty()) { | |
| return; | |
| } | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { | |
| return; | |
| } | |
| SbViewVolume vol = cam->getViewVolume(); | |
| if (vol.ulf == vol.llf) { | |
| return; // empty frustum (no view up vector defined) | |
| } | |
| SbVec2f s = vol.projectBox(box); | |
| SbVec2s size = viewer->getSoRenderManager()->getSize(); | |
| SbVec3f pt1, pt2, pt3, tmp; | |
| vol.projectPointToLine(SbVec2f(0.0f, 0.0f), pt1, tmp); | |
| vol.projectPointToLine(SbVec2f(s[0], 0.0f), pt2, tmp); | |
| vol.projectPointToLine(SbVec2f(0.0f, s[1]), pt3, tmp); | |
| float cam_width = (pt2 - pt1).length(); | |
| float cam_height = (pt3 - pt1).length(); | |
| // add a small border | |
| cam_height = 1.08f * std::max<float>((cam_width * (float)size[1]) / (float)size[0], cam_height); | |
| float aspect = cam->aspectRatio.getValue(); | |
| if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { | |
| auto ocam = static_cast<SoOrthographicCamera*>(cam); | |
| if (aspect < 1.0f) { | |
| ocam->height = cam_height / aspect; | |
| } | |
| else { | |
| ocam->height = cam_height; | |
| } | |
| } | |
| } | |
| void NavigationStyle::findBoundingSphere() | |
| { | |
| // Find a bounding sphere for the scene | |
| SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); | |
| action.apply(viewer->getSceneGraph()); | |
| boundingSphere.circumscribe(action.getBoundingBox()); | |
| } | |
| /** Rotate the camera by the given amount, then reposition it so we're still pointing at the same | |
| * focal point | |
| */ | |
| void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation) | |
| { | |
| reorientCamera(camera, rotation, getFocalPoint()); | |
| } | |
| /** Rotate the camera by the given amount, then reposition it so the rotation center stays in the | |
| * same place | |
| */ | |
| void NavigationStyle::reorientCamera( | |
| SoCamera* camera, | |
| const SbRotation& rotation, | |
| const SbVec3f& rotationCenter | |
| ) | |
| { | |
| if (!camera) { | |
| return; | |
| } | |
| // Distance from rotation center to camera position in camera coordinate system | |
| SbVec3f rotationCenterDistanceCam; | |
| camera->orientation.getValue().inverse().multVec( | |
| camera->position.getValue() - rotationCenter, | |
| rotationCenterDistanceCam | |
| ); | |
| // Set new orientation value by accumulating the new rotation | |
| camera->orientation = rotation * camera->orientation.getValue(); | |
| // Distance from rotation center to new camera position in global coordinate system | |
| SbVec3f newRotationCenterDistance; | |
| camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance); | |
| // Reposition camera so the rotation center stays in the same place | |
| camera->position = rotationCenter + newRotationCenterDistance; | |
| if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) { | |
| // Adjust the camera position to keep the focal point in the same place while making sure | |
| // the focal distance stays small. This increases rotation stability for small and large | |
| // scenes | |
| SbVec3f direction; | |
| camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction); | |
| // Large focal distance puts the camera far away which causes Coin's auto clipping | |
| // calculations to add more slack (more space between near and far plane) and thus reduces | |
| // chances or hidden geometry. | |
| constexpr float orthographicFocalDistance = 250; | |
| constexpr float orthographicFocalDistance = 1; | |
| camera->position = getFocalPoint() - orthographicFocalDistance * direction; | |
| camera->focalDistance = orthographicFocalDistance; | |
| } | |
| // 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; | |
| camera->orientation.getValue().inverse().multVec( | |
| boundingSphere.getCenter() - camera->position.getValue(), | |
| center | |
| ); | |
| 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; | |
| } | |
| } | |
| void NavigationStyle::panCamera( | |
| SoCamera* cam, | |
| float aspectratio, | |
| const SbPlane& panplane, | |
| const SbVec2f& currpos, | |
| const SbVec2f& prevpos | |
| ) | |
| { | |
| if (!cam) { // can happen for empty scenegraph | |
| return; | |
| } | |
| if (currpos == prevpos) { // useless invocation | |
| return; | |
| } | |
| // Find projection points for the last and current mouse coordinates. | |
| SbViewVolume vv = cam->getViewVolume(aspectratio); | |
| // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping | |
| if (aspectratio < 1.0) { | |
| vv.scale(1.0 / aspectratio); | |
| } | |
| SbLine line; | |
| vv.projectPointToLine(currpos, line); | |
| SbVec3f current_planept; | |
| panplane.intersect(line, current_planept); | |
| vv.projectPointToLine(prevpos, line); | |
| SbVec3f old_planept; | |
| panplane.intersect(line, old_planept); | |
| // Reposition camera according to the vector difference between the | |
| // projected points. | |
| cam->position = cam->position.getValue() - (current_planept - old_planept); | |
| if (this->currentmode != NavigationStyle::IDLE) { | |
| hasPanned = true; | |
| } | |
| } | |
| void NavigationStyle::setupPanningPlane(const SoCamera* camera) | |
| { | |
| // The plane we're projecting the mouse coordinates to get 3D | |
| // coordinates should stay the same during the whole pan | |
| // operation, so we should calculate this value here. | |
| if (!camera) { // can happen for empty scenegraph | |
| this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0); | |
| } | |
| else { | |
| const SbViewportRegion& vp = viewer->getViewportRegion(); | |
| const float aspectratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(aspectratio); | |
| // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping | |
| if (aspectratio < 1.0) { | |
| vv.scale(1.0 / aspectratio); | |
| } | |
| this->panningplane = vv.getPlane(camera->focalDistance.getValue()); | |
| } | |
| } | |
| /** Dependent on the camera type this will either shrink or expand the | |
| * height of the viewport (orthogonal camera) or move the camera | |
| * closer or further away from the focal point in the scene. | |
| */ | |
| void NavigationStyle::zoom(SoCamera* cam, float diffvalue) | |
| { | |
| if (!cam) { // can happen for empty scenegraph | |
| return; | |
| } | |
| animator->stop(); | |
| SoType t = cam->getTypeId(); | |
| SbName tname = t.getName(); | |
| // This will be in the range of <0, ->>. | |
| auto multiplicator = float(exp(diffvalue)); | |
| if (t.isDerivedFrom(SoOrthographicCamera::getClassTypeId())) { | |
| // Since there's no perspective, "zooming" in the original sense | |
| // of the word won't have any visible effect. So we just increase | |
| // or decrease the field-of-view values of the camera instead, to | |
| // "shrink" the projection size of the model / scene. | |
| auto oc = static_cast<SoOrthographicCamera*>(cam); | |
| oc->height = oc->height.getValue() * multiplicator; | |
| } | |
| else { | |
| // FrustumCamera can be found in the SmallChange CVS module (it's | |
| // a camera that lets you specify (for instance) an off-center | |
| // frustum (similar to glFrustum()) | |
| if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) && tname != "FrustumCamera") { | |
| static SbBool first = true; | |
| if (first) { | |
| SoDebugError::postWarning( | |
| "NavigationStyle::zoom", | |
| "Unknown camera type, " | |
| "will zoom by moving position, " | |
| "but this might not be correct." | |
| ); | |
| first = false; | |
| } | |
| } | |
| const float oldfocaldist = cam->focalDistance.getValue(); | |
| const float newfocaldist = oldfocaldist * multiplicator; | |
| SbVec3f direction; | |
| cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction); | |
| const SbVec3f oldpos = cam->position.getValue(); | |
| const SbVec3f newpos = oldpos + (newfocaldist - oldfocaldist) * -direction; | |
| // This catches a rather common user interface "buglet": if the | |
| // user zooms the camera out to a distance from origo larger than | |
| // what we still can safely do floating point calculations on | |
| // (i.e. without getting NaN or Inf values), the faulty floating | |
| // point values will propagate until we start to get debug error | |
| // messages and eventually an assert failure from core Coin code. | |
| // | |
| // With the below bounds check, this problem is avoided. | |
| // | |
| // (But note that we depend on the input argument ''diffvalue'' to | |
| // be small enough that zooming happens gradually. Ideally, we | |
| // should also check distorigo with isinf() and isnan() (or | |
| // inversely; isinfite()), but those only became standardized with | |
| // C99.) | |
| const float distorigo = newpos.length(); | |
| // sqrt(FLT_MAX) == ~ 1e+19, which should be both safe for further | |
| // calculations and ok for the end-user and app-programmer. | |
| float maxDistance = std::sqrt(std::numeric_limits<float>::max()); | |
| if (distorigo > maxDistance) { | |
| // do nothing here | |
| } | |
| else { | |
| cam->position = newpos; | |
| cam->focalDistance = newfocaldist; | |
| } | |
| } | |
| if (this->currentmode != NavigationStyle::IDLE) { | |
| hasZoomed = true; | |
| } | |
| } | |
| // Calculate a zoom/dolly factor from the difference of the current | |
| // cursor position and the last. | |
| void NavigationStyle::zoomByCursor(const SbVec2f& thispos, const SbVec2f& prevpos) | |
| { | |
| // There is no "geometrically correct" value, 20 just seems to give | |
| // about the right "feel". | |
| float value = (thispos[1] - prevpos[1]) * 10.0f /*20.0f*/; | |
| if (this->invertZoom) { | |
| value = -value; | |
| } | |
| zoom(viewer->getSoRenderManager()->getCamera(), value); | |
| } | |
| void NavigationStyle::zoomIn() | |
| { | |
| zoom(viewer->getSoRenderManager()->getCamera(), -this->zoomStep); | |
| } | |
| void NavigationStyle::zoomOut() | |
| { | |
| zoom(viewer->getSoRenderManager()->getCamera(), this->zoomStep); | |
| } | |
| /*! | |
| * Returns the steps if the mouse wheel is rotated | |
| */ | |
| int NavigationStyle::getDelta() const | |
| { | |
| return 120; | |
| } | |
| void NavigationStyle::doZoom(SoCamera* camera, int wheeldelta, const SbVec2f& pos) | |
| { | |
| float value = this->zoomStep * wheeldelta / float(getDelta()); | |
| if (this->invertZoom) { | |
| value = -value; | |
| } | |
| doZoom(camera, value, pos); | |
| } | |
| /*! | |
| *\brief NavigationStyle::doZoom Zooms in or out by specified factor, keeping the point on screen | |
| * specified by parameter pos fixed or not according to user preference | |
| * (NavigationStyle::zoomAtCursor). Ignores invertZoom user preference. | |
| */ | |
| void NavigationStyle::doZoom(SoCamera* camera, float logfactor, const SbVec2f& pos) | |
| { | |
| // something is asking for big zoom factor. This func is made for interactive zooming, | |
| // where the changes are per mouse move and thus are small. | |
| if (fabs(logfactor) > 4.0) { | |
| return; | |
| } | |
| SbBool zoomAtCur = this->zoomAtCursor; | |
| if (zoomAtCur) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio()); | |
| SbPlane panplane = vv.getPlane(camera->focalDistance.getValue()); | |
| panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, SbVec2f(0.5, 0.5), pos); | |
| } | |
| zoom(camera, logfactor); | |
| if (zoomAtCur) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio()); | |
| SbPlane panplane = vv.getPlane(camera->focalDistance.getValue()); | |
| panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5, 0.5)); | |
| // Change the position of the rotation center indicator after zooming at cursor | |
| // Rotation mode is WindowCenter | |
| if (!rotationCenterMode) { | |
| viewer->changeRotationCenterPosition(getFocalPoint()); | |
| findBoundingSphere(); | |
| } | |
| } | |
| } | |
| void NavigationStyle::doScale(SoCamera* cam, float factor) | |
| { | |
| if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) { | |
| float height = static_cast<SoOrthographicCamera*>(cam)->height.getValue() * factor; | |
| static_cast<SoOrthographicCamera*>(cam)->height = height; | |
| } | |
| else if (cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) { | |
| float height = static_cast<SoPerspectiveCamera*>(cam)->heightAngle.getValue() / 2.0f; | |
| height = 2.0f * atan(tan(height) * factor); | |
| static_cast<SoPerspectiveCamera*>(cam)->heightAngle = height; | |
| } | |
| } | |
| void NavigationStyle::doRotate(SoCamera* camera, float angle, const SbVec2f& pos) | |
| { | |
| SbBool zoomAtCur = this->zoomAtCursor; | |
| if (zoomAtCur) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio()); | |
| SbPlane panplane = vv.getPlane(camera->focalDistance.getValue()); | |
| panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, SbVec2f(0.5, 0.5), pos); | |
| } | |
| SbRotation rotcam = camera->orientation.getValue(); | |
| // get view direction | |
| SbVec3f vdir; | |
| rotcam.multVec(SbVec3f(0, 0, -1), vdir); | |
| // rotate | |
| SbRotation drot(vdir, angle); | |
| camera->orientation.setValue(rotcam * drot); | |
| if (zoomAtCur) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio()); | |
| SbPlane panplane = vv.getPlane(camera->focalDistance.getValue()); | |
| panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5, 0.5)); | |
| } | |
| } | |
| SbVec3f NavigationStyle::getRotationCenter(SbBool& found) const | |
| { | |
| found = this->rotationCenterFound; | |
| return this->rotationCenter; | |
| } | |
| std::optional<SbVec2s>& NavigationStyle::getRightClickPosition() | |
| { | |
| return rightClickPosition; | |
| } | |
| void NavigationStyle::setRotationCenter(const SbVec3f& cnt) | |
| { | |
| this->rotationCenter = cnt; | |
| this->rotationCenterFound = true; | |
| const auto camera = getCamera(); | |
| if (camera->isOfType(SoPerspectiveCamera::getClassTypeId())) { | |
| SbVec3f direction; | |
| camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction); | |
| // Calculate distance from camera to rotation center | |
| const auto rotationCenterDistance = rotationCenter - camera->position.getValue(); | |
| const auto rotationCenterDepth = rotationCenterDistance.dot(direction); | |
| // Set focal distance to match rotation center depth so we can zoom at the new rotation | |
| // center with a perspective camera | |
| camera->focalDistance.setValue(rotationCenterDepth); | |
| } | |
| } | |
| SbVec3f NavigationStyle::getFocalPoint() const | |
| { | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { | |
| return {0, 0, 0}; | |
| } | |
| // Find global coordinates of focal point. | |
| SbVec3f direction; | |
| cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction); | |
| SbVec3f focal = cam->position.getValue() + cam->focalDistance.getValue() * direction; | |
| return focal; | |
| } | |
| /** Uses the sphere sheet projector to map the mouse position onto | |
| * a 3D point and find a rotation from this and the last calculated point. | |
| */ | |
| void NavigationStyle::spin(const SbVec2f& pointerpos) | |
| { | |
| if (this->log.historysize < 2) { | |
| return; | |
| } | |
| assert(this->spinprojector); | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| SbVec2s glsize(vp.getViewportSizePixels()); | |
| SbVec2f lastpos; | |
| lastpos[0] = float(this->log.position[1][0]) / float(std::max((int)(glsize[0] - 1), 1)); | |
| lastpos[1] = float(this->log.position[1][1]) / float(std::max((int)(glsize[1] - 1), 1)); | |
| if (getOrbitStyle() == FreeTurntable) { | |
| SbVec2f midpos(lastpos[0], pointerpos[1]); | |
| spinInternal(pointerpos, midpos); | |
| spinInternal(midpos, lastpos); | |
| } | |
| else { | |
| spinInternal(pointerpos, lastpos); | |
| } | |
| if (this->currentmode != NavigationStyle::IDLE) { | |
| hasDragged = true; | |
| } | |
| } | |
| void NavigationStyle::spinInternal(const SbVec2f& pointerpos, const SbVec2f& lastpos) | |
| { | |
| 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 || getOrbitStyle() == TrackballClassic | |
| || getOrbitStyle() == RoundedArcball) | |
| && rotationCenterMode & RotationCenterMode::ScenePointAtCursor && rotationCenterFound | |
| && rotationCenterIsScenePointAtCursor) { | |
| const auto pointOnScreen = viewer->getPointOnViewport(rotationCenter); | |
| const auto sphereCenter = 2 * normalizePixelPos(pointOnScreen) - SbVec2f {1, 1}; | |
| float x, y; | |
| sphereCenter.getValue(x, y); | |
| const float sphereScale = 1 + sphereCenter.length(); | |
| const float radius = FCSphereSheetProjector::defaultSphereRadius * sphereScale; | |
| sensitivity *= sphereScale; | |
| spinprojector->setSphere(SbSphere {SbVec3f {x, y, 0}, radius}); | |
| } | |
| else { | |
| spinprojector->setSphere( | |
| SbSphere {SbVec3f {0, 0, 0}, FCSphereSheetProjector::defaultSphereRadius} | |
| ); | |
| } | |
| // 0000333: Turntable camera rotation | |
| SbMatrix mat; | |
| viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat); | |
| this->spinprojector->setWorkingSpace(mat); | |
| this->spinprojector->project(lastpos); | |
| SbRotation r; | |
| this->spinprojector->projectAndGetRotation(pointerpos, r); | |
| if (sensitivity > 1.0f) { | |
| SbVec3f axis; | |
| float radians {}; | |
| r.getValue(axis, radians); | |
| radians = sensitivity * radians; | |
| r.setValue(axis, radians); | |
| } | |
| r.invert(); | |
| if (this->rotationCenterMode && this->rotationCenterFound) { | |
| this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r, rotationCenter); | |
| } | |
| else { | |
| this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r); | |
| } | |
| // Calculate an average angle magnitude value to make the transition | |
| // to a possible spin animation mode appear smooth. | |
| SbVec3f dummy_axis, newaxis; | |
| float acc_angle {}, newangle {}; | |
| this->spinincrement.getValue(dummy_axis, acc_angle); | |
| acc_angle *= this->spinsamplecounter; // weight | |
| r.getValue(newaxis, newangle); | |
| acc_angle += newangle; | |
| this->spinsamplecounter++; | |
| acc_angle /= this->spinsamplecounter; | |
| // FIXME: accumulate and average axis vectors as well? 19990501 mortene. | |
| this->spinincrement.setValue(newaxis, acc_angle); | |
| // Don't carry too much baggage, as that'll give unwanted results | |
| // when the user quickly trigger (as in "click-drag-release") a spin | |
| // animation. | |
| if (this->spinsamplecounter > 3) { | |
| this->spinsamplecounter = 3; | |
| } | |
| } | |
| /*! | |
| * \brief NavigationStyle::spin_simplified is a simplified version of | |
| * NavigationStyle::spin(..), which uses less global variables. Doesn't support | |
| * starting an animated spinning. | |
| * | |
| * \param cam the camera to affect. The rotation amount is determined by delta | |
| * (curpos-prevpos), and rotation axis is also affected by average pos. | |
| * \param curpos current normalized position or mouse pointer | |
| * \param prevpos previous normalized position of mouse pointer | |
| */ | |
| void NavigationStyle::spin_simplified(SbVec2f curpos, SbVec2f prevpos) | |
| { | |
| assert(this->spinprojector); | |
| if (getOrbitStyle() == FreeTurntable) { | |
| SbVec2f midpos(prevpos[0], curpos[1]); | |
| spinSimplifiedInternal(curpos, midpos); | |
| spinSimplifiedInternal(midpos, prevpos); | |
| } | |
| else { | |
| spinSimplifiedInternal(curpos, prevpos); | |
| } | |
| hasDragged = true; | |
| } | |
| void NavigationStyle::spinSimplifiedInternal(SbVec2f curpos, SbVec2f prevpos) | |
| { | |
| // 0000333: Turntable camera rotation | |
| SbMatrix mat; | |
| viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat); | |
| this->spinprojector->setWorkingSpace(mat); | |
| this->spinprojector->project(prevpos); | |
| SbRotation r; | |
| this->spinprojector->projectAndGetRotation(curpos, r); | |
| float sensitivity = getSensitivity(); | |
| if (sensitivity > 1.0f) { | |
| SbVec3f axis; | |
| float radians {}; | |
| r.getValue(axis, radians); | |
| radians = sensitivity * radians; | |
| r.setValue(axis, radians); | |
| } | |
| r.invert(); | |
| if (this->rotationCenterMode && this->rotationCenterFound) { | |
| this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r, rotationCenter); | |
| } | |
| else { | |
| this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r); | |
| } | |
| } | |
| SbBool NavigationStyle::doSpin() | |
| { | |
| if (this->log.historysize >= 3) { | |
| SbTime stoptime = (SbTime::getTimeOfDay() - this->log.time[0]); | |
| if (isSpinningAnimationEnabled() && stoptime.getValue() < 0.100) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| const SbVec2s glsize(vp.getViewportSizePixels()); | |
| SbVec3f from = this->spinprojector->project(SbVec2f( | |
| float(this->log.position[2][0]) / float(std::max(glsize[0] - 1, 1)), | |
| float(this->log.position[2][1]) / float(std::max(glsize[1] - 1, 1)) | |
| )); | |
| SbVec3f to = this->spinprojector->project(this->lastmouseposition); | |
| SbRotation rot = this->spinprojector->getRotation(from, to); | |
| SbTime delta = (this->log.time[0] - this->log.time[2]); | |
| double deltatime = delta.getValue(); | |
| rot.invert(); | |
| rot.scaleAngle(float(0.200 / deltatime)); | |
| SbVec3f axis; | |
| float radians {}; | |
| rot.getValue(axis, radians); | |
| if ((radians > 0.01f) && (deltatime < 0.300)) { | |
| viewer->startSpinningAnimation(axis, radians * 5); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| void NavigationStyle::saveCursorPosition(const SoEvent* const ev) | |
| { | |
| this->globalPos.setValue(QCursor::pos().x(), QCursor::pos().y()); | |
| this->localPos = ev->getPosition(); | |
| rotationCenterIsScenePointAtCursor = false; | |
| // mode is WindowCenter | |
| if (!this->rotationCenterMode) { | |
| setRotationCenter(getFocalPoint()); | |
| } | |
| // Option to get point on model (slow) or always on focal plane (fast) | |
| // | |
| // mode is ScenePointAtCursor to get exact point if possible | |
| if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::ScenePointAtCursor) { | |
| SoRayPickAction rpaction(viewer->getSoRenderManager()->getViewportRegion()); | |
| rpaction.setPoint(this->localPos); | |
| rpaction.setRadius(viewer->getPickRadius()); | |
| rpaction.apply(viewer->getSoRenderManager()->getSceneGraph()); | |
| SoPickedPoint* picked = rpaction.getPickedPoint(); | |
| if (picked) { | |
| setRotationCenter(picked->getPoint()); | |
| rotationCenterIsScenePointAtCursor = true; | |
| return; | |
| } | |
| } | |
| // mode is FocalPointAtCursor or a ScenePointAtCursor failed | |
| if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::FocalPointAtCursor) { | |
| // get the intersection point of the ray and the focal plane | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { // no camera | |
| return; | |
| } | |
| SbViewVolume vv = cam->getViewVolume(ratio); | |
| SbLine line; | |
| SbVec2f currpos = ev->getNormalizedPosition(vp); | |
| vv.projectPointToLine(currpos, line); | |
| SbVec3f current_planept; | |
| SbPlane panplane = vv.getPlane(cam->focalDistance.getValue()); | |
| panplane.intersect(line, current_planept); | |
| setRotationCenter(current_planept); | |
| } | |
| // mode is BoundingBoxCenter or a ScenePointAtCursor failed | |
| if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::BoundingBoxCenter) { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| float ratio = vp.getViewportAspectRatio(); | |
| SoCamera* cam = viewer->getSoRenderManager()->getCamera(); | |
| if (!cam) { // no camera | |
| return; | |
| } | |
| // Get the bounding box center of the physical object group | |
| SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion()); | |
| action.apply(viewer->objectGroup); | |
| SbBox3f boundingBox = action.getBoundingBox(); | |
| SbVec3f boundingBoxCenter = boundingBox.getCenter(); | |
| setRotationCenter(boundingBoxCenter); | |
| // To drag around the center point of the bbox we have to determine | |
| // its projection on the screen because this information is used in | |
| // NavigationStyle::spin() for the panning | |
| SbViewVolume vv = cam->getViewVolume(ratio); | |
| vv.projectToScreen(boundingBoxCenter, boundingBoxCenter); | |
| SbVec2s size = vp.getViewportSizePixels(); | |
| auto tox = static_cast<short>(boundingBoxCenter[0] * size[0]); | |
| auto toy = static_cast<short>(boundingBoxCenter[1] * size[1]); | |
| this->localPos.setValue(tox, toy); | |
| } | |
| } | |
| SbVec2f NavigationStyle::normalizePixelPos(SbVec2s pixpos) | |
| { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| const SbVec2s size(vp.getViewportSizePixels()); | |
| return { | |
| (float)pixpos[0] / (float)std::max((int)(size[0] - 1), 1), | |
| (float)pixpos[1] / (float)std::max((int)(size[1] - 1), 1) | |
| }; | |
| } | |
| SbVec2f NavigationStyle::normalizePixelPos(SbVec2f pixpos) | |
| { | |
| const SbViewportRegion& vp = viewer->getSoRenderManager()->getViewportRegion(); | |
| const SbVec2s size(vp.getViewportSizePixels()); | |
| return { | |
| pixpos[0] / (float)std::max((int)(size[0] - 1), 1), | |
| pixpos[1] / (float)std::max((int)(size[1] - 1), 1) | |
| }; | |
| } | |
| void NavigationStyle::moveCursorPosition() | |
| { | |
| if (!isResetCursorPosition()) { | |
| return; | |
| } | |
| QPoint cpos = QCursor::pos(); | |
| if (abs(cpos.x() - globalPos[0]) > 10 || abs(cpos.y() - globalPos[1]) > 10) { | |
| QCursor::setPos(globalPos[0], globalPos[1] - 1); | |
| this->log.position[0] = localPos; | |
| } | |
| } | |
| SbBool NavigationStyle::handleEventInForeground(const SoEvent* const e) | |
| { | |
| SoHandleEventAction action(viewer->getSoRenderManager()->getViewportRegion()); | |
| action.setEvent(e); | |
| action.setPickRadius(viewer->getPickRadius()); | |
| action.apply(viewer->foregroundroot); | |
| return action.isHandled(); | |
| } | |
| /** | |
| * @brief Decide if it should be possible to start any animation | |
| * | |
| * If the enable flag is false and we're currently animating, the animation will be stopped | |
| */ | |
| void NavigationStyle::setAnimationEnabled(const SbBool enable) | |
| { | |
| animationEnabled = enable; | |
| if (!enable && isAnimating()) { | |
| animator->stop(); | |
| } | |
| } | |
| /** | |
| * @brief Decide if it should be possible to start a spin animation of the model in the viewer by | |
| * releasing the mouse button while dragging | |
| * | |
| * If the enable flag is false and we're currently animating, the spin animation will be stopped | |
| */ | |
| void NavigationStyle::setSpinningAnimationEnabled(const SbBool enable) | |
| { | |
| spinningAnimationEnabled = enable; | |
| if (!enable && isSpinning()) { | |
| animator->stop(); | |
| } | |
| } | |
| /** | |
| * @return Whether or not it is possible to start any animation | |
| */ | |
| SbBool NavigationStyle::isAnimationEnabled() const | |
| { | |
| return animationEnabled; | |
| } | |
| /** | |
| * @return Whether or not it is possible to start a spinning animation e.g. after dragging | |
| */ | |
| SbBool NavigationStyle::isSpinningAnimationEnabled() const | |
| { | |
| return animationEnabled && spinningAnimationEnabled; | |
| } | |
| /** | |
| * @return Whether or not any animation is currently active | |
| */ | |
| SbBool NavigationStyle::isAnimating() const | |
| { | |
| return animator->isAnimating(); | |
| } | |
| /** | |
| * @return Whether or not a spinning animation is currently active e.g. after a user drag | |
| */ | |
| SbBool NavigationStyle::isSpinning() const | |
| { | |
| return currentmode == NavigationStyle::SPINNING; | |
| } | |
| void NavigationStyle::startAnimating(const std::shared_ptr<NavigationAnimation>& animation, bool wait) const | |
| { | |
| if (wait) { | |
| animator->startAndWait(animation); | |
| } | |
| else { | |
| animator->start(animation); | |
| } | |
| } | |
| void NavigationStyle::stopAnimating() const | |
| { | |
| animator->stop(); | |
| } | |
| void NavigationStyle::setSensitivity(float val) | |
| { | |
| this->sensitivity = val; | |
| } | |
| float NavigationStyle::getSensitivity() const | |
| { | |
| return this->sensitivity; | |
| } | |
| void NavigationStyle::setResetCursorPosition(SbBool on) | |
| { | |
| this->resetcursorpos = on; | |
| } | |
| SbBool NavigationStyle::isResetCursorPosition() const | |
| { | |
| return this->resetcursorpos; | |
| } | |
| void NavigationStyle::setZoomInverted(SbBool on) | |
| { | |
| this->invertZoom = on; | |
| } | |
| SbBool NavigationStyle::isZoomInverted() const | |
| { | |
| return this->invertZoom; | |
| } | |
| void NavigationStyle::setZoomStep(float val) | |
| { | |
| this->zoomStep = val; | |
| } | |
| void NavigationStyle::setZoomAtCursor(SbBool on) | |
| { | |
| this->zoomAtCursor = on; | |
| } | |
| SbBool NavigationStyle::isZoomAtCursor() const | |
| { | |
| return this->zoomAtCursor; | |
| } | |
| void NavigationStyle::setRotationCenterMode(NavigationStyle::RotationCenterModes mode) | |
| { | |
| this->rotationCenterMode = mode; | |
| } | |
| NavigationStyle::RotationCenterModes NavigationStyle::getRotationCenterMode() const | |
| { | |
| return this->rotationCenterMode; | |
| } | |
| void NavigationStyle::startSelection(AbstractMouseSelection* mouse) | |
| { | |
| if (!mouse) { | |
| return; | |
| } | |
| if (mouseSelection) { | |
| SoDebugError::postWarning( | |
| "NavigationStyle::startSelection", | |
| "Set new mouse selection while an old is still active." | |
| ); | |
| } | |
| mouseSelection = mouse; | |
| mouseSelection->grabMouseModel(viewer); | |
| } | |
| void NavigationStyle::startSelection(NavigationStyle::SelectionMode mode) | |
| { | |
| if (mouseSelection) { | |
| return; | |
| } | |
| if (isSelecting()) { | |
| stopSelection(); | |
| } | |
| 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; | |
| } | |
| if (mouseSelection) { | |
| mouseSelection->grabMouseModel(viewer); | |
| } | |
| } | |
| void NavigationStyle::abortSelection() | |
| { | |
| pcPolygon.clear(); | |
| if (mouseSelection) { | |
| mouseSelection->releaseMouseModel(true); | |
| delete mouseSelection; | |
| mouseSelection = nullptr; | |
| } | |
| } | |
| void NavigationStyle::stopSelection() | |
| { | |
| pcPolygon.clear(); | |
| if (mouseSelection) { | |
| mouseSelection->releaseMouseModel(); | |
| delete mouseSelection; | |
| mouseSelection = nullptr; | |
| } | |
| } | |
| SbBool NavigationStyle::isSelecting() const | |
| { | |
| return (mouseSelection ? true : false); | |
| } | |
| const std::vector<SbVec2s>& NavigationStyle::getPolygon(SelectionRole* role) const | |
| { | |
| if (role) { | |
| *role = this->selectedRole; | |
| } | |
| return pcPolygon; | |
| } | |
| bool NavigationStyle::isDraggerUnderCursor(const SbVec2s pos) const | |
| { | |
| SoRayPickAction rp(this->viewer->getSoRenderManager()->getViewportRegion()); | |
| rp.setRadius(viewer->getPickRadius()); | |
| rp.setPoint(pos); | |
| rp.apply(this->viewer->getSoRenderManager()->getSceneGraph()); | |
| SoPickedPoint* pick = rp.getPickedPoint(); | |
| if (pick) { | |
| const auto fullpath = static_cast<const SoFullPath*>(pick->getPath()); | |
| for (int i = 0; i < fullpath->getLength(); ++i) { | |
| if (fullpath->getNode(i)->isOfType(SoDragger::getClassTypeId())) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| return false; | |
| } | |
| // This method adds another point to the mouse location log, used for spin | |
| // animation calculations. | |
| void NavigationStyle::addToLog(const SbVec2s pos, const SbTime time) | |
| { | |
| // In case someone changes the const size setting at the top of this | |
| // file too small. | |
| assert(this->log.size > 2 && "mouse log too small!"); | |
| if (this->log.historysize > 0 && pos == this->log.position[0]) { | |
| return; | |
| } | |
| int lastidx = this->log.historysize; | |
| // If we've filled up the log, we should throw away the last item: | |
| if (lastidx == this->log.size) { | |
| lastidx--; | |
| } | |
| assert(lastidx < this->log.size); | |
| for (int i = lastidx; i > 0; i--) { | |
| this->log.position[i] = this->log.position[i - 1]; | |
| this->log.time[i] = this->log.time[i - 1]; | |
| } | |
| this->log.position[0] = pos; | |
| this->log.time[0] = time; | |
| if (this->log.historysize < this->log.size) { | |
| this->log.historysize += 1; | |
| } | |
| } | |
| // This method "clears" the mouse location log, used for spin | |
| // animation calculations. | |
| void NavigationStyle::clearLog() | |
| { | |
| this->log.historysize = 0; | |
| } | |
| void NavigationStyle::syncModifierKeys(const SoEvent* const ev) | |
| { | |
| // Mismatches in state of the modifier keys happens if the user | |
| // presses or releases them outside the viewer window. | |
| if (this->ctrldown != ev->wasCtrlDown()) { | |
| this->ctrldown = ev->wasCtrlDown(); | |
| } | |
| if (this->shiftdown != ev->wasShiftDown()) { | |
| this->shiftdown = ev->wasShiftDown(); | |
| } | |
| if (this->altdown != ev->wasAltDown()) { | |
| this->altdown = ev->wasAltDown(); | |
| } | |
| } | |
| // The viewer is a state machine, and all changes to the current state | |
| // are made through this call. | |
| void NavigationStyle::setViewingMode(const ViewerMode newmode) | |
| { | |
| const ViewerMode oldmode = this->currentmode; | |
| // Reset flags when changing from IDLE to another mode or if the mode is IDLE and the buttons | |
| // are released | |
| if ((oldmode == IDLE && newmode != IDLE) | |
| || (newmode == IDLE && !button1down && !button2down && !button3down)) { | |
| hasPanned = false; | |
| hasDragged = false; | |
| hasZoomed = false; | |
| } | |
| if (newmode == oldmode) { | |
| // The rotation center could have been changed even if the mode has not changed | |
| if (newmode == NavigationStyle::DRAGGING && rotationCenterFound) { | |
| viewer->changeRotationCenterPosition(rotationCenter); | |
| } | |
| return; | |
| } | |
| switch (newmode) { | |
| case DRAGGING: | |
| // Set up initial projection point for the projector object when | |
| // first starting a drag operation. | |
| animator->stop(); | |
| viewer->showRotationCenter(true); | |
| findBoundingSphere(); | |
| this->spinprojector->project(this->lastmouseposition); | |
| this->interactiveCountInc(); | |
| this->clearLog(); | |
| break; | |
| case SPINNING: | |
| this->interactiveCountInc(); | |
| viewer->getSoRenderManager()->scheduleRedraw(); | |
| break; | |
| case PANNING: | |
| animator->stop(); | |
| setupPanningPlane(viewer->getSoRenderManager()->getCamera()); | |
| this->interactiveCountInc(); | |
| break; | |
| case ZOOMING: | |
| animator->stop(); | |
| this->interactiveCountInc(); | |
| break; | |
| case BOXZOOM: | |
| animator->stop(); | |
| this->interactiveCountInc(); | |
| break; | |
| default: // include default to avoid compiler warnings. | |
| break; | |
| } | |
| switch (oldmode) { | |
| case SPINNING: | |
| case DRAGGING: | |
| viewer->showRotationCenter(false); | |
| [[fallthrough]]; | |
| case PANNING: | |
| case ZOOMING: | |
| case BOXZOOM: | |
| this->interactiveCountDec(); | |
| break; | |
| default: | |
| break; | |
| } | |
| viewer->setCursorRepresentation(newmode); | |
| this->currentmode = newmode; | |
| } | |
| int NavigationStyle::getViewingMode() const | |
| { | |
| return (int)this->currentmode; | |
| } | |
| SbBool NavigationStyle::processEvent(const SoEvent* const ev) | |
| { | |
| // If we're in picking mode then all events must be redirected to the | |
| // appropriate mouse model. | |
| if (mouseSelection) { | |
| int hd = mouseSelection->handleEvent(ev, viewer->getSoRenderManager()->getViewportRegion()); | |
| if (hd == AbstractMouseSelection::Continue || hd == AbstractMouseSelection::Restart) { | |
| return true; | |
| } | |
| else if (hd == AbstractMouseSelection::Finish) { | |
| pcPolygon = mouseSelection->getPositions(); | |
| selectedRole = mouseSelection->selectedRole(); | |
| delete mouseSelection; | |
| mouseSelection = nullptr; | |
| syncWithEvent(ev); | |
| return NavigationStyle::processSoEvent(ev); | |
| } | |
| else if (hd == AbstractMouseSelection::Cancel) { | |
| pcPolygon.clear(); | |
| delete mouseSelection; | |
| mouseSelection = nullptr; | |
| syncWithEvent(ev); | |
| return NavigationStyle::processSoEvent(ev); | |
| } | |
| } | |
| const ViewerMode curmode = this->currentmode; | |
| SbBool processed = false; | |
| processed = this->processSoEvent(ev); | |
| // check for left click without selecting something | |
| if ((curmode == NavigationStyle::SELECTION || curmode == NavigationStyle::IDLE) && !processed) { | |
| if (SoMouseButtonEvent::isButtonReleaseEvent(ev, SoMouseButtonEvent::BUTTON1)) { | |
| if (!ev->wasCtrlDown()) { | |
| Gui::Selection().clearSelection(); | |
| } | |
| } | |
| } | |
| return processed; | |
| } | |
| SbBool NavigationStyle::processSoEvent(const SoEvent* const ev) | |
| { | |
| bool processed = false; | |
| bool offeredtoViewerEventBase = false; | |
| // handle mouse wheel zoom | |
| if (ev->isOfType(SoMouseWheelEvent::getClassTypeId())) { | |
| auto const event = static_cast<const SoMouseWheelEvent*>(ev); | |
| processed = processWheelEvent(event); | |
| viewer->processSoEventBase(ev); | |
| offeredtoViewerEventBase = true; | |
| } | |
| if (!processed && !offeredtoViewerEventBase) { | |
| processed = viewer->processSoEventBase(ev); | |
| } | |
| return processed; | |
| } | |
| void NavigationStyle::syncWithEvent(const SoEvent* const ev) | |
| { | |
| // Events when in "ready-to-seek" mode are ignored, except those | |
| // which influence the seek mode itself -- these are handled further | |
| // up the inheritance hierarchy. | |
| if (this->isSeekMode()) { | |
| return; | |
| } | |
| const SoType type(ev->getTypeId()); | |
| // Mismatches in state of the modifier keys happens if the user | |
| // presses or releases them outside the viewer window. | |
| syncModifierKeys(ev); | |
| // Keyboard handling | |
| if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { | |
| 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; | |
| } | |
| } | |
| // Mouse Button / Spaceball Button handling | |
| if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { | |
| auto const event = static_cast<const SoMouseButtonEvent*>(ev); | |
| const int button = event->getButton(); | |
| const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; | |
| 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; | |
| } | |
| } | |
| } | |
| SbBool NavigationStyle::processMotionEvent(const SoMotion3Event* const ev) | |
| { | |
| SoCamera* const camera = viewer->getSoRenderManager()->getCamera(); | |
| if (!camera) { | |
| return false; | |
| } | |
| SbViewVolume volume(camera->getViewVolume()); | |
| SbVec3f center(volume.getSightPoint(camera->focalDistance.getValue())); | |
| float scale(volume.getWorldToScreenScale(center, 1.0)); | |
| float translationFactor = scale * .0001; | |
| SbVec3f dir = ev->getTranslation(); | |
| if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) { | |
| auto oCam = static_cast<SoOrthographicCamera*>(camera); | |
| oCam->scaleHeight(1.0 + (dir[2] * 0.0001)); | |
| dir[2] = 0.0; // don't move the cam for z translation. | |
| } | |
| SbRotation newRotation(ev->getRotation() * camera->orientation.getValue()); | |
| SbVec3f newPosition, newDirection; | |
| newRotation.multVec(SbVec3f(0.0, 0.0, -1.0), newDirection); | |
| newPosition = center - (newDirection * camera->focalDistance.getValue()); | |
| camera->orientation.setValue(newRotation); | |
| camera->orientation.getValue().multVec(dir, dir); | |
| camera->position = newPosition + (dir * translationFactor); | |
| return true; | |
| } | |
| 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: | |
| if (!this->isViewing()) { | |
| this->setViewing(true); | |
| } | |
| break; | |
| case SoKeyboardEvent::PAGE_UP: { | |
| 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; | |
| } | |
| return processed; | |
| } | |
| SbBool NavigationStyle::processClickEvent(const SoMouseButtonEvent* const event) | |
| { | |
| // issue #0002433: avoid to swallow the UP event if down the | |
| // scene graph somewhere a dialog gets opened | |
| SbBool processed = false; | |
| const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false; | |
| if (press) { | |
| SbTime tmp = (event->getTime() - mouseDownConsumedEvent.getTime()); | |
| float dci = (float)QApplication::doubleClickInterval() / 1000.0f; | |
| // a double-click? | |
| if (tmp.getValue() < dci) { | |
| mouseDownConsumedEvent = *event; | |
| mouseDownConsumedEvent.setTime(event->getTime()); | |
| processed = true; | |
| } | |
| else { | |
| mouseDownConsumedEvent.setTime(event->getTime()); | |
| // 'ANY' is used to mark that we don't know yet if it will | |
| // be a double-click event. | |
| mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); | |
| } | |
| } | |
| else if (!press) { | |
| if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) { | |
| // now handle the postponed event | |
| NavigationStyle::processSoEvent(&mouseDownConsumedEvent); | |
| mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY); | |
| } | |
| } | |
| return processed; | |
| } | |
| SbBool NavigationStyle::processWheelEvent(const SoMouseWheelEvent* const event) | |
| { | |
| const SbVec2s pos(event->getPosition()); | |
| const SbVec2f posn = normalizePixelPos(pos); | |
| // handle mouse wheel zoom | |
| doZoom(viewer->getSoRenderManager()->getCamera(), event->getDelta(), posn); | |
| return true; | |
| } | |
| void NavigationStyle::setPopupMenuEnabled(const SbBool on) | |
| { | |
| this->menuenabled = on; | |
| } | |
| SbBool NavigationStyle::isPopupMenuEnabled() const | |
| { | |
| return this->menuenabled; | |
| } | |
| bool NavigationStyle::isNavigationStyleAction(QAction* action, QActionGroup* navMenuGroup) const | |
| { | |
| return action && navMenuGroup->actions().indexOf(action) >= 0 && action->isChecked(); | |
| } | |
| QWidget* NavigationStyle::findView3DInventorWidget() const | |
| { | |
| QWidget* widget = viewer->getWidget(); | |
| while (widget && !widget->inherits("Gui::View3DInventor")) { | |
| widget = widget->parentWidget(); | |
| } | |
| return widget; | |
| } | |
| void NavigationStyle::applyNavigationStyleChange(QAction* selectedAction) | |
| { | |
| QByteArray navigationStyleTypeName = selectedAction->data().toByteArray(); | |
| QWidget* view3DWidget = findView3DInventorWidget(); | |
| if (view3DWidget) { | |
| Base::Type newNavigationStyle = Base::Type::fromName(navigationStyleTypeName.constData()); | |
| if (newNavigationStyle != this->getTypeId()) { | |
| QEvent* navigationChangeEvent = new NavigationStyleEvent(newNavigationStyle); | |
| QApplication::postEvent(view3DWidget, navigationChangeEvent); | |
| } | |
| } | |
| } | |
| void NavigationStyle::openPopupMenu(const SbVec2s& position) | |
| { | |
| // store the right-click position for potential use by Clarify Selection | |
| rightClickPosition = position; | |
| // ask workbenches and view provider, ... | |
| MenuItem view; | |
| Gui::Application::Instance->setupContextMenu("View", &view); | |
| auto contextMenu = new QMenu(viewer->getGLWidget()); | |
| MenuManager::getInstance()->setupContextMenu(&view, *contextMenu); | |
| contextMenu->setAttribute(Qt::WA_DeleteOnClose); | |
| auto navMenu = contextMenu->addMenu(QObject::tr("Navigation styles")); | |
| auto navMenuGroup = new QActionGroup(navMenu); | |
| // add submenu at the end to select navigation style | |
| const std::map<Base::Type, std::string> styles = UserNavigationStyle::getUserFriendlyNames(); | |
| for (const auto& style : styles) { | |
| const QString name = QApplication::translate(style.first.getName(), style.second.c_str()); | |
| QAction* item = navMenuGroup->addAction(name); | |
| navMenu->addAction(item); | |
| item->setCheckable(true); | |
| item->setData(QByteArray(style.first.getName())); | |
| if (const Base::Type item_style = style.first; item_style != this->getTypeId()) { | |
| auto triggeredFun = [this, item_style]() { | |
| QWidget* widget = viewer->getWidget(); | |
| while (widget && !widget->inherits("Gui::View3DInventor")) { | |
| widget = widget->parentWidget(); | |
| } | |
| if (widget) { | |
| // this is the widget where the viewer is embedded | |
| QEvent* ns_event = new NavigationStyleEvent(item_style); | |
| QApplication::postEvent(widget, ns_event); | |
| } | |
| }; | |
| item->connect(item, &QAction::triggered, triggeredFun); | |
| } | |
| else { | |
| item->setChecked(true); | |
| } | |
| } | |
| // Add Clarify Selection option if there are objects under cursor | |
| bool separator = false; | |
| auto posAction = !contextMenu->actions().empty() ? contextMenu->actions().front() : nullptr; | |
| // Get picked objects at position | |
| SoRayPickAction rp(viewer->getSoRenderManager()->getViewportRegion()); | |
| rp.setPoint(position); | |
| rp.setRadius(viewer->getPickRadius()); | |
| rp.setPickAll(true); | |
| rp.apply(viewer->getSoRenderManager()->getSceneGraph()); | |
| const SoPickedPointList& pplist = rp.getPickedPointList(); | |
| QAction* pickAction = nullptr; | |
| if (pplist.getLength() > 0) { | |
| separator = true; | |
| if (auto cmd | |
| = Application::Instance->commandManager().getCommandByName("Std_ClarifySelection")) { | |
| pickAction = new QAction(cmd->getAction()->text(), contextMenu); | |
| pickAction->setShortcut(cmd->getAction()->shortcut()); | |
| } | |
| else { | |
| pickAction = new QAction(QObject::tr("Clarify Selection"), contextMenu); | |
| } | |
| if (posAction) { | |
| contextMenu->insertAction(posAction, pickAction); | |
| contextMenu->insertSeparator(posAction); | |
| } | |
| else { | |
| contextMenu->addAction(pickAction); | |
| } | |
| } | |
| if (separator && posAction) { | |
| contextMenu->insertSeparator(posAction); | |
| } | |
| auto navigationFunction = [this, navMenuGroup](QAction* selectedAction) { | |
| // handle navigation style change if user selected a navigation style option | |
| if (isNavigationStyleAction(selectedAction, navMenuGroup)) { | |
| applyNavigationStyleChange(selectedAction); | |
| rightClickPosition.reset(); | |
| } | |
| }; | |
| auto clarifyFunction = [pickAction](QAction* selectedAction) { | |
| if (selectedAction == pickAction) { | |
| // Execute the Clarify Selection command at this position | |
| auto cmd = Application::Instance->commandManager().getCommandByName("Std_ClarifySelection"); | |
| if (cmd && cmd->isActive()) { | |
| cmd->invoke(0); // required placeholder value - we don't use group command | |
| } | |
| } | |
| }; | |
| QObject::connect(contextMenu, &QMenu::triggered, navigationFunction); | |
| QObject::connect(contextMenu, &QMenu::triggered, clarifyFunction); | |
| contextMenu->popup(QCursor::pos()); | |
| rightClickPosition.reset(); | |
| } | |
| PyObject* NavigationStyle::getPyObject() | |
| { | |
| if (pythonObject.is(nullptr)) { | |
| // ref counter is set to 1 | |
| pythonObject = Py::asObject(new NavigationStylePy(this)); | |
| } | |
| return Py::new_reference_to(pythonObject); | |
| } | |
| // ---------------------------------------------------------------------------------- | |
| TYPESYSTEM_SOURCE_ABSTRACT(Gui::UserNavigationStyle, Gui::NavigationStyle) | |
| std::string UserNavigationStyle::userFriendlyName() const | |
| { | |
| std::string name = this->getTypeId().getName(); | |
| // remove namespaces | |
| std::size_t pos = name.rfind("::"); | |
| if (pos != std::string::npos) { | |
| name = name.substr(pos + 2); | |
| } | |
| // remove 'NavigationStyle' | |
| pos = name.find("NavigationStyle"); | |
| if (pos != std::string::npos) { | |
| name = name.substr(0, pos); | |
| } | |
| return name; | |
| } | |
| std::map<Base::Type, std::string> UserNavigationStyle::getUserFriendlyNames() | |
| { | |
| std::map<Base::Type, std::string> names; | |
| std::vector<Base::Type> types; | |
| Base::Type::getAllDerivedFrom(UserNavigationStyle::getClassTypeId(), types); | |
| for (auto& type : types) { | |
| if (type != UserNavigationStyle::getClassTypeId()) { | |
| std::unique_ptr<UserNavigationStyle> inst( | |
| static_cast<UserNavigationStyle*>(type.createInstance()) | |
| ); | |
| if (inst) { | |
| names[type] = inst->userFriendlyName(); | |
| } | |
| } | |
| } | |
| return names; | |
| } | |