FreeCAD

Форк
0
/
NavigationStyle.cpp 
1806 строк · 59.1 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <Inventor/SbViewportRegion.h>
26
# include <Inventor/SoPickedPoint.h>
27
# include <Inventor/actions/SoGetBoundingBoxAction.h>
28
# include <Inventor/errors/SoDebugError.h>
29
# include <Inventor/nodes/SoSeparator.h>
30
# include <Inventor/nodes/SoCamera.h>
31
# include <Inventor/nodes/SoOrthographicCamera.h>
32
# include <Inventor/nodes/SoPerspectiveCamera.h>
33
# include <Inventor/projectors/SbSphereSheetProjector.h>
34
# include <QAction>
35
# include <QActionGroup>
36
# include <QApplication>
37
# include <QByteArray>
38
# include <QCursor>
39
# include <QMenu>
40
#endif
41

42
#include <App/Application.h>
43

44
#include "NavigationStyle.h"
45
#include "Application.h"
46
#include "MenuManager.h"
47
#include "MouseSelection.h"
48
#include "NavigationAnimator.h"
49
#include "NavigationAnimation.h"
50
#include "SoMouseWheelEvent.h"
51
#include "View3DInventorViewer.h"
52

53
using namespace Gui;
54

55
class FCSphereSheetProjector : public SbSphereSheetProjector {
56
    using inherited = SbSphereSheetProjector;
57

58
public:
59
    enum OrbitStyle {
60
        Turntable,
61
        Trackball,
62
        FreeTurntable
63
    };
64

65
    FCSphereSheetProjector(const SbSphere & sph, const SbBool orienttoeye = true)
66
        : SbSphereSheetProjector(sph, orienttoeye)
67
    {
68
    }
69

70
    void setViewVolume (const SbViewVolume &vol) override
71
    {
72
        inherited::setViewVolume(vol);
73
    }
74

75
    void setWorkingSpace (const SbMatrix &space) override
76
    {
77
        //inherited::setWorkingSpace(space);
78
        this->worldToScreen = space.inverse();
79
    }
80

81
    SbVec3f project(const SbVec2f &point) override
82
    {
83
        return inherited::project(point);
84
    }
85

86
    SbRotation getRotation(const SbVec3f &point1, const SbVec3f &point2) override
87
    {
88
        SbRotation rot = inherited::getRotation(point1, point2);
89
        if (orbit == Turntable) {
90
            return getTurntable(rot, point1, point2);
91
        }
92
        if (orbit == FreeTurntable) {
93
            return getFreeTurntable(point1, point2);
94
        }
95

96
        return rot;
97
    }
98

99
    void setOrbitStyle(OrbitStyle style)
100
    {
101
        this->orbit = style;
102
    }
103

104
    OrbitStyle getOrbitStyle() const
105
    {
106
        return this->orbit;
107
    }
108

109
private:
110
    SbRotation getTurntable(SbRotation rot, const SbVec3f &point1, const SbVec3f &point2) const
111
    {
112
        // 0000333: Turntable camera rotation
113
        SbVec3f axis;
114
        float angle{};
115
        rot.getValue(axis, angle);
116
        SbVec3f dif = point1 - point2;
117
        if (fabs(dif[1]) > fabs(dif[0])) {
118
            SbVec3f xaxis(1,0,0);
119
            if (dif[1] < 0) {
120
                angle = -angle;
121
            }
122
            rot.setValue(xaxis, angle);
123
        }
124
        else {
125
            SbVec3f zaxis(0,0,1);
126
            this->worldToScreen.multDirMatrix(zaxis, zaxis);
127
            if (zaxis[1] < 0) {
128
                if (dif[0] < 0) {
129
                    angle = -angle;
130
                }
131
            }
132
            else {
133
                if (dif[0] > 0) {
134
                    angle = -angle;
135
                }
136
            }
137
            rot.setValue(zaxis, angle);
138
        }
139

140
        return rot;
141
    }
142

143
    SbRotation getFreeTurntable(const SbVec3f &point1, const SbVec3f &point2) const
144
    {
145
        // Turntable without constraints
146
        SbRotation zrot;
147
        SbRotation xrot;
148
        SbVec3f dif = point1 - point2;
149

150
        SbVec3f zaxis(1,0,0);
151
        zrot.setValue(zaxis, dif[1]);
152

153
        SbVec3f xaxis(0,0,1);
154
        this->worldToScreen.multDirMatrix(xaxis, xaxis);
155
        xrot.setValue(xaxis, -dif[0]);
156

157
        return zrot * xrot;
158
    }
159

160
private:
161
    SbMatrix worldToScreen;
162
    OrbitStyle orbit{Trackball};
163
};
164

165
NavigationStyleEvent::NavigationStyleEvent(const Base::Type& s)
166
  : QEvent(QEvent::User), t(s)
167
{
168
}
169

170
NavigationStyleEvent::~NavigationStyleEvent() = default;
171

172
const Base::Type& NavigationStyleEvent::style() const
173
{
174
    return t;
175
}
176

177
TYPESYSTEM_SOURCE_ABSTRACT(Gui::NavigationStyle,Base::BaseClass)
178

179
NavigationStyle::NavigationStyle() : viewer(nullptr), mouseSelection(nullptr)
180
{
181
    this->rotationCenterMode = NavigationStyle::RotationCenterMode::ScenePointAtCursor
182
        | NavigationStyle::RotationCenterMode::FocalPointAtCursor;
183
    initialize();
184
}
185

186
NavigationStyle::~NavigationStyle()
187
{
188
    finalize();
189
    delete this->animator;
190
}
191

192
NavigationStyle& NavigationStyle::operator = (const NavigationStyle& ns)
193
{
194
    this->panningplane = ns.panningplane;
195
    this->menuenabled = ns.menuenabled;
196
    this->animationEnabled = ns.animationEnabled;
197
    this->spinningAnimationEnabled = ns.spinningAnimationEnabled;
198
    static_cast<FCSphereSheetProjector*>(this->spinprojector)->setOrbitStyle
199
        (static_cast<FCSphereSheetProjector*>(ns.spinprojector)->getOrbitStyle());
200
    return *this;
201
}
202

203
void NavigationStyle::setViewer(View3DInventorViewer* view)
204
{
205
    this->viewer = view;
206
}
207

208
void NavigationStyle::initialize()
209
{
210
    this->animator = new NavigationAnimator();
211

212
    this->sensitivity = 2.0f;
213
    this->resetcursorpos = false;
214
    this->currentmode = NavigationStyle::IDLE;
215
    this->animationEnabled = true;
216
    this->spinningAnimationEnabled = false;
217
    this->spinsamplecounter = 0;
218
    this->spinincrement = SbRotation::identity();
219
    this->rotationCenterFound = false;
220

221
    // FIXME: use a smaller sphere than the default one to have a larger
222
    // area close to the borders that gives us "z-axis rotation"?
223
    // 19990425 mortene.
224
    this->spinprojector = new FCSphereSheetProjector(SbSphere(SbVec3f(0, 0, 0), 0.8f));
225
    SbViewVolume volume;
226
    volume.ortho(-1, 1, -1, 1, -1, 1);
227
    this->spinprojector->setViewVolume(volume);
228

229
    this->log.size = 16;
230
    this->log.position = new SbVec2s [ 16 ];
231
    this->log.time = new SbTime [ 16 ];
232
    this->log.historysize = 0;
233

234
    this->menuenabled = true;
235
    this->button1down = false;
236
    this->button2down = false;
237
    this->button3down = false;
238
    this->ctrldown = false;
239
    this->shiftdown = false;
240
    this->altdown = false;
241
    this->invertZoom = App::GetApplication().GetParameterGroupByPath
242
        ("User parameter:BaseApp/Preferences/View")->GetBool("InvertZoom",true);
243
    this->zoomAtCursor = App::GetApplication().GetParameterGroupByPath
244
        ("User parameter:BaseApp/Preferences/View")->GetBool("ZoomAtCursor",true);
245
    this->zoomStep = App::GetApplication().GetParameterGroupByPath
246
        ("User parameter:BaseApp/Preferences/View")->GetFloat("ZoomStep",0.2f);
247
    long mode = App::GetApplication().GetParameterGroupByPath
248
        ("User parameter:BaseApp/Preferences/View")->GetInt("RotationMode", 1);
249
    if (mode == 0) {
250
        setRotationCenterMode(NavigationStyle::RotationCenterMode::WindowCenter);
251
    }
252
    else if (mode == 1) {
253
        setRotationCenterMode(NavigationStyle::RotationCenterMode::ScenePointAtCursor |
254
                              NavigationStyle::RotationCenterMode::FocalPointAtCursor);
255
    }
256
    else if (mode == 2) {
257
        setRotationCenterMode(NavigationStyle::RotationCenterMode::ScenePointAtCursor |
258
                              NavigationStyle::RotationCenterMode::BoundingBoxCenter);
259
    }
260

261
    this->hasDragged = false;
262
    this->hasPanned = false;
263
    this->hasZoomed = false;
264
}
265

266
void NavigationStyle::finalize()
267
{
268
    delete this->spinprojector;
269
    delete[] this->log.position;
270
    delete[] this->log.time;
271
}
272

273
void NavigationStyle::interactiveCountInc()
274
{
275
    viewer->interactiveCountInc();
276
}
277

278
void NavigationStyle::interactiveCountDec()
279
{
280
    viewer->interactiveCountDec();
281
}
282

283
int NavigationStyle::getInteractiveCount() const
284
{
285
    return viewer->getInteractiveCount();
286
}
287

288
void NavigationStyle::setOrbitStyle(NavigationStyle::OrbitStyle style)
289
{
290
    auto projector = static_cast<FCSphereSheetProjector*>(this->spinprojector);
291
    projector->setOrbitStyle(FCSphereSheetProjector::OrbitStyle(style));
292
}
293

294
NavigationStyle::OrbitStyle NavigationStyle::getOrbitStyle() const
295
{
296
    auto projector = static_cast<FCSphereSheetProjector*>(this->spinprojector);
297
    return NavigationStyle::OrbitStyle(projector->getOrbitStyle());
298
}
299

300
SbBool NavigationStyle::isViewing() const
301
{
302
    return viewer->isViewing();
303
}
304

305
void NavigationStyle::setViewing(SbBool enable)
306
{
307
    viewer->setViewing(enable);
308
}
309

310
SbBool NavigationStyle::isSeekMode() const
311
{
312
    return viewer->isSeekMode();
313
}
314

315
void NavigationStyle::setSeekMode(SbBool enable)
316
{
317
    viewer->setSeekMode(enable);
318
}
319

320
SbBool NavigationStyle::seekToPoint(const SbVec2s screenpos)
321
{
322
    return viewer->seekToPoint(screenpos);
323
}
324

325
void NavigationStyle::seekToPoint(const SbVec3f& scenepos)
326
{
327
    viewer->seekToPoint(scenepos);
328
}
329

330
void NavigationStyle::lookAtPoint(const SbVec2s screenpos)
331
{
332
    const SoCamera* camera = viewer->getCamera();
333
    if (!camera) {
334
        return;
335
    }
336

337
    SoRayPickAction rpaction(viewer->getViewportRegion());
338
    rpaction.setPoint(screenpos);
339
    rpaction.setRadius(viewer->getPickRadius());
340
    rpaction.apply(viewer->getSoRenderManager()->getSceneGraph());
341

342
    const SoPickedPoint* picked = rpaction.getPickedPoint();
343

344
    // Point is either the hitpoint or the projected point on the panning plane
345
    SbVec3f point;
346
    if (picked) {
347
        point = picked->getPoint();
348
    }
349
    else {
350
        const SbViewportRegion& vp = viewer->getViewportRegion();
351
        const float aspectratio = vp.getViewportAspectRatio();
352
        SbViewVolume vv = camera->getViewVolume(aspectratio);
353

354
        // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping
355
        if (aspectratio < 1.0) {
356
            vv.scale(1.0 / aspectratio);
357
        }
358

359
        SbLine line;
360
        vv.projectPointToLine(normalizePixelPos(screenpos), line);
361
        panningplane.intersect(line, point);
362
    }
363

364
    lookAtPoint(point);
365
}
366

367
void NavigationStyle::lookAtPoint(const SbVec3f& position)
368
{
369
    this->rotationCenterFound = false;
370
    translateCamera(position - getFocalPoint());
371
}
372

373
SoCamera* NavigationStyle::getCamera() const
374
{
375
    return this->viewer->getCamera();
376
}
377

378
void NavigationStyle::setCameraOrientation(const SbRotation& orientation, SbBool moveToCenter)
379
{
380
    SoCamera* camera = getCamera();
381
    if (!camera)
382
        return;
383

384
    animator->stop();
385

386
    SbVec3f focalPoint = getFocalPoint();
387
    SbVec3f translation(0, 0, 0);
388

389
    if (moveToCenter) {
390
        SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
391
        action.apply(viewer->getSceneGraph());
392
        SbBox3f box = action.getBoundingBox();
393
        if (!box.isEmpty()) {
394
            translation = box.getCenter() - focalPoint;
395
        }
396
    }
397

398
    // Start an animation or set the pose directly
399
    if (isAnimationEnabled()) {
400
        viewer->startAnimation(orientation, focalPoint, translation);
401
    }
402
    else {
403
        // Distance from rotation center to camera position in camera coordinate system
404
        SbVec3f rotationCenterDistanceCam = camera->focalDistance.getValue() * SbVec3f(0, 0, 1);
405

406
        // Set to the given orientation
407
        camera->orientation = orientation;
408

409
        // Distance from rotation center to new camera position in global coordinate system
410
        SbVec3f newRotationCenterDistance;
411
        camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance);
412

413
        // Reposition camera so the rotation center stays in the same place
414
        // Optionally add translation to move to center
415
        camera->position = focalPoint + newRotationCenterDistance + translation;
416
    }
417
}
418

419
void NavigationStyle::translateCamera(const SbVec3f& translation)
420
{
421
    SoCamera* camera = getCamera();
422
    if (!camera)
423
        return;
424

425
    animator->stop();
426

427
    // Start an animation or set the pose directly
428
    if (isAnimationEnabled()) {
429
        viewer->startAnimation(camera->orientation.getValue(), SbVec3f(0, 0, 0), translation);
430
    }
431
    else {
432
        camera->position = camera->position.getValue() + translation;
433
    }
434
}
435

436
void NavigationStyle::boxZoom(const SbBox2s& box)
437
{
438
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
439
    if (!cam) // no camera
440
        return;
441
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
442
    SbViewVolume vv = cam->getViewVolume(vp.getViewportAspectRatio());
443

444
    short sizeX{},sizeY{};
445
    box.getSize(sizeX, sizeY);
446
    SbVec2s size = vp.getViewportSizePixels();
447

448
    // The bbox must not be empty i.e. width and length is zero, but it is possible that
449
    // either width or length is zero
450
    if (sizeX == 0 && sizeY == 0)
451
        return;
452

453
    // Get the new center in normalized pixel coordinates
454
    short xmin{},xmax{},ymin{},ymax{};
455
    box.getBounds(xmin,ymin,xmax,ymax);
456
    const SbVec2f center((float) ((xmin+xmax)/2) / (float) std::max((int)(size[0] - 1), 1),
457
                         (float) (size[1]-(ymin+ymax)/2) / (float) std::max((int)(size[1] - 1), 1));
458

459
    SbPlane plane = vv.getPlane(cam->focalDistance.getValue());
460
    panCamera(cam,vp.getViewportAspectRatio(),plane, SbVec2f(0.5,0.5), center);
461

462
    // Set height or height angle of the camera
463
    float scaleX = (float)sizeX/(float)size[0];
464
    float scaleY = (float)sizeY/(float)size[1];
465
    float scale = std::max<float>(scaleX, scaleY);
466
    if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
467
        float height = static_cast<SoOrthographicCamera*>(cam)->height.getValue() * scale;
468
        static_cast<SoOrthographicCamera*>(cam)->height = height;
469
    }
470
    else if (cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
471
        float height = static_cast<SoPerspectiveCamera*>(cam)->heightAngle.getValue() / 2.0f;
472
        height = 2.0f * atan(tan(height) * scale);
473
        static_cast<SoPerspectiveCamera*>(cam)->heightAngle = height;
474
    }
475
}
476

477
void NavigationStyle::viewAll()
478
{
479
    // Get the bounding box of the scene
480
    SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
481
    action.apply(viewer->getSceneGraph());
482
    SbBox3f box = action.getBoundingBox();
483
    if (box.isEmpty())
484
        return;
485

486

487
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
488
    if (!cam)
489
        return;
490

491
    SbViewVolume  vol = cam->getViewVolume();
492
    if (vol.ulf == vol.llf)
493
        return; // empty frustum (no view up vector defined)
494
    SbVec2f s = vol.projectBox(box);
495
    SbVec2s size = viewer->getSoRenderManager()->getSize();
496

497
    SbVec3f pt1, pt2, pt3, tmp;
498
    vol.projectPointToLine( SbVec2f(0.0f,0.0f), pt1, tmp );
499
    vol.projectPointToLine( SbVec2f(s[0],0.0f), pt2, tmp );
500
    vol.projectPointToLine( SbVec2f(0.0f,s[1]), pt3, tmp );
501

502
    float cam_width = (pt2-pt1).length();
503
    float cam_height = (pt3-pt1).length();
504

505
    // add a small border
506
    cam_height = 1.08f * std::max<float>((cam_width*(float)size[1])/(float)size[0],cam_height);
507

508
    float aspect = cam->aspectRatio.getValue();
509

510
    if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
511
        auto ocam = static_cast<SoOrthographicCamera *>(cam);
512
        if (aspect < 1.0f)
513
            ocam->height = cam_height / aspect;
514
        else
515
            ocam->height = cam_height;
516
    }
517
}
518

519
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
520
void NavigationStyle::findBoundingSphere() {
521
    // Find a bounding sphere for the scene
522
    SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
523
    action.apply(viewer->getSceneGraph());
524
    boundingSphere.circumscribe(action.getBoundingBox());
525
}
526
#endif
527

528
/** Rotate the camera by the given amount, then reposition it so we're still pointing at the same
529
 * focal point
530
 */
531
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation)
532
{
533
    reorientCamera(camera, rotation, getFocalPoint());
534
}
535

536
/** Rotate the camera by the given amount, then reposition it so the rotation center stays in the
537
 * same place
538
 */
539
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation, const SbVec3f& rotationCenter)
540
{
541
    if (!camera) {
542
        return;
543
    }
544

545
    // Distance from rotation center to camera position in camera coordinate system
546
    SbVec3f rotationCenterDistanceCam;
547
    camera->orientation.getValue().inverse().multVec(camera->position.getValue() - rotationCenter, rotationCenterDistanceCam);
548

549
    // Set new orientation value by accumulating the new rotation
550
    camera->orientation = rotation * camera->orientation.getValue();
551

552
    // Distance from rotation center to new camera position in global coordinate system
553
    SbVec3f newRotationCenterDistance;
554
    camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance);
555

556
    // Reposition camera so the rotation center stays in the same place
557
    camera->position = rotationCenter + newRotationCenterDistance;
558

559
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
560
    // Fix issue with near clipping in orthogonal view
561
    if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
562

563
         // The center of the bounding sphere in camera coordinate system
564
         SbVec3f center;
565
         camera->orientation.getValue().inverse().multVec(boundingSphere.getCenter() - camera->position.getValue(), center);
566

567
         SbVec3f dir;
568
         camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), dir);
569

570
         // Reposition the camera but keep the focal point the same
571
         // nearDistance is 0 and farDistance is the diameter of the bounding sphere
572
         float repositionDistance = -center.getValue()[2] - boundingSphere.getRadius();
573
         camera->position = camera->position.getValue() + repositionDistance * dir;
574
         camera->nearDistance = 0;
575
         camera->farDistance = 2 * boundingSphere.getRadius() + 1;
576
         camera->focalDistance = camera->focalDistance.getValue() - repositionDistance;
577
     }
578
#endif
579
}
580

581
void NavigationStyle::panCamera(SoCamera * cam, float aspectratio, const SbPlane & panplane,
582
                                const SbVec2f & currpos, const SbVec2f & prevpos)
583
{
584
    if (!cam) // can happen for empty scenegraph
585
        return;
586
    if (currpos == prevpos) // useless invocation
587
        return;
588

589

590
    // Find projection points for the last and current mouse coordinates.
591
    SbViewVolume vv = cam->getViewVolume(aspectratio);
592

593
    // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping
594
    if(aspectratio < 1.0)
595
        vv.scale(1.0 / aspectratio);
596

597
    SbLine line;
598
    vv.projectPointToLine(currpos, line);
599
    SbVec3f current_planept;
600
    panplane.intersect(line, current_planept);
601
    vv.projectPointToLine(prevpos, line);
602
    SbVec3f old_planept;
603
    panplane.intersect(line, old_planept);
604

605
    // Reposition camera according to the vector difference between the
606
    // projected points.
607
    cam->position = cam->position.getValue() - (current_planept - old_planept);
608

609
    if (this->currentmode != NavigationStyle::IDLE) {
610
        hasPanned = true;
611
    }
612
}
613

614
void NavigationStyle::setupPanningPlane(const SoCamera* camera)
615
{
616
    // The plane we're projecting the mouse coordinates to get 3D
617
    // coordinates should stay the same during the whole pan
618
    // operation, so we should calculate this value here.
619
    if (!camera) {  // can happen for empty scenegraph
620
        this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0);
621
    }
622
    else {
623
        const SbViewportRegion& vp = viewer->getViewportRegion();
624
        const float aspectratio = vp.getViewportAspectRatio();
625
        SbViewVolume vv = camera->getViewVolume(aspectratio);
626

627
        // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping
628
        if (aspectratio < 1.0) {
629
            vv.scale(1.0 / aspectratio);
630
        }
631

632
        this->panningplane = vv.getPlane(camera->focalDistance.getValue());
633
    }
634
}
635

636
/** Dependent on the camera type this will either shrink or expand the
637
 * height of the viewport (orthogonal camera) or move the camera
638
 * closer or further away from the focal point in the scene.
639
 */
640
void NavigationStyle::zoom(SoCamera * cam, float diffvalue)
641
{
642
    if (!cam) // can happen for empty scenegraph
643
        return;
644

645
    animator->stop();
646

647
    SoType t = cam->getTypeId();
648
    SbName tname = t.getName();
649

650
    // This will be in the range of <0, ->>.
651
    auto multiplicator = float(exp(diffvalue));
652

653
    if (t.isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
654

655
        // Since there's no perspective, "zooming" in the original sense
656
        // of the word won't have any visible effect. So we just increase
657
        // or decrease the field-of-view values of the camera instead, to
658
        // "shrink" the projection size of the model / scene.
659

660
        auto oc = static_cast<SoOrthographicCamera *>(cam);
661
        oc->height = oc->height.getValue() * multiplicator;
662

663
    }
664
    else {
665
        // FrustumCamera can be found in the SmallChange CVS module (it's
666
        // a camera that lets you specify (for instance) an off-center
667
        // frustum (similar to glFrustum())
668
        if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) &&
669
            tname != "FrustumCamera") {
670
 /*         static SbBool first = true;
671
            if (first) {
672
                SoDebugError::postWarning("SoGuiFullViewerP::zoom",
673
                                          "Unknown camera type, "
674
                                          "will zoom by moving position, but this might not be correct.");
675
                first = false;
676
            }*/
677
        }
678

679
        const float oldfocaldist = cam->focalDistance.getValue();
680
        const float newfocaldist = oldfocaldist * multiplicator;
681

682
        SbVec3f direction;
683
        cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
684

685
        const SbVec3f oldpos = cam->position.getValue();
686
        const SbVec3f newpos = oldpos + (newfocaldist - oldfocaldist) * -direction;
687

688
        // This catches a rather common user interface "buglet": if the
689
        // user zooms the camera out to a distance from origo larger than
690
        // what we still can safely do floating point calculations on
691
        // (i.e. without getting NaN or Inf values), the faulty floating
692
        // point values will propagate until we start to get debug error
693
        // messages and eventually an assert failure from core Coin code.
694
        //
695
        // With the below bounds check, this problem is avoided.
696
        //
697
        // (But note that we depend on the input argument ''diffvalue'' to
698
        // be small enough that zooming happens gradually. Ideally, we
699
        // should also check distorigo with isinf() and isnan() (or
700
        // inversely; isinfite()), but those only became standardized with
701
        // C99.)
702
        const float distorigo = newpos.length();
703
        // sqrt(FLT_MAX) == ~ 1e+19, which should be both safe for further
704
        // calculations and ok for the end-user and app-programmer.
705
        if (distorigo > float(sqrt(FLT_MAX))) {
706
            // do nothing here
707
        }
708
        else {
709
            cam->position = newpos;
710
            cam->focalDistance = newfocaldist;
711
        }
712
    }
713

714
    if (this->currentmode != NavigationStyle::IDLE) {
715
        hasZoomed = true;
716
    }
717
}
718

719
// Calculate a zoom/dolly factor from the difference of the current
720
// cursor position and the last.
721
void NavigationStyle::zoomByCursor(const SbVec2f & thispos, const SbVec2f & prevpos)
722
{
723
    // There is no "geometrically correct" value, 20 just seems to give
724
    // about the right "feel".
725
    float value = (thispos[1] - prevpos[1]) * 10.0f/*20.0f*/;
726
    if (this->invertZoom)
727
        value = -value;
728
    zoom(viewer->getSoRenderManager()->getCamera(), value);
729
}
730

731
void NavigationStyle::zoomIn()
732
{
733
    zoom(viewer->getSoRenderManager()->getCamera(), -this->zoomStep);
734
}
735

736
void NavigationStyle::zoomOut()
737
{
738
    zoom(viewer->getSoRenderManager()->getCamera(), this->zoomStep);
739
}
740

741
/*!
742
 * Returns the steps if the mouse wheel is rotated
743
 */
744
int NavigationStyle::getDelta() const
745
{
746
    return 120;
747
}
748

749
void NavigationStyle::doZoom(SoCamera* camera, int wheeldelta, const SbVec2f& pos)
750
{
751
    float value = this->zoomStep * wheeldelta / float(getDelta());
752
    if (this->invertZoom)
753
        value = -value;
754
    doZoom(camera, value, pos);
755
}
756

757
/*!
758
 *\brief NavigationStyle::doZoom Zooms in or out by specified factor, keeping the point on screen specified by parameter pos fixed
759
 *  or not according to user preference (NavigationStyle::zoomAtCursor). Ignores invertZoom user preference.
760
 */
761
void NavigationStyle::doZoom(SoCamera* camera, float logfactor, const SbVec2f& pos)
762
{
763
    // something is asking for big zoom factor. This func is made for interactive zooming,
764
    // where the changes are per mouse move and thus are small.
765
    if (fabs(logfactor)>4.0)
766
        return;
767
    SbBool zoomAtCur = this->zoomAtCursor;
768
    if (zoomAtCur) {
769
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
770
        float ratio = vp.getViewportAspectRatio();
771
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
772
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
773
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, SbVec2f(0.5,0.5), pos);
774
    }
775

776
    zoom(camera, logfactor);
777

778
    if (zoomAtCur) {
779
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
780
        float ratio = vp.getViewportAspectRatio();
781
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
782
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
783
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5,0.5));
784

785
        // Change the position of the rotation center indicator after zooming at cursor
786
        // Rotation mode is WindowCenter
787
        if (!rotationCenterMode) {
788
            viewer->changeRotationCenterPosition(getFocalPoint());
789

790
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
791
            findBoundingSphere();
792
#endif
793
        }
794
    }
795
}
796

797
void NavigationStyle::doRotate(SoCamera * camera, float angle, const SbVec2f& pos)
798
{
799
    SbBool zoomAtCur = this->zoomAtCursor;
800
    if (zoomAtCur) {
801
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
802
        float ratio = vp.getViewportAspectRatio();
803
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
804
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
805
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, SbVec2f(0.5,0.5), pos);
806
    }
807

808
    SbRotation rotcam = camera->orientation.getValue();
809
    //get view direction
810
    SbVec3f vdir;
811
    rotcam.multVec(SbVec3f(0,0,-1),vdir);
812
    //rotate
813
    SbRotation drot(vdir,angle);
814
    camera->orientation.setValue(rotcam * drot);
815

816
    if (zoomAtCur) {
817
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
818
        float ratio = vp.getViewportAspectRatio();
819
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
820
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
821
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5,0.5));
822
    }
823

824
}
825

826
SbVec3f NavigationStyle::getRotationCenter(SbBool& found) const
827
{
828
    found = this->rotationCenterFound;
829
    return this->rotationCenter;
830
}
831

832
void NavigationStyle::setRotationCenter(const SbVec3f& cnt)
833
{
834
    this->rotationCenter = cnt;
835
    this->rotationCenterFound = true;
836
}
837

838
SbVec3f NavigationStyle::getFocalPoint() const
839
{
840
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
841
    if (!cam)
842
        return {0,0,0};
843

844
    // Find global coordinates of focal point.
845
    SbVec3f direction;
846
    cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
847
    SbVec3f focal = cam->position.getValue() +
848
                    cam->focalDistance.getValue() * direction;
849
    return focal;
850
}
851

852
/** Uses the sphere sheet projector to map the mouseposition onto
853
 * a 3D point and find a rotation from this and the last calculated point.
854
 */
855
void NavigationStyle::spin(const SbVec2f & pointerpos)
856
{
857
    if (this->log.historysize < 2)
858
        return;
859
    assert(this->spinprojector);
860

861
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
862
    SbVec2s glsize(vp.getViewportSizePixels());
863
    SbVec2f lastpos;
864
    lastpos[0] = float(this->log.position[1][0]) / float(std::max((int)(glsize[0]-1), 1));
865
    lastpos[1] = float(this->log.position[1][1]) / float(std::max((int)(glsize[1]-1), 1));
866

867
    if (this->rotationCenterMode && this->rotationCenterFound) {
868
        SbVec3f hitpoint = this->rotationCenter;
869

870
        // set to the given position
871
        SbVec3f direction;
872
        viewer->getSoRenderManager()->getCamera()->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
873
        viewer->getSoRenderManager()->getCamera()->position = hitpoint - viewer->getSoRenderManager()->getCamera()->focalDistance.getValue() * direction;
874
    }
875

876
    // 0000333: Turntable camera rotation
877
    SbMatrix mat;
878
    viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat);
879
    this->spinprojector->setWorkingSpace(mat);
880

881
    this->spinprojector->project(lastpos);
882
    SbRotation r;
883
    this->spinprojector->projectAndGetRotation(pointerpos, r);
884
    float sensitivity = getSensitivity();
885
    if (sensitivity > 1.0f) {
886
        SbVec3f axis;
887
        float radians{};
888
        r.getValue(axis, radians);
889
        radians = sensitivity * radians;
890
        r.setValue(axis, radians);
891
    }
892
    r.invert();
893
    this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r);
894

895
    if (this->rotationCenterMode && this->rotationCenterFound) {
896
        float ratio = vp.getViewportAspectRatio();
897
        SbViewVolume vv = viewer->getSoRenderManager()->getCamera()->getViewVolume(vp.getViewportAspectRatio());
898
        SbPlane panplane = vv.getPlane(viewer->getSoRenderManager()->getCamera()->focalDistance.getValue());
899
        SbVec2f posn;
900
        posn[0] = float(this->localPos[0]) / float(std::max((int)(glsize[0]-1), 1));
901
        posn[1] = float(this->localPos[1]) / float(std::max((int)(glsize[1]-1), 1));
902
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, posn, SbVec2f(0.5,0.5));
903
    }
904

905
    // Calculate an average angle magnitude value to make the transition
906
    // to a possible spin animation mode appear smooth.
907

908
    SbVec3f dummy_axis, newaxis;
909
    float acc_angle{}, newangle{};
910
    this->spinincrement.getValue(dummy_axis, acc_angle);
911
    acc_angle *= this->spinsamplecounter; // weight
912
    r.getValue(newaxis, newangle);
913
    acc_angle += newangle;
914

915
    this->spinsamplecounter++;
916
    acc_angle /= this->spinsamplecounter;
917
    // FIXME: accumulate and average axis vectors as well? 19990501 mortene.
918
    this->spinincrement.setValue(newaxis, acc_angle);
919

920
    // Don't carry too much baggage, as that'll give unwanted results
921
    // when the user quickly trigger (as in "click-drag-release") a spin
922
    // animation.
923
    if (this->spinsamplecounter > 3) this->spinsamplecounter = 3;
924

925
    if (this->currentmode != NavigationStyle::IDLE) {
926
        hasDragged = true;
927
    }
928
}
929

930
/*!
931
 * \brief NavigationStyle::spin_simplified is a simplified version of
932
 * NavigationStyle::spin(..), which uses less global variables. Doesn't support
933
 * starting an animated spinning.
934
 *
935
 * \param cam the camera to affect. The rotation amount is determined by delta
936
 * (curpos-prevpos), and rotation axis is also affected by average pos.
937
 * \param curpos  current normalized position or mouse pointer
938
 * \param prevpos  previous normalized position of mouse pointer
939
 */
940
void NavigationStyle::spin_simplified(SoCamera* cam, SbVec2f curpos, SbVec2f prevpos)
941
{
942
    assert(this->spinprojector);
943

944
    if (this->rotationCenterMode && this->rotationCenterFound) {
945
        SbVec3f hitpoint = this->rotationCenter;
946

947
        // set to the given position
948
        SbVec3f direction;
949
        viewer->getSoRenderManager()->getCamera()->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
950
        viewer->getSoRenderManager()->getCamera()->position = hitpoint - viewer->getSoRenderManager()->getCamera()->focalDistance.getValue() * direction;
951
    }
952

953
    // 0000333: Turntable camera rotation
954
    SbMatrix mat;
955
    viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat);
956
    this->spinprojector->setWorkingSpace(mat);
957

958
    this->spinprojector->project(prevpos);
959
    SbRotation r;
960
    this->spinprojector->projectAndGetRotation(curpos, r);
961
    float sensitivity = getSensitivity();
962
    if (sensitivity > 1.0f) {
963
        SbVec3f axis;
964
        float radians{};
965
        r.getValue(axis, radians);
966
        radians = sensitivity * radians;
967
        r.setValue(axis, radians);
968
    }
969
    r.invert();
970
    this->reorientCamera(cam, r);
971

972
    if (this->rotationCenterMode && this->rotationCenterFound) {
973
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
974
        SbVec2s glsize(vp.getViewportSizePixels());
975

976
        float ratio = vp.getViewportAspectRatio();
977
        SbViewVolume vv = viewer->getSoRenderManager()->getCamera()->getViewVolume(vp.getViewportAspectRatio());
978
        SbPlane panplane = vv.getPlane(viewer->getSoRenderManager()->getCamera()->focalDistance.getValue());
979
        SbVec2f posn;
980
        posn[0] = float(this->localPos[0]) / float(std::max((int)(glsize[0]-1), 1));
981
        posn[1] = float(this->localPos[1]) / float(std::max((int)(glsize[1]-1), 1));
982
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, posn, SbVec2f(0.5,0.5));
983
    }
984

985
    hasDragged = true;
986
}
987

988
SbBool NavigationStyle::doSpin()
989
{
990
    if (this->log.historysize >= 3) {
991
        SbTime stoptime = (SbTime::getTimeOfDay() - this->log.time[0]);
992
        if (isSpinningAnimationEnabled() && stoptime.getValue() < 0.100) {
993
            const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
994
            const SbVec2s glsize(vp.getViewportSizePixels());
995
            SbVec3f from = this->spinprojector->project(SbVec2f(float(this->log.position[2][0]) / float(std::max(glsize[0]-1, 1)),
996
                                                                float(this->log.position[2][1]) / float(std::max(glsize[1]-1, 1))));
997
            SbVec3f to = this->spinprojector->project(this->lastmouseposition);
998
            SbRotation rot = this->spinprojector->getRotation(from, to);
999

1000
            SbTime delta = (this->log.time[0] - this->log.time[2]);
1001
            double deltatime = delta.getValue();
1002
            rot.invert();
1003
            rot.scaleAngle(float(0.200 / deltatime));
1004

1005
            SbVec3f axis;
1006
            float radians{};
1007
            rot.getValue(axis, radians);
1008
            if ((radians > 0.01f) && (deltatime < 0.300)) {
1009
                viewer->startSpinningAnimation(axis, radians * 5);
1010
                return true;
1011
            }
1012
        }
1013
    }
1014

1015
    return false;
1016
}
1017

1018
void NavigationStyle::saveCursorPosition(const SoEvent * const ev)
1019
{
1020
    this->globalPos.setValue(QCursor::pos().x(), QCursor::pos().y());
1021
    this->localPos = ev->getPosition();
1022

1023
    // mode is WindowCenter
1024
    if (!this->rotationCenterMode) {
1025
        setRotationCenter(getFocalPoint());
1026
    }
1027

1028
    //Option to get point on model (slow) or always on focal plane (fast)
1029
    //
1030
    // mode is ScenePointAtCursor to get exact point if possible
1031
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::ScenePointAtCursor) {
1032
        SoRayPickAction rpaction(viewer->getSoRenderManager()->getViewportRegion());
1033
        rpaction.setPoint(this->localPos);
1034
        rpaction.setRadius(viewer->getPickRadius());
1035
        rpaction.apply(viewer->getSoRenderManager()->getSceneGraph());
1036

1037
        SoPickedPoint * picked = rpaction.getPickedPoint();
1038
        if (picked) {
1039
            setRotationCenter(picked->getPoint());
1040
            return;
1041
        }
1042
    }
1043

1044
    // mode is FocalPointAtCursor or a ScenePointAtCursor failed
1045
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::FocalPointAtCursor) {
1046
        // get the intersection point of the ray and the focal plane
1047
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1048
        float ratio = vp.getViewportAspectRatio();
1049

1050
        SoCamera* cam = viewer->getSoRenderManager()->getCamera();
1051
        if (!cam) // no camera
1052
            return;
1053
        SbViewVolume vv = cam->getViewVolume(ratio);
1054

1055
        SbLine line;
1056
        SbVec2f currpos = ev->getNormalizedPosition(vp);
1057
        vv.projectPointToLine(currpos, line);
1058
        SbVec3f current_planept;
1059
        SbPlane panplane = vv.getPlane(cam->focalDistance.getValue());
1060
        panplane.intersect(line, current_planept);
1061

1062
        setRotationCenter(current_planept);
1063
    }
1064

1065
    // mode is BoundingBoxCenter or a ScenePointAtCursor failed
1066
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::BoundingBoxCenter) {
1067
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1068
        float ratio = vp.getViewportAspectRatio();
1069

1070
        SoCamera* cam = viewer->getSoRenderManager()->getCamera();
1071
        if (!cam) // no camera
1072
            return;
1073

1074
        // Get the bounding box center of the physical object group
1075
        SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
1076
        action.apply(viewer->objectGroup);
1077
        SbBox3f boundingBox = action.getBoundingBox();
1078
        SbVec3f boundingBoxCenter = boundingBox.getCenter();
1079
        setRotationCenter(boundingBoxCenter);
1080

1081
        // To drag around the center point of the bbox we have to determine
1082
        // its projection on the screen because this information is used in
1083
        // NavigationStyle::spin() for the panning
1084
        SbViewVolume vv = cam->getViewVolume(ratio);
1085
        vv.projectToScreen(boundingBoxCenter, boundingBoxCenter);
1086
        SbVec2s size = vp.getViewportSizePixels();
1087
        auto tox = static_cast<short>(boundingBoxCenter[0] * size[0]);
1088
        auto toy = static_cast<short>(boundingBoxCenter[1] * size[1]);
1089
        this->localPos.setValue(tox, toy);
1090
    }
1091
}
1092

1093
SbVec2f NavigationStyle::normalizePixelPos(SbVec2s pixpos)
1094
{
1095
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1096
    const SbVec2s size(vp.getViewportSizePixels());
1097
    return {(float) pixpos[0] / (float) std::max((int)(size[0] - 1), 1),
1098
            (float) pixpos[1] / (float) std::max((int)(size[1] - 1), 1)};
1099
}
1100

1101
SbVec2f NavigationStyle::normalizePixelPos(SbVec2f pixpos)
1102
{
1103
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1104
    const SbVec2s size(vp.getViewportSizePixels());
1105
    return {pixpos[0] / (float) std::max((int)(size[0] - 1), 1),
1106
            pixpos[1] / (float) std::max((int)(size[1] - 1), 1)};
1107
}
1108

1109
void NavigationStyle::moveCursorPosition()
1110
{
1111
    if (!isResetCursorPosition())
1112
        return;
1113

1114
    QPoint cpos = QCursor::pos();
1115
    if (abs(cpos.x()-globalPos[0]) > 10 ||
1116
        abs(cpos.y()-globalPos[1]) > 10) {
1117
        QCursor::setPos(globalPos[0], globalPos[1]-1);
1118
        this->log.position[0] = localPos;
1119
    }
1120
}
1121

1122

1123
SbBool NavigationStyle::handleEventInForeground(const SoEvent* const e)
1124
{
1125
    SoHandleEventAction action(viewer->getSoRenderManager()->getViewportRegion());
1126
    action.setEvent(e);
1127
    action.setPickRadius(viewer->getPickRadius());
1128
    action.apply(viewer->foregroundroot);
1129
    return action.isHandled();
1130
}
1131

1132
/**
1133
 * @brief Decide if it should be possible to start any animation
1134
 *
1135
 * If the enable flag is false and we're currently animating, the animation will be stopped
1136
 */
1137
void NavigationStyle::setAnimationEnabled(const SbBool enable)
1138
{
1139
    animationEnabled = enable;
1140
    if (!enable && isAnimating()) {
1141
        animator->stop();
1142
    }
1143
}
1144

1145
/**
1146
 * @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
1147
 *
1148
 * If the enable flag is false and we're currently animating, the spin animation will be stopped
1149
 */
1150
void NavigationStyle::setSpinningAnimationEnabled(const SbBool enable)
1151
{
1152
    spinningAnimationEnabled = enable;
1153
    if (!enable && isSpinning()) {
1154
        animator->stop();
1155
    }
1156
}
1157

1158
/**
1159
 * @return Whether or not it is possible to start any animation
1160
 */
1161
SbBool NavigationStyle::isAnimationEnabled() const
1162
{
1163
    return animationEnabled;
1164
}
1165

1166
/**
1167
 * @return Whether or not it is possible to start a spinning animation e.g. after dragging
1168
 */
1169
SbBool NavigationStyle::isSpinningAnimationEnabled() const
1170
{
1171
    return animationEnabled && spinningAnimationEnabled;
1172
}
1173

1174
/**
1175
 * @return Whether or not any animation is currently active
1176
 */
1177
SbBool NavigationStyle::isAnimating() const
1178
{
1179
    return animator->isAnimating();
1180
}
1181

1182
/**
1183
 * @return Whether or not a spinning animation is currently active e.g. after a user drag
1184
 */
1185
SbBool NavigationStyle::isSpinning() const
1186
{
1187
    return currentmode == NavigationStyle::SPINNING;
1188
}
1189

1190
void NavigationStyle::startAnimating(const std::shared_ptr<NavigationAnimation>& animation, bool wait) const
1191
{
1192
    if (wait) {
1193
        animator->startAndWait(animation);
1194
    }
1195
    else {
1196
        animator->start(animation);
1197
    }
1198
}
1199

1200
void NavigationStyle::stopAnimating() const
1201
{
1202
    animator->stop();
1203
}
1204

1205
void NavigationStyle::setSensitivity(float val)
1206
{
1207
    this->sensitivity = val;
1208
}
1209

1210
float NavigationStyle::getSensitivity() const
1211
{
1212
    return this->sensitivity;
1213
}
1214

1215
void NavigationStyle::setResetCursorPosition(SbBool on)
1216
{
1217
    this->resetcursorpos = on;
1218
}
1219

1220
SbBool NavigationStyle::isResetCursorPosition() const
1221
{
1222
    return this->resetcursorpos;
1223
}
1224

1225
void NavigationStyle::setZoomInverted(SbBool on)
1226
{
1227
    this->invertZoom = on;
1228
}
1229

1230
SbBool NavigationStyle::isZoomInverted() const
1231
{
1232
    return this->invertZoom;
1233
}
1234

1235
void NavigationStyle::setZoomStep(float val)
1236
{
1237
    this->zoomStep = val;
1238
}
1239

1240
void NavigationStyle::setZoomAtCursor(SbBool on)
1241
{
1242
    this->zoomAtCursor = on;
1243
}
1244

1245
SbBool NavigationStyle::isZoomAtCursor() const
1246
{
1247
    return this->zoomAtCursor;
1248
}
1249

1250
void NavigationStyle::setRotationCenterMode(NavigationStyle::RotationCenterModes mode)
1251
{
1252
    this->rotationCenterMode = mode;
1253
}
1254

1255
NavigationStyle::RotationCenterModes NavigationStyle::getRotationCenterMode() const
1256
{
1257
    return this->rotationCenterMode;
1258
}
1259

1260
void NavigationStyle::startSelection(AbstractMouseSelection* mouse)
1261
{
1262
    if (!mouse)
1263
        return;
1264

1265
    if (mouseSelection) {
1266
        SoDebugError::postWarning("NavigationStyle::startSelection",
1267
                                  "Set new mouse selection while an old is still active.");
1268
    }
1269

1270
    mouseSelection = mouse;
1271
    mouseSelection->grabMouseModel(viewer);
1272
}
1273

1274
void NavigationStyle::startSelection(NavigationStyle::SelectionMode mode)
1275
{
1276
    if (mouseSelection)
1277
        return;
1278
    if (isSelecting())
1279
        stopSelection();
1280

1281
    switch (mode)
1282
    {
1283
    case Lasso:
1284
        mouseSelection = new PolyPickerSelection();
1285
        break;
1286
    case Rectangle:
1287
        mouseSelection = new RectangleSelection();
1288
        break;
1289
    case Rubberband:
1290
        mouseSelection = new RubberbandSelection();
1291
        break;
1292
    case BoxZoom:
1293
        mouseSelection = new BoxZoomSelection();
1294
        break;
1295
    case Clip:
1296
        mouseSelection = new PolyClipSelection();
1297
        break;
1298
    default:
1299
        break;
1300
    }
1301

1302
    if (mouseSelection)
1303
        mouseSelection->grabMouseModel(viewer);
1304
}
1305

1306
void NavigationStyle::abortSelection()
1307
{
1308
    pcPolygon.clear();
1309
    if (mouseSelection) {
1310
        mouseSelection->releaseMouseModel(true);
1311
        delete mouseSelection;
1312
        mouseSelection = nullptr;
1313
    }
1314
}
1315

1316
void NavigationStyle::stopSelection()
1317
{
1318
    pcPolygon.clear();
1319
    if (mouseSelection) {
1320
        mouseSelection->releaseMouseModel();
1321
        delete mouseSelection;
1322
        mouseSelection = nullptr;
1323
    }
1324
}
1325

1326
SbBool NavigationStyle::isSelecting() const
1327
{
1328
    return (mouseSelection ? true : false);
1329
}
1330

1331
const std::vector<SbVec2s>& NavigationStyle::getPolygon(SelectionRole* role) const
1332
{
1333
    if (role)
1334
       *role = this->selectedRole;
1335
    return pcPolygon;
1336
}
1337

1338
// This method adds another point to the mouse location log, used for spin
1339
// animation calculations.
1340
void NavigationStyle::addToLog(const SbVec2s pos, const SbTime time)
1341
{
1342
    // In case someone changes the const size setting at the top of this
1343
    // file too small.
1344
    assert (this->log.size > 2 && "mouse log too small!");
1345

1346
    if (this->log.historysize > 0 && pos == this->log.position[0]) {
1347
        return;
1348
    }
1349

1350
    int lastidx = this->log.historysize;
1351
    // If we've filled up the log, we should throw away the last item:
1352
    if (lastidx == this->log.size) { lastidx--; }
1353

1354
    assert(lastidx < this->log.size);
1355
    for (int i = lastidx; i > 0; i--) {
1356
        this->log.position[i] = this->log.position[i-1];
1357
        this->log.time[i] = this->log.time[i-1];
1358
    }
1359

1360
    this->log.position[0] = pos;
1361
    this->log.time[0] = time;
1362
    if (this->log.historysize < this->log.size)
1363
        this->log.historysize += 1;
1364
}
1365

1366
// This method "clears" the mouse location log, used for spin
1367
// animation calculations.
1368
void NavigationStyle::clearLog()
1369
{
1370
    this->log.historysize = 0;
1371
}
1372

1373
void NavigationStyle::syncModifierKeys(const SoEvent * const ev)
1374
{
1375
    // Mismatches in state of the modifier keys happens if the user
1376
    // presses or releases them outside the viewer window.
1377
    if (this->ctrldown != ev->wasCtrlDown()) {
1378
        this->ctrldown = ev->wasCtrlDown();
1379
    }
1380
    if (this->shiftdown != ev->wasShiftDown()) {
1381
        this->shiftdown = ev->wasShiftDown();
1382
    }
1383
    if (this->altdown != ev->wasAltDown()) {
1384
        this->altdown = ev->wasAltDown();
1385
    }
1386
}
1387

1388
// The viewer is a state machine, and all changes to the current state
1389
// are made through this call.
1390
void NavigationStyle::setViewingMode(const ViewerMode newmode)
1391
{
1392
    const ViewerMode oldmode = this->currentmode;
1393
    if (newmode == oldmode) {
1394

1395
        // The rotation center could have been changed even if the mode has not changed
1396
        if (newmode == NavigationStyle::DRAGGING && rotationCenterFound) {
1397
            viewer->changeRotationCenterPosition(rotationCenter);
1398
        }
1399

1400
        return;
1401
    }
1402

1403
    if (newmode == NavigationStyle::IDLE) {
1404
        hasPanned = false;
1405
        hasDragged = false;
1406
        hasZoomed = false;
1407
    }
1408

1409
    switch (newmode) {
1410
    case DRAGGING:
1411
        // Set up initial projection point for the projector object when
1412
        // first starting a drag operation.
1413
        animator->stop();
1414
        viewer->showRotationCenter(true);
1415

1416
#if (COIN_MAJOR_VERSION * 100 + COIN_MINOR_VERSION * 10 + COIN_MICRO_VERSION < 403)
1417
        findBoundingSphere();
1418
#endif
1419

1420
        this->spinprojector->project(this->lastmouseposition);
1421
        this->interactiveCountInc();
1422
        this->clearLog();
1423
        break;
1424

1425
    case SPINNING:
1426
        this->interactiveCountInc();
1427
        viewer->getSoRenderManager()->scheduleRedraw();
1428
        break;
1429

1430
    case PANNING:
1431
        animator->stop();
1432
        setupPanningPlane(viewer->getSoRenderManager()->getCamera());
1433
        this->interactiveCountInc();
1434
        break;
1435

1436
    case ZOOMING:
1437
        animator->stop();
1438
        this->interactiveCountInc();
1439
        break;
1440

1441
    case BOXZOOM:
1442
        animator->stop();
1443
        this->interactiveCountInc();
1444
        break;
1445

1446
    default: // include default to avoid compiler warnings.
1447
        break;
1448
    }
1449

1450
    switch (oldmode) {
1451
    case SPINNING:
1452
    case DRAGGING:
1453
        viewer->showRotationCenter(false);
1454
        [[fallthrough]];
1455
    case PANNING:
1456
    case ZOOMING:
1457
    case BOXZOOM:
1458
        this->interactiveCountDec();
1459
        break;
1460

1461
    default:
1462
        break;
1463
    }
1464

1465
    viewer->setCursorRepresentation(newmode);
1466
    this->currentmode = newmode;
1467
}
1468

1469
int NavigationStyle::getViewingMode() const
1470
{
1471
    return (int)this->currentmode;
1472
}
1473

1474
SbBool NavigationStyle::processEvent(const SoEvent * const ev)
1475
{
1476
    // If we're in picking mode then all events must be redirected to the
1477
    // appropriate mouse model.
1478
    if (mouseSelection) {
1479
        int hd=mouseSelection->handleEvent(ev,viewer->getSoRenderManager()->getViewportRegion());
1480
        if (hd==AbstractMouseSelection::Continue||
1481
            hd==AbstractMouseSelection::Restart) {
1482
            return true;
1483
        }
1484
        else if (hd==AbstractMouseSelection::Finish) {
1485
            pcPolygon = mouseSelection->getPositions();
1486
            selectedRole = mouseSelection->selectedRole();
1487
            delete mouseSelection;
1488
            mouseSelection = nullptr;
1489
            syncWithEvent(ev);
1490
            return NavigationStyle::processSoEvent(ev);
1491
        }
1492
        else if (hd==AbstractMouseSelection::Cancel) {
1493
            pcPolygon.clear();
1494
            delete mouseSelection;
1495
            mouseSelection = nullptr;
1496
            syncWithEvent(ev);
1497
            return NavigationStyle::processSoEvent(ev);
1498
        }
1499
    }
1500

1501
    const ViewerMode curmode = this->currentmode;
1502

1503
    SbBool processed = false;
1504
    processed = this->processSoEvent(ev);
1505

1506
    // check for left click without selecting something
1507
    if ((curmode == NavigationStyle::SELECTION || curmode == NavigationStyle::IDLE)
1508
            && !processed) {
1509
        if (SoMouseButtonEvent::isButtonReleaseEvent(ev, SoMouseButtonEvent::BUTTON1)) {
1510
            if (!ev->wasCtrlDown()) {
1511
                Gui::Selection().clearSelection();
1512
            }
1513
        }
1514
    }
1515

1516
    return processed;
1517
}
1518

1519
SbBool NavigationStyle::processSoEvent(const SoEvent * const ev)
1520
{
1521
    bool processed = false;
1522
    bool offeredtoViewerEventBase = false;
1523

1524
    //handle mouse wheel zoom
1525
    if (ev->isOfType(SoMouseWheelEvent::getClassTypeId())) {
1526
        auto const event = static_cast<const SoMouseWheelEvent *>(ev);
1527
        processed = processWheelEvent(event);
1528
        viewer->processSoEventBase(ev);
1529
        offeredtoViewerEventBase = true;
1530
    }
1531

1532
    if (!processed && !offeredtoViewerEventBase) {
1533
        processed = viewer->processSoEventBase(ev);
1534
    }
1535

1536
    return processed;
1537
}
1538

1539
void NavigationStyle::syncWithEvent(const SoEvent * const ev)
1540
{
1541
    // Events when in "ready-to-seek" mode are ignored, except those
1542
    // which influence the seek mode itself -- these are handled further
1543
    // up the inheritance hierarchy.
1544
    if (this->isSeekMode()) {
1545
        return;
1546
    }
1547

1548
    const SoType type(ev->getTypeId());
1549

1550
    // Mismatches in state of the modifier keys happens if the user
1551
    // presses or releases them outside the viewer window.
1552
    syncModifierKeys(ev);
1553

1554
    // Keyboard handling
1555
    if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
1556
        auto const event = static_cast<const SoKeyboardEvent *>(ev);
1557
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1558
        switch (event->getKey()) {
1559
        case SoKeyboardEvent::LEFT_CONTROL:
1560
        case SoKeyboardEvent::RIGHT_CONTROL:
1561
            this->ctrldown = press;
1562
            break;
1563
        case SoKeyboardEvent::LEFT_SHIFT:
1564
        case SoKeyboardEvent::RIGHT_SHIFT:
1565
            this->shiftdown = press;
1566
            break;
1567
        case SoKeyboardEvent::LEFT_ALT:
1568
        case SoKeyboardEvent::RIGHT_ALT:
1569
            this->altdown = press;
1570
            break;
1571
        default:
1572
            break;
1573
        }
1574
    }
1575

1576
    // Mouse Button / Spaceball Button handling
1577
    if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
1578
        auto const event = static_cast<const SoMouseButtonEvent *>(ev);
1579
        const int button = event->getButton();
1580
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1581

1582
        // SoDebugError::postInfo("processSoEvent", "button = %d", button);
1583
        switch (button) {
1584
        case SoMouseButtonEvent::BUTTON1:
1585
            this->button1down = press;
1586
            break;
1587
        case SoMouseButtonEvent::BUTTON2:
1588
            this->button2down = press;
1589
            break;
1590
        case SoMouseButtonEvent::BUTTON3:
1591
            this->button3down = press;
1592
            break;
1593
        default:
1594
            break;
1595
        }
1596
    }
1597
}
1598

1599
SbBool NavigationStyle::processMotionEvent(const SoMotion3Event * const ev)
1600
{
1601
    SoCamera * const camera = viewer->getSoRenderManager()->getCamera();
1602
    if (!camera)
1603
        return false;
1604

1605
    SbViewVolume volume(camera->getViewVolume());
1606
    SbVec3f center(volume.getSightPoint(camera->focalDistance.getValue()));
1607
    float scale(volume.getWorldToScreenScale(center, 1.0));
1608
    float translationFactor = scale * .0001;
1609

1610
    SbVec3f dir = ev->getTranslation();
1611

1612
    if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())){
1613
        auto oCam = static_cast<SoOrthographicCamera *>(camera);
1614
        oCam->scaleHeight(1.0 + (dir[2] * 0.0001));
1615
        dir[2] = 0.0;//don't move the cam for z translation.
1616
    }
1617

1618
    SbRotation newRotation(ev->getRotation() * camera->orientation.getValue());
1619
    SbVec3f newPosition, newDirection;
1620
    newRotation.multVec(SbVec3f(0.0, 0.0, -1.0), newDirection);
1621
    newPosition = center - (newDirection * camera->focalDistance.getValue());
1622

1623
    camera->orientation.setValue(newRotation);
1624
    camera->orientation.getValue().multVec(dir,dir);
1625
    camera->position = newPosition + (dir * translationFactor);
1626

1627
    return true;
1628
}
1629

1630
SbBool NavigationStyle::processKeyboardEvent(const SoKeyboardEvent * const event)
1631
{
1632
    SbBool processed = false;
1633
    const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1634
    switch (event->getKey()) {
1635
    case SoKeyboardEvent::LEFT_CONTROL:
1636
    case SoKeyboardEvent::RIGHT_CONTROL:
1637
        this->ctrldown = press;
1638
        break;
1639
    case SoKeyboardEvent::LEFT_SHIFT:
1640
    case SoKeyboardEvent::RIGHT_SHIFT:
1641
        this->shiftdown = press;
1642
        break;
1643
    case SoKeyboardEvent::LEFT_ALT:
1644
    case SoKeyboardEvent::RIGHT_ALT:
1645
        this->altdown = press;
1646
        break;
1647
    case SoKeyboardEvent::S:
1648
    case SoKeyboardEvent::HOME:
1649
    case SoKeyboardEvent::LEFT_ARROW:
1650
    case SoKeyboardEvent::UP_ARROW:
1651
    case SoKeyboardEvent::RIGHT_ARROW:
1652
    case SoKeyboardEvent::DOWN_ARROW:
1653
        if (!this->isViewing())
1654
            this->setViewing(true);
1655
        break;
1656
    case SoKeyboardEvent::PAGE_UP:
1657
    {
1658
        processed = true;
1659
        const SbVec2f posn = normalizePixelPos(event->getPosition());
1660
        doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn);
1661
        break;
1662
    }
1663
    case SoKeyboardEvent::PAGE_DOWN:
1664
    {
1665
        processed = true;
1666
        const SbVec2f posn = normalizePixelPos(event->getPosition());
1667
        doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn);
1668
        break;
1669
    }
1670
    default:
1671
        break;
1672
    }
1673

1674
    return processed;
1675
}
1676

1677
SbBool NavigationStyle::processClickEvent(const SoMouseButtonEvent * const event)
1678
{
1679
    // issue #0002433: avoid to swallow the UP event if down the
1680
    // scene graph somewhere a dialog gets opened
1681
    SbBool processed = false;
1682
    const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1683
    if (press) {
1684
        SbTime tmp = (event->getTime() - mouseDownConsumedEvent.getTime());
1685
        float dci = (float)QApplication::doubleClickInterval()/1000.0f;
1686
        // a double-click?
1687
        if (tmp.getValue() < dci) {
1688
            mouseDownConsumedEvent = *event;
1689
            mouseDownConsumedEvent.setTime(event->getTime());
1690
            processed = true;
1691
        }
1692
        else {
1693
            mouseDownConsumedEvent.setTime(event->getTime());
1694
            // 'ANY' is used to mark that we don't know yet if it will
1695
            // be a double-click event.
1696
            mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY);
1697
        }
1698
    }
1699
    else if (!press) {
1700
        if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) {
1701
            // now handle the postponed event
1702
            NavigationStyle::processSoEvent(&mouseDownConsumedEvent);
1703
            mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY);
1704
        }
1705
    }
1706

1707
    return processed;
1708
}
1709

1710
SbBool NavigationStyle::processWheelEvent(const SoMouseWheelEvent * const event)
1711
{
1712
    const SbVec2s pos(event->getPosition());
1713
    const SbVec2f posn = normalizePixelPos(pos);
1714

1715
    //handle mouse wheel zoom
1716
    doZoom(viewer->getSoRenderManager()->getCamera(),
1717
           event->getDelta(), posn);
1718
    return true;
1719
}
1720

1721
void NavigationStyle::setPopupMenuEnabled(const SbBool on)
1722
{
1723
    this->menuenabled = on;
1724
}
1725

1726
SbBool NavigationStyle::isPopupMenuEnabled() const
1727
{
1728
    return this->menuenabled;
1729
}
1730

1731
void NavigationStyle::openPopupMenu(const SbVec2s& position)
1732
{
1733
    Q_UNUSED(position);
1734
    // ask workbenches and view provider, ...
1735
    MenuItem view;
1736
    Gui::Application::Instance->setupContextMenu("View", &view);
1737

1738
    auto contextMenu = new QMenu(viewer->getGLWidget());
1739
    MenuManager::getInstance()->setupContextMenu(&view, *contextMenu);
1740
    contextMenu->setAttribute(Qt::WA_DeleteOnClose);
1741

1742
    auto navMenu = contextMenu->addMenu(QObject::tr("Navigation styles"));
1743
    auto navMenuGroup = new QActionGroup(navMenu);
1744

1745
    // add submenu at the end to select navigation style
1746
    const std::map<Base::Type, std::string> styles = UserNavigationStyle::getUserFriendlyNames();
1747
    for (const auto &style : styles) {
1748
        const QString name = QApplication::translate(style.first.getName(), style.second.c_str());
1749
        QAction *item = navMenuGroup->addAction(name);
1750
        navMenu->addAction(item);
1751
        item->setCheckable(true);
1752

1753
        if (const Base::Type item_style = style.first; item_style != this->getTypeId()) {
1754
            auto triggeredFun = [this, item_style](){
1755
                QWidget *widget = viewer->getWidget();
1756
                while (widget && !widget->inherits("Gui::View3DInventor"))
1757
                    widget = widget->parentWidget();
1758
                if (widget) {
1759
                    // this is the widget where the viewer is embedded
1760
                    QEvent *ns_event = new NavigationStyleEvent(item_style);
1761
                    QApplication::postEvent(widget, ns_event);
1762
                }
1763
            };
1764
            item->connect(item, &QAction::triggered, triggeredFun);
1765
        } else
1766
            item->setChecked(true);
1767
    }
1768

1769
    contextMenu->popup(QCursor::pos());
1770
}
1771

1772
// ----------------------------------------------------------------------------------
1773

1774
TYPESYSTEM_SOURCE_ABSTRACT(Gui::UserNavigationStyle,Gui::NavigationStyle)
1775

1776
std::string UserNavigationStyle::userFriendlyName() const
1777
{
1778
    std::string name = this->getTypeId().getName();
1779
    // remove namespaces
1780
    std::size_t pos = name.rfind("::");
1781
    if (pos != std::string::npos)
1782
        name = name.substr(pos + 2);
1783

1784
    // remove 'NavigationStyle'
1785
    pos = name.find("NavigationStyle");
1786
    if (pos != std::string::npos)
1787
        name = name.substr(0, pos);
1788
    return name;
1789
}
1790

1791
std::map<Base::Type, std::string> UserNavigationStyle::getUserFriendlyNames()
1792
{
1793
    std::map<Base::Type, std::string> names;
1794
    std::vector<Base::Type> types;
1795
    Base::Type::getAllDerivedFrom(UserNavigationStyle::getClassTypeId(), types);
1796

1797
    for (auto & type : types) {
1798
        if (type != UserNavigationStyle::getClassTypeId()) {
1799
            std::unique_ptr<UserNavigationStyle> inst(static_cast<UserNavigationStyle*>(type.createInstance()));
1800
            if (inst) {
1801
                names[type] = inst->userFriendlyName();
1802
            }
1803
        }
1804
    }
1805
    return names;
1806
}
1807

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.