NavigationStyle.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net>     *
00003  *                                                                         *
00004  *   This file is part of the FreeCAD CAx development system.              *
00005  *                                                                         *
00006  *   This library is free software; you can redistribute it and/or         *
00007  *   modify it under the terms of the GNU Library General Public           *
00008  *   License as published by the Free Software Foundation; either          *
00009  *   version 2 of the License, or (at your option) any later version.      *
00010  *                                                                         *
00011  *   This library  is distributed in the hope that it will be useful,      *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00014  *   GNU Library General Public License for more details.                  *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU Library General Public     *
00017  *   License along with this library; see the file COPYING.LIB. If not,    *
00018  *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
00019  *   Suite 330, Boston, MA  02111-1307, USA                                *
00020  *                                                                         *
00021  ***************************************************************************/
00022 
00023 
00024 #include "PreCompiled.h"
00025 #ifndef _PreComp_
00026 # include <cfloat>
00027 # include "InventorAll.h"
00028 # include <QAction>
00029 # include <QActionGroup>
00030 # include <QApplication>
00031 # include <QByteArray>
00032 # include <QCursor>
00033 # include <QList>
00034 # include <QMenu>
00035 # include <QMetaObject>
00036 # include <QRegExp>
00037 #endif
00038 
00039 #include <Inventor/sensors/SoTimerSensor.h>
00040 
00041 #include <App/Application.h>
00042 #include "NavigationStyle.h"
00043 #include "View3DInventorViewer.h"
00044 #include "Application.h"
00045 #include "MenuManager.h"
00046 #include "MouseSelection.h"
00047 
00048 using namespace Gui;
00049 
00050 namespace Gui {
00051 struct NavigationStyleP {
00052     int animationsteps;
00053     int animationdelta;
00054     SbVec3f focal1, focal2;
00055     SbRotation endRotation;
00056     SoTimerSensor * animsensor;
00057 
00058     NavigationStyleP()
00059     {
00060         this->animationsteps = 0;
00061     }
00062     static void viewAnimationCB(void * data, SoSensor * sensor);
00063 };
00064 }
00065 
00066 class FCSphereSheetProjector : public SbSphereSheetProjector {
00067     typedef SbSphereSheetProjector inherited;
00068 
00069 public:
00070     enum OrbitStyle {
00071         Turntable,
00072         Trackball
00073     };
00074 
00075     FCSphereSheetProjector(const SbSphere & sph, const SbBool orienttoeye = TRUE)
00076         : SbSphereSheetProjector(sph, orienttoeye), orbit(Trackball)
00077     {
00078     }
00079 
00080     void setViewVolume (const SbViewVolume &vol)
00081     {
00082         inherited::setViewVolume(vol);
00083     }
00084 
00085     void setWorkingSpace (const SbMatrix &space)
00086     {
00087         //inherited::setWorkingSpace(space);
00088         this->worldToScreen = space.inverse();
00089     }
00090 
00091     SbVec3f project(const SbVec2f &point)
00092     {
00093         return inherited::project(point);
00094     }
00095 
00096     SbRotation getRotation(const SbVec3f &point1, const SbVec3f &point2)
00097     {
00098         SbRotation rot = inherited::getRotation(point1, point2);
00099         if (orbit == Trackball)
00100             return rot;
00101 
00102         // 0000333: Turntable camera rotation
00103         SbVec3f axis;
00104         float angle;
00105         rot.getValue(axis, angle);
00106         SbVec3f dif = point1 - point2;
00107         if (fabs(dif[1]) > fabs(dif[0])) {
00108             SbVec3f xaxis(1,0,0);
00109             if (dif[1] < 0)
00110                 angle = -angle;
00111             rot.setValue(xaxis, angle);
00112         }
00113         else {
00114             SbVec3f zaxis(0,0,1);
00115             this->worldToScreen.multDirMatrix(zaxis, zaxis);
00116             if (dif[0] > 0)
00117                 angle = -angle;
00118             rot.setValue(zaxis, angle);
00119         }
00120 
00121         return rot;
00122     }
00123 
00124     void setOrbitStyle(OrbitStyle style)
00125     {
00126         this->orbit = style;
00127     }
00128 
00129     OrbitStyle getOrbitStyle() const
00130     {
00131         return this->orbit;
00132     }
00133 
00134 private:
00135     SbMatrix worldToScreen;
00136     OrbitStyle orbit;
00137 };
00138 
00139 NavigationStyleEvent::NavigationStyleEvent(const Base::Type& s)
00140   : QEvent(QEvent::User), t(s)
00141 {
00142 }
00143 
00144 NavigationStyleEvent::~NavigationStyleEvent()
00145 {
00146 }
00147 
00148 const Base::Type& NavigationStyleEvent::style() const
00149 { 
00150     return t;
00151 }
00152 
00153 #define PRIVATE(ptr) (ptr->pimpl)
00154 #define PUBLIC(ptr) (ptr->pub)
00155 
00156 TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStyle,Base::BaseClass);
00157 
00158 NavigationStyle::NavigationStyle() : viewer(0), mouseSelection(0)
00159 {
00160     PRIVATE(this) = new NavigationStyleP();
00161     PRIVATE(this)->animsensor = new SoTimerSensor(NavigationStyleP::viewAnimationCB, this);
00162     initialize();
00163 }
00164 
00165 NavigationStyle::~NavigationStyle()
00166 {
00167     finalize();
00168     if (PRIVATE(this)->animsensor->isScheduled())
00169         PRIVATE(this)->animsensor->unschedule();
00170     delete PRIVATE(this)->animsensor;
00171     delete PRIVATE(this);
00172 }
00173 
00174 NavigationStyle& NavigationStyle::operator = (const NavigationStyle& ns)
00175 {
00176     this->panningplane = ns.panningplane;
00177     this->menuenabled = ns.menuenabled;
00178     this->spinanimatingallowed = ns.spinanimatingallowed;
00179     return *this;
00180 }
00181 
00182 void NavigationStyle::setViewer(View3DInventorViewer* view)
00183 {
00184     this->viewer = view;
00185 }
00186 
00187 void NavigationStyle::initialize()
00188 {
00189     this->currentmode = NavigationStyle::IDLE;
00190     this->prevRedrawTime = SbTime::getTimeOfDay();
00191     this->spinanimatingallowed = TRUE;
00192     this->spinsamplecounter = 0;
00193     this->spinincrement = SbRotation::identity();
00194     this->spinRotation.setValue(SbVec3f(0, 0, -1), 0);
00195 
00196     // FIXME: use a smaller sphere than the default one to have a larger
00197     // area close to the borders that gives us "z-axis rotation"?
00198     // 19990425 mortene.
00199     this->spinprojector = new FCSphereSheetProjector(SbSphere(SbVec3f(0, 0, 0), 0.8f));
00200     SbViewVolume volume;
00201     volume.ortho(-1, 1, -1, 1, -1, 1);
00202     this->spinprojector->setViewVolume(volume);
00203 
00204     this->log.size = 16;
00205     this->log.position = new SbVec2s [ 16 ];
00206     this->log.time = new SbTime [ 16 ];
00207     this->log.historysize = 0;
00208 
00209     this->menuenabled = TRUE;
00210     this->button1down = FALSE;
00211     this->button2down = FALSE;
00212     this->button3down = FALSE;
00213     this->ctrldown = FALSE;
00214     this->shiftdown = FALSE;
00215     this->altdown = FALSE;
00216     this->invertZoom = App::GetApplication().GetParameterGroupByPath
00217         ("User parameter:BaseApp/Preferences/View")->GetBool("InvertZoom",false);
00218 }
00219 
00220 void NavigationStyle::finalize()
00221 {
00222     delete this->spinprojector;
00223     delete[] this->log.position;
00224     delete[] this->log.time;
00225 }
00226 
00227 void NavigationStyle::interactiveCountInc(void)
00228 {
00229     viewer->interactiveCountInc();
00230 }
00231 
00232 void NavigationStyle::interactiveCountDec(void)
00233 {
00234     viewer->interactiveCountDec();
00235 }
00236 
00237 int NavigationStyle::getInteractiveCount(void) const
00238 {
00239     return viewer->getInteractiveCount();
00240 }
00241 
00242 void NavigationStyle::setOrbitStyle(NavigationStyle::OrbitStyle style)
00243 {
00244     FCSphereSheetProjector* projector = static_cast<FCSphereSheetProjector*>(this->spinprojector);
00245     projector->setOrbitStyle(FCSphereSheetProjector::OrbitStyle(style));
00246 }
00247 
00248 NavigationStyle::OrbitStyle NavigationStyle::getOrbitStyle() const
00249 {
00250     FCSphereSheetProjector* projector = static_cast<FCSphereSheetProjector*>(this->spinprojector);
00251     return NavigationStyle::OrbitStyle(projector->getOrbitStyle());
00252 }
00253 
00254 SbBool NavigationStyle::isViewing(void) const
00255 {
00256     return viewer->isViewing();
00257 }
00258 
00259 void NavigationStyle::setViewing(SbBool enable)
00260 {
00261     viewer->setViewing(enable);
00262 }
00263 
00264 SbBool NavigationStyle::isSeekMode(void) const
00265 {
00266     return viewer->isSeekMode();
00267 }
00268 
00269 void NavigationStyle::setSeekMode(SbBool enable)
00270 {
00271     viewer->setSeekMode(enable);
00272 }
00273 
00274 SbBool NavigationStyle::seekToPoint(const SbVec2s screenpos)
00275 {
00276     return viewer->seekToPoint(screenpos);
00277 }
00278 
00279 void NavigationStyle::seekToPoint(const SbVec3f& scenepos)
00280 {
00281     viewer->seekToPoint(scenepos);
00282 }
00283 
00284 SbBool NavigationStyle::lookAtPoint(const SbVec2s screenpos)
00285 {
00286     SoCamera* cam = viewer->getCamera();
00287     if (cam == 0) return FALSE;
00288 
00289     SoRayPickAction rpaction(viewer->getViewportRegion());
00290     rpaction.setPoint(screenpos);
00291     rpaction.setRadius(2);
00292     rpaction.apply(viewer->getSceneManager()->getSceneGraph());
00293 
00294     SoPickedPoint * picked = rpaction.getPickedPoint();
00295     if (!picked) {
00296         this->interactiveCountInc();
00297         return FALSE;
00298     }
00299 
00300     SbVec3f hitpoint;
00301     hitpoint = picked->getPoint();
00302     lookAtPoint(hitpoint);
00303     return TRUE;
00304 }
00305 
00306 void NavigationStyle::lookAtPoint(const SbVec3f& pos)
00307 {
00308     SoCamera* cam = viewer->getCamera();
00309     if (cam == 0) return;
00310 
00311     // Find global coordinates of focal point.
00312     SbVec3f direction;
00313     cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00314     PRIVATE(this)->focal1 = cam->position.getValue() +
00315                             cam->focalDistance.getValue() * direction;
00316     PRIVATE(this)->focal2 = pos;
00317 
00318     // avoid to interfere with spinning (fixes #3101462)
00319     if (this->isAnimating())
00320         this->stopAnimating();
00321 
00322     if (PRIVATE(this)->animsensor->isScheduled()) {
00323         PRIVATE(this)->animsensor->unschedule();
00324         this->interactiveCountDec();
00325     }
00326 
00327     if (isAnimationEnabled()) {
00328         SbRotation cam_rot = cam->orientation.getValue();
00329         // get the amount of movement
00330         SbVec3f dir1 = direction, dir2;
00331         dir2 = pos - cam->position.getValue();
00332         dir2.normalize();
00333         SbRotation rot(dir1, dir2);
00334         float val = 0.5f*(1.0f + dir1.dot(dir2)); // value in range [0,1]
00335         int div = (int)(val * 20.0f);
00336         int steps = 20-div; // do it with max. 20 steps
00337 
00338         // check whether a movement is required
00339         if (steps > 0) {
00340             PRIVATE(this)->endRotation = cam_rot;
00341             this->spinRotation = cam_rot;
00342             PRIVATE(this)->animationsteps = 5;
00343             PRIVATE(this)->animationdelta = std::max<int>(100/steps, 5);
00344             PRIVATE(this)->animsensor->setBaseTime(SbTime::getTimeOfDay());
00345             PRIVATE(this)->animsensor->schedule();
00346             this->interactiveCountInc();
00347         }
00348         else {
00349             // set to the given position
00350             SbVec3f direction;
00351             cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00352             cam->position = pos - cam->focalDistance.getValue() * direction;
00353         }
00354     }
00355     else {
00356         // set to the given position
00357         SbVec3f direction;
00358         cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00359         cam->position = pos - cam->focalDistance.getValue() * direction;
00360     }
00361 }
00362 
00363 void NavigationStyle::setCameraOrientation(const SbRotation& rot)
00364 {
00365     SoCamera* cam = viewer->getCamera();
00366     if (cam == 0) return;
00367 
00368     // Find global coordinates of focal point.
00369     SbVec3f direction;
00370     cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00371     PRIVATE(this)->focal1 = cam->position.getValue() +
00372                             cam->focalDistance.getValue() * direction;
00373     PRIVATE(this)->focal2 = PRIVATE(this)->focal1;
00374     SoGetBoundingBoxAction action(viewer->getViewportRegion());
00375     action.apply(viewer->getSceneGraph());
00376     SbBox3f box = action.getBoundingBox();
00377     if (!box.isEmpty()) {
00378         rot.multVec(SbVec3f(0, 0, -1), direction);
00379         //float s = (this->focal1 - box.getCenter()).dot(direction);
00380         //this->focal2 = box.getCenter() + s * direction;
00381         // setting the center of the overall bounding box as the future focal point
00382         // seems to be a satisfactory solution
00383         PRIVATE(this)->focal2 = box.getCenter();
00384     }
00385 
00386     // avoid to interfere with spinning (fixes #3101462)
00387     if (this->isAnimating())
00388         this->stopAnimating();
00389 
00390     if (PRIVATE(this)->animsensor->isScheduled()) {
00391         PRIVATE(this)->animsensor->unschedule();
00392         this->interactiveCountDec();
00393     }
00394 
00395     if (isAnimationEnabled()) {
00396         // get the amount of movement
00397         SbVec3f dir1, dir2;
00398         SbRotation cam_rot = cam->orientation.getValue();
00399         cam_rot.multVec(SbVec3f(0, 0, -1), dir1);
00400         rot.multVec(SbVec3f(0, 0, -1), dir2);
00401         float val = 0.5f*(1.0f + dir1.dot(dir2)); // value in range [0,1]
00402         int div = (int)(val * 20.0f);
00403         int steps = 20-div; // do it with max. 20 steps
00404 
00405         // check whether a movement is required
00406         if (steps > 0) {
00407             PRIVATE(this)->endRotation = rot; // this is the final camera orientation
00408             this->spinRotation = cam_rot;
00409             PRIVATE(this)->animationsteps = 5;
00410             PRIVATE(this)->animationdelta = std::max<int>(100/steps, 5);
00411             PRIVATE(this)->animsensor->setBaseTime(SbTime::getTimeOfDay());
00412             PRIVATE(this)->animsensor->schedule();
00413             this->interactiveCountInc();
00414         }
00415         else {
00416             // due to possible round-off errors make sure that the
00417             // exact orientation is set
00418             cam->orientation.setValue(rot);
00419         }
00420     }
00421     else {
00422         // set to the given rotation
00423         cam->orientation.setValue(rot);
00424         cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00425         cam->position = PRIVATE(this)->focal2 - cam->focalDistance.getValue() * direction;
00426     }
00427 }
00428 
00429 void NavigationStyleP::viewAnimationCB(void * data, SoSensor * sensor)
00430 {
00431     NavigationStyle* that = reinterpret_cast<NavigationStyle*>(data);
00432     if (PRIVATE(that)->animationsteps > 0) {
00433         // here the camera rotates from the current rotation to a given
00434         // rotation (e.g. the standard views). To get this movement animated
00435         // we calculate an interpolated rotation and update the view after
00436         // each step
00437         float step = std::min<float>((float)PRIVATE(that)->animationsteps/100.0f, 1.0f);
00438         SbRotation slerp = SbRotation::slerp(that->spinRotation, PRIVATE(that)->endRotation, step);
00439         SbVec3f focalpoint = (1.0f-step)*PRIVATE(that)->focal1 + step*PRIVATE(that)->focal2;
00440         SoCamera* cam = that->viewer->getCamera();
00441         SbVec3f direction;
00442         cam->orientation.setValue(slerp);
00443         cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00444         cam->position = focalpoint - cam->focalDistance.getValue() * direction;
00445 
00446         PRIVATE(that)->animationsteps += PRIVATE(that)->animationdelta;
00447         if (PRIVATE(that)->animationsteps > 100) {
00448             // now we have reached the end of the movement
00449             PRIVATE(that)->animationsteps=0;
00450             PRIVATE(that)->animsensor->unschedule();
00451             that->interactiveCountDec();
00452             // set to the actual given rotation
00453             cam->orientation.setValue(PRIVATE(that)->endRotation);
00454             cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00455             cam->position = PRIVATE(that)->focal2 - cam->focalDistance.getValue() * direction;
00456         }
00457     }
00458 }
00459 
00460 void NavigationStyle::boxZoom(const SbBox2s& box)
00461 {
00462     SoCamera* cam = viewer->getCamera();
00463     if (!cam) return; // no camera 
00464     const SbViewportRegion & vp = viewer->getViewportRegion();
00465     SbViewVolume vv = cam->getViewVolume(vp.getViewportAspectRatio());
00466 
00467     short sizeX,sizeY;
00468     box.getSize(sizeX, sizeY);
00469     SbVec2s size = vp.getViewportSizePixels();
00470 
00471     // The bbox must not be empty i.e. width and length is zero, but it is possible that
00472     // either width or length is zero
00473     if (sizeX == 0 && sizeY == 0) 
00474         return;
00475 
00476     // Get the new center in normalized pixel coordinates
00477     short xmin,xmax,ymin,ymax;
00478     box.getBounds(xmin,ymin,xmax,ymax);
00479     const SbVec2f center((float) ((xmin+xmax)/2) / (float) SoQtMax((int)(size[0] - 1), 1),
00480                          (float) (size[1]-(ymin+ymax)/2) / (float) SoQtMax((int)(size[1] - 1), 1));
00481 
00482     SbPlane plane = vv.getPlane(cam->focalDistance.getValue());
00483     panCamera(cam,vp.getViewportAspectRatio(),plane, SbVec2f(0.5,0.5), center);
00484 
00485     // Set height or height angle of the camera
00486     float scaleX = (float)sizeX/(float)size[0];
00487     float scaleY = (float)sizeY/(float)size[1];
00488     float scale = std::max<float>(scaleX, scaleY);
00489     if (cam && cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
00490         float height = static_cast<SoOrthographicCamera*>(cam)->height.getValue() * scale;
00491         static_cast<SoOrthographicCamera*>(cam)->height = height;
00492     }
00493     else if (cam && cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
00494         float height = static_cast<SoPerspectiveCamera*>(cam)->heightAngle.getValue() / 2.0f;
00495         height = 2.0f * atan(tan(height) * scale);
00496         static_cast<SoPerspectiveCamera*>(cam)->heightAngle = height;
00497     }
00498 }
00499 
00500 void NavigationStyle::viewAll()
00501 {
00502     // Get the bounding box of the scene
00503     SoGetBoundingBoxAction action(viewer->getViewportRegion());
00504     action.apply(viewer->getSceneGraph());
00505     SbBox3f box = action.getBoundingBox();
00506     if (box.isEmpty()) return;
00507 
00508 #if 0
00509     // check whether the box is very wide or tall, if not do nothing
00510     float box_width, box_height, box_depth;
00511     box.getSize( box_width, box_height, box_depth );
00512     if (box_width < 5.0f*box_height && box_width < 5.0f*box_depth && 
00513         box_height < 5.0f*box_width && box_height < 5.0f*box_depth && 
00514         box_depth < 5.0f*box_width && box_depth < 5.0f*box_height )
00515         return;
00516 #endif
00517 
00518     SoCamera* cam = viewer->getCamera();
00519     if (!cam) return;
00520 
00521     SbViewVolume  vol = cam->getViewVolume();
00522     if (vol.ulf == vol.llf)
00523         return; // empty frustum (no view up vector defined)
00524     SbVec2f s = vol.projectBox(box);
00525     SbVec2s size = viewer->getSize();
00526 
00527     SbVec3f pt1, pt2, pt3, tmp;
00528     vol.projectPointToLine( SbVec2f(0.0f,0.0f), pt1, tmp );
00529     vol.projectPointToLine( SbVec2f(s[0],0.0f), pt2, tmp );
00530     vol.projectPointToLine( SbVec2f(0.0f,s[1]), pt3, tmp );
00531 
00532     float cam_width = (pt2-pt1).length();
00533     float cam_height = (pt3-pt1).length();
00534 
00535     // add a small border
00536     cam_height = 1.08f * std::max<float>((cam_width*(float)size[1])/(float)size[0],cam_height);
00537 
00538     float aspect = cam->aspectRatio.getValue();
00539 
00540     if (cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
00541         // set the new camera position dependent on the occupied space of projected bounding box
00542         //SbVec3f direction = cam->position.getValue() - box.getCenter();
00543         //float movelength = direction.length();
00544         //direction.normalize();
00545         //float fRatio = getViewportRegion().getViewportAspectRatio();
00546         //if ( fRatio > 1.0f ) {
00547         //  float factor = std::max<float>(s[0]/fRatio,s[1]);
00548         //  movelength = factor * movelength;
00549         //}
00550         //else {
00551         //    float factor = std::max<float>(s[0],s[1]/fRatio);
00552         //    movelength = factor * movelength;
00553         //}
00554         //cam->position.setValue(box.getCenter() + direction * movelength);
00555     }
00556     else if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
00557         SoOrthographicCamera* ocam = (SoOrthographicCamera *)cam;  // safe downward cast, knows the type
00558         if (aspect < 1.0f)
00559             ocam->height = cam_height / aspect;
00560         else
00561             ocam->height = cam_height;
00562     }
00563 }
00564 
00568 void NavigationStyle::reorientCamera(SoCamera * cam, const SbRotation & rot)
00569 {
00570     if (cam == NULL) return;
00571 
00572     // Find global coordinates of focal point.
00573     SbVec3f direction;
00574     cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00575     SbVec3f focalpoint = cam->position.getValue() +
00576                          cam->focalDistance.getValue() * direction;
00577 
00578     // Set new orientation value by accumulating the new rotation.
00579     cam->orientation = rot * cam->orientation.getValue();
00580 
00581     // Reposition camera so we are still pointing at the same old focal point.
00582     cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00583     cam->position = focalpoint - cam->focalDistance.getValue() * direction;
00584 }
00585 
00586 void NavigationStyle::panCamera(SoCamera * cam, float aspectratio, const SbPlane & panplane,
00587                                 const SbVec2f & currpos, const SbVec2f & prevpos)
00588 {
00589     if (cam == NULL) return; // can happen for empty scenegraph
00590     if (currpos == prevpos) return; // useless invocation
00591 
00592 
00593     // Find projection points for the last and current mouse coordinates.
00594     SbViewVolume vv = cam->getViewVolume(aspectratio);
00595     SbLine line;
00596     vv.projectPointToLine(currpos, line);
00597     SbVec3f current_planept;
00598     panplane.intersect(line, current_planept);
00599     vv.projectPointToLine(prevpos, line);
00600     SbVec3f old_planept;
00601     panplane.intersect(line, old_planept);
00602 
00603     // Reposition camera according to the vector difference between the
00604     // projected points.
00605     cam->position = cam->position.getValue() - (current_planept - old_planept);
00606 }
00607 
00608 void NavigationStyle::pan(SoCamera* camera)
00609 {
00610     // The plane we're projecting the mouse coordinates to get 3D
00611     // coordinates should stay the same during the whole pan
00612     // operation, so we should calculate this value here.
00613     if (camera == NULL) { // can happen for empty scenegraph
00614         this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0);
00615     }
00616     else {
00617         const SbViewportRegion & vp = viewer->getViewportRegion();
00618         SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
00619         this->panningplane = vv.getPlane(camera->focalDistance.getValue());
00620     }
00621 }
00622 
00623 void NavigationStyle::panToCenter(const SbPlane & pplane, const SbVec2f & currpos)
00624 {
00625     const SbViewportRegion & vp = viewer->getViewportRegion();
00626     float ratio = vp.getViewportAspectRatio();
00627     panCamera(viewer->getCamera(), ratio, pplane, SbVec2f(0.5,0.5), currpos);
00628 }
00629 
00634 void NavigationStyle::zoom(SoCamera * cam, float diffvalue)
00635 {
00636     if (cam == NULL) return; // can happen for empty scenegraph
00637     SoType t = cam->getTypeId();
00638     SbName tname = t.getName();
00639 
00640     // This will be in the range of <0, ->>.
00641     float multiplicator = float(exp(diffvalue));
00642 
00643     if (t.isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
00644 
00645         // Since there's no perspective, "zooming" in the original sense
00646         // of the word won't have any visible effect. So we just increase
00647         // or decrease the field-of-view values of the camera instead, to
00648         // "shrink" the projection size of the model / scene.
00649         SoOrthographicCamera * oc = (SoOrthographicCamera *)cam;
00650         oc->height = oc->height.getValue() * multiplicator;
00651 
00652     }
00653     else {
00654         // FrustumCamera can be found in the SmallChange CVS module (it's
00655         // a camera that lets you specify (for instance) an off-center
00656         // frustum (similar to glFrustum())
00657         if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) &&
00658             tname != "FrustumCamera") {
00659  /*         static SbBool first = TRUE;
00660             if (first) {
00661                 SoDebugError::postWarning("SoGuiFullViewerP::zoom",
00662                                           "Unknown camera type, "
00663                                           "will zoom by moving position, but this might not be correct.");
00664                 first = FALSE;
00665             }*/
00666         }
00667 
00668         const float oldfocaldist = cam->focalDistance.getValue();
00669         const float newfocaldist = oldfocaldist * multiplicator;
00670 
00671         SbVec3f direction;
00672         cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
00673 
00674         const SbVec3f oldpos = cam->position.getValue();
00675         const SbVec3f newpos = oldpos + (newfocaldist - oldfocaldist) * -direction;
00676 
00677         // This catches a rather common user interface "buglet": if the
00678         // user zooms the camera out to a distance from origo larger than
00679         // what we still can safely do floating point calculations on
00680         // (i.e. without getting NaN or Inf values), the faulty floating
00681         // point values will propagate until we start to get debug error
00682         // messages and eventually an assert failure from core Coin code.
00683         //
00684         // With the below bounds check, this problem is avoided.
00685         //
00686         // (But note that we depend on the input argument ''diffvalue'' to
00687         // be small enough that zooming happens gradually. Ideally, we
00688         // should also check distorigo with isinf() and isnan() (or
00689         // inversely; isinfite()), but those only became standardized with
00690         // C99.)
00691         const float distorigo = newpos.length();
00692         // sqrt(FLT_MAX) == ~ 1e+19, which should be both safe for further
00693         // calculations and ok for the end-user and app-programmer.
00694         if (distorigo > float(sqrt(FLT_MAX))) {
00695         }
00696         else {
00697             cam->position = newpos;
00698             cam->focalDistance = newfocaldist;
00699         }
00700     }
00701 }
00702 
00703 // Calculate a zoom/dolly factor from the difference of the current
00704 // cursor position and the last.
00705 void NavigationStyle::zoomByCursor(const SbVec2f & thispos, const SbVec2f & prevpos)
00706 {
00707     // There is no "geometrically correct" value, 20 just seems to give
00708     // about the right "feel".
00709     zoom(viewer->getCamera(), (thispos[1] - prevpos[1]) * 10.0f/*20.0f*/);
00710 }
00711 
00715 void NavigationStyle::spin(const SbVec2f & pointerpos)
00716 {
00717     if (this->log.historysize < 2) return;
00718     assert(this->spinprojector != NULL);
00719 
00720     const SbViewportRegion & vp = viewer->getViewportRegion();
00721     SbVec2s glsize(vp.getViewportSizePixels());
00722     SbVec2f lastpos;
00723     lastpos[0] = float(this->log.position[1][0]) / float(SoQtMax((int)(glsize[0]-1), 1));
00724     lastpos[1] = float(this->log.position[1][1]) / float(SoQtMax((int)(glsize[1]-1), 1));
00725 
00726     // 0000333: Turntable camera rotation
00727     SbMatrix mat;
00728     viewer->getCamera()->orientation.getValue().getValue(mat);
00729     this->spinprojector->setWorkingSpace(mat);
00730 
00731     this->spinprojector->project(lastpos);
00732     SbRotation r;
00733     this->spinprojector->projectAndGetRotation(pointerpos, r);
00734     r.invert();
00735     this->reorientCamera(viewer->getCamera(), r);
00736 
00737     // Calculate an average angle magnitude value to make the transition
00738     // to a possible spin animation mode appear smooth.
00739 
00740     SbVec3f dummy_axis, newaxis;
00741     float acc_angle, newangle;
00742     this->spinincrement.getValue(dummy_axis, acc_angle);
00743     acc_angle *= this->spinsamplecounter; // weight
00744     r.getValue(newaxis, newangle);
00745     acc_angle += newangle;
00746 
00747     this->spinsamplecounter++;
00748     acc_angle /= this->spinsamplecounter;
00749     // FIXME: accumulate and average axis vectors aswell? 19990501 mortene.
00750     this->spinincrement.setValue(newaxis, acc_angle);
00751 
00752     // Don't carry too much baggage, as that'll give unwanted results
00753     // when the user quickly trigger (as in "click-drag-release") a spin
00754     // animation.
00755     if (this->spinsamplecounter > 3) this->spinsamplecounter = 3;
00756 }
00757 
00758 SbBool NavigationStyle::doSpin()
00759 {
00760     if (this->log.historysize >= 3) {
00761         SbTime stoptime = (SbTime::getTimeOfDay() - this->log.time[0]);
00762         if (this->spinanimatingallowed && stoptime.getValue() < 0.100) {
00763             const SbViewportRegion & vp = viewer->getViewportRegion();
00764             const SbVec2s glsize(vp.getViewportSizePixels());
00765             SbVec3f from = this->spinprojector->project(SbVec2f(float(this->log.position[2][0]) / float(SoQtMax(glsize[0]-1, 1)),
00766                                                                 float(this->log.position[2][1]) / float(SoQtMax(glsize[1]-1, 1))));
00767             SbVec3f to = this->spinprojector->project(this->lastmouseposition);
00768             SbRotation rot = this->spinprojector->getRotation(from, to);
00769 
00770             SbTime delta = (this->log.time[0] - this->log.time[2]);
00771             double deltatime = delta.getValue();
00772             rot.invert();
00773             rot.scaleAngle(float(0.200 / deltatime));
00774 
00775             SbVec3f axis;
00776             float radians;
00777             rot.getValue(axis, radians);
00778             if ((radians > 0.01f) && (deltatime < 0.300)) {
00779                 this->spinRotation = rot;
00780                 return TRUE;
00781             }
00782         }
00783     }
00784 
00785     return FALSE;
00786 }
00787 
00788 void NavigationStyle::updateAnimation()
00789 {
00790     SbTime now = SbTime::getTimeOfDay();
00791     double secs = now.getValue() -  prevRedrawTime.getValue();
00792     this->prevRedrawTime = now;
00793 
00794     if (this->isAnimating()) {
00795         // here the camera rotates around a fix axis
00796         SbRotation deltaRotation = this->spinRotation;
00797         deltaRotation.scaleAngle(secs * 5.0);
00798         this->reorientCamera(viewer->getCamera(), deltaRotation);
00799     }
00800 }
00801 
00802 void NavigationStyle::redraw()
00803 {
00804     if (mouseSelection)
00805         mouseSelection->redraw();
00806 }
00807 
00808 SbBool NavigationStyle::handleEventInForeground(const SoEvent* const e)
00809 {
00810     SoHandleEventAction action(viewer->getViewportRegion());
00811     action.setEvent(e);
00812     action.apply(viewer->foregroundroot);
00813     return action.isHandled();
00814 }
00815 
00823 void
00824 NavigationStyle::setAnimationEnabled(const SbBool enable)
00825 {
00826     this->spinanimatingallowed = enable;
00827     if (!enable && this->isAnimating()) { this->stopAnimating(); }
00828 }
00829 
00835 SbBool
00836 NavigationStyle::isAnimationEnabled(void) const
00837 {
00838     return this->spinanimatingallowed;
00839 }
00840 
00845 SbBool NavigationStyle::isAnimating(void) const
00846 {
00847     return this->currentmode == NavigationStyle::SPINNING;
00848 }
00849 
00854 void NavigationStyle::startAnimating(const SbVec3f& axis, float velocity)
00855 {
00856     if (!isAnimationEnabled()) return;
00857 
00858     this->prevRedrawTime = SbTime::getTimeOfDay();
00859     this->spinincrement = SbRotation::identity();
00860     SbRotation rot;
00861     rot.setValue(axis, velocity);
00862 
00863     this->setViewing(true);
00864     this->setViewingMode(NavigationStyle::SPINNING);
00865     this->spinRotation = rot;
00866 }
00867 
00868 void NavigationStyle::stopAnimating(void)
00869 {
00870     if (this->currentmode != NavigationStyle::SPINNING) {
00871         return;
00872     }
00873     this->setViewingMode(this->isViewing() ? 
00874         NavigationStyle::IDLE : NavigationStyle::INTERACT);
00875 }
00876 
00877 void NavigationStyle::setZoomInverted(SbBool on)
00878 {
00879     this->invertZoom = on;
00880 }
00881 
00882 SbBool NavigationStyle::isZoomInverted() const
00883 {
00884     return this->invertZoom;
00885 }
00886 
00887 void NavigationStyle::startSelection(AbstractMouseSelection* mouse)
00888 {
00889     if (!mouse)
00890         return;
00891   
00892     mouseSelection = mouse;
00893     mouseSelection->grabMouseModel(viewer);
00894 }
00895 
00896 void NavigationStyle::startSelection(NavigationStyle::SelectionMode mode)
00897 {
00898     if (mouseSelection)
00899         return;
00900     if (isSelecting())
00901         stopSelection();
00902   
00903     switch (mode)
00904     {
00905     case Lasso:
00906         mouseSelection = new PolyPickerSelection();
00907         break;
00908     case Rectangle:
00909         mouseSelection = new RectangleSelection();
00910         break;
00911     case BoxZoom:
00912         mouseSelection = new BoxZoomSelection();
00913         break;
00914     case Clip:
00915         mouseSelection = new PolyClipSelection();
00916         break;
00917     default:
00918         break;
00919     }
00920 
00921     if (mouseSelection)
00922         mouseSelection->grabMouseModel(viewer);
00923 }
00924 
00925 void NavigationStyle::stopSelection()
00926 {
00927     pcPolygon.clear();
00928     delete mouseSelection; 
00929     mouseSelection = 0;
00930 }
00931 
00932 SbBool NavigationStyle::isSelecting() const
00933 {
00934     return (mouseSelection ? TRUE : FALSE);
00935 }
00936 
00937 const std::vector<SbVec2s>& NavigationStyle::getPolygon(SbBool* clip_inner) const
00938 {
00939     if (clip_inner)
00940         *clip_inner = this->clipInner;
00941     return pcPolygon;
00942 }
00943 
00944 // This method adds another point to the mouse location log, used for spin
00945 // animation calculations.
00946 void NavigationStyle::addToLog(const SbVec2s pos, const SbTime time)
00947 {
00948     // In case someone changes the const size setting at the top of this
00949     // file too small.
00950     assert (this->log.size > 2 && "mouse log too small!");
00951 
00952     if (this->log.historysize > 0 && pos == this->log.position[0]) {
00953 #if SOQt_DEBUG && 0 // debug
00954         // This can at least happen under SoQt.
00955         SoDebugError::postInfo("NavigationStyle::addToLog", "got position already!");
00956 #endif // debug
00957         return;
00958     }
00959 
00960     int lastidx = this->log.historysize;
00961     // If we've filled up the log, we should throw away the last item:
00962     if (lastidx == this->log.size) { lastidx--; }
00963 
00964     assert(lastidx < this->log.size);
00965     for (int i = lastidx; i > 0; i--) {
00966         this->log.position[i] = this->log.position[i-1];
00967         this->log.time[i] = this->log.time[i-1];
00968     }
00969 
00970     this->log.position[0] = pos;
00971     this->log.time[0] = time;
00972     if (this->log.historysize < this->log.size)
00973         this->log.historysize += 1;
00974 }
00975 
00976 // This method "clears" the mouse location log, used for spin
00977 // animation calculations.
00978 void NavigationStyle::clearLog(void)
00979 {
00980     this->log.historysize = 0;
00981 }
00982 
00983 // The viewer is a state machine, and all changes to the current state
00984 // are made through this call.
00985 void NavigationStyle::setViewingMode(const ViewerMode newmode)
00986 {
00987     const ViewerMode oldmode = this->currentmode;
00988     if (newmode == oldmode) { return; }
00989 
00990     switch (newmode) {
00991     case DRAGGING:
00992         // Set up initial projection point for the projector object when
00993         // first starting a drag operation.
00994         this->spinprojector->project(this->lastmouseposition);
00995         this->interactiveCountInc();
00996         this->clearLog();
00997         break;
00998 
00999     case SPINNING:
01000         this->interactiveCountInc();
01001         viewer->scheduleRedraw();
01002         break;
01003 
01004     case PANNING:
01005         pan(viewer->getCamera());
01006         this->interactiveCountInc();
01007         break;
01008 
01009     case ZOOMING:
01010         this->interactiveCountInc();
01011         break;
01012 
01013     case BOXZOOM:
01014         this->interactiveCountInc();
01015         break;
01016 
01017     default: // include default to avoid compiler warnings.
01018         break;
01019     }
01020 
01021     switch (oldmode) {
01022     case SPINNING:
01023     case DRAGGING:
01024     case PANNING:
01025     case ZOOMING:
01026     case BOXZOOM:
01027         this->interactiveCountDec();
01028         break;
01029 
01030     default:
01031         break;
01032     }
01033 
01034     viewer->setCursorRepresentation(newmode);
01035     this->currentmode = newmode;
01036 }
01037 
01038 int NavigationStyle::getViewingMode() const
01039 {
01040     return (int)this->currentmode;
01041 }
01042 
01043 SbBool NavigationStyle::processEvent(const SoEvent * const ev)
01044 {
01045     // If we're in picking mode then all events must be redirected to the
01046     // appropriate mouse model.
01047     if (mouseSelection) {
01048         int hd=mouseSelection->handleEvent(ev,viewer->getViewportRegion());
01049         if (hd==AbstractMouseSelection::Continue||
01050             hd==AbstractMouseSelection::Restart) {
01051             return TRUE;
01052         }
01053         else if (hd==AbstractMouseSelection::Finish) {
01054             pcPolygon = mouseSelection->getPositions();
01055             clipInner = mouseSelection->isInner();
01056             delete mouseSelection; mouseSelection = 0;
01057             return NavigationStyle::processSoEvent(ev);
01058         }
01059         else if (hd==AbstractMouseSelection::Cancel) {
01060             pcPolygon.clear();
01061             delete mouseSelection; mouseSelection = 0;
01062             return NavigationStyle::processSoEvent(ev);
01063         }
01064     }
01065 
01066     const ViewerMode curmode = this->currentmode;
01067 
01068     SbBool processed = FALSE;
01069     processed = this->processSoEvent(ev);
01070 
01071     // check for left click without selecting something
01072     if (curmode == NavigationStyle::SELECTION && !processed) {
01073         if (ev->getTypeId().isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
01074             SoMouseButtonEvent * const e = (SoMouseButtonEvent *) ev;
01075             if (SoMouseButtonEvent::isButtonReleaseEvent(e,SoMouseButtonEvent::BUTTON1)) {
01076                 Gui::Selection().clearSelection();
01077             }
01078         }
01079     }
01080 
01081     return processed;
01082 }
01083 
01084 SbBool NavigationStyle::processSoEvent(const SoEvent * const ev)
01085 {
01086     return viewer->processSoEventBase(ev);
01087 }
01088 
01089 void NavigationStyle::setPopupMenuEnabled(const SbBool on)
01090 {
01091     this->menuenabled = on;
01092 }
01093 
01094 SbBool NavigationStyle::isPopupMenuEnabled(void) const
01095 {
01096     return this->menuenabled;
01097 }
01098 
01099 void NavigationStyle::openPopupMenu(const SbVec2s& position)
01100 {
01101     // ask workbenches and view provider, ...
01102     MenuItem* view = new MenuItem;
01103     Gui::Application::Instance->setupContextMenu("View", view);
01104 
01105     QMenu contextMenu(viewer->getGLWidget());
01106     QMenu subMenu;
01107     QActionGroup subMenuGroup(&subMenu);
01108     subMenuGroup.setExclusive(true);
01109     subMenu.setTitle(QObject::tr("Navigation styles"));
01110 
01111     MenuManager::getInstance()->setupContextMenu(view, contextMenu);
01112     contextMenu.addMenu(&subMenu);
01113 
01114     // add submenu at the end to select navigation style
01115     QRegExp rx(QString::fromAscii("^\\w+::(\\w+)Navigation\\w+$"));
01116     std::vector<Base::Type> types;
01117     Base::Type::getAllDerivedFrom(UserNavigationStyle::getClassTypeId(), types);
01118     for (std::vector<Base::Type>::iterator it = types.begin(); it != types.end(); ++it) {
01119         if (*it != UserNavigationStyle::getClassTypeId()) {
01120             QString data = QString::fromAscii(it->getName());
01121             QString name = data.mid(data.indexOf(QLatin1String("::"))+2);
01122             if (rx.indexIn(data) > -1) {
01123                 name = QObject::tr("%1 navigation").arg(rx.cap(1));
01124                 QAction* item = subMenuGroup.addAction(name);
01125                 item->setData(QByteArray(it->getName()));
01126                 item->setCheckable(true);
01127                 if (*it == this->getTypeId())
01128                     item->setChecked(true);
01129                 subMenu.addAction(item);
01130             }
01131         }
01132     }
01133 
01134     delete view;
01135     QAction* used = contextMenu.exec(QCursor::pos());
01136     if (used && subMenuGroup.actions().indexOf(used) >= 0 && used->isChecked()) {
01137         QByteArray type = used->data().toByteArray();
01138         QWidget* widget = viewer->getWidget();
01139         while (widget && !widget->inherits("Gui::View3DInventor"))
01140             widget = widget->parentWidget();
01141         if (widget) {
01142             // this is the widget where the viewer is embedded
01143             Base::Type style = Base::Type::fromName((const char*)type);
01144             if (style != this->getTypeId()) {
01145                 QEvent* event = new NavigationStyleEvent(style);
01146                 QApplication::postEvent(widget, event);
01147             }
01148         }
01149     }
01150 }
01151 
01152 // ----------------------------------------------------------------------------------
01153 
01154 TYPESYSTEM_SOURCE_ABSTRACT(Gui::UserNavigationStyle,Gui::NavigationStyle);

Generated on Wed Nov 23 19:00:24 2011 for FreeCAD by  doxygen 1.6.1