FreeCAD

Форк
0
/
NavigationStyle.cpp 
1766 строк · 57.2 Кб
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
SbBool NavigationStyle::lookAtPoint(const SbVec2s screenpos)
331
{
332
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
333
    if (!cam)
334
        return false;
335

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

341
    SoPickedPoint * picked = rpaction.getPickedPoint();
342
    if (!picked) {
343
        this->interactiveCountInc();
344
        return false;
345
    }
346

347
    SbVec3f hitpoint;
348
    hitpoint = picked->getPoint();
349
    lookAtPoint(hitpoint);
350
    return true;
351
}
352

353
void NavigationStyle::lookAtPoint(const SbVec3f& position)
354
{
355
    this->rotationCenterFound = false;
356
    translateCamera(position - getFocalPoint());
357
}
358

359
SoCamera* NavigationStyle::getCamera() const
360
{
361
    return this->viewer->getCamera();
362
}
363

364
void NavigationStyle::setCameraOrientation(const SbRotation& orientation, SbBool moveToCenter)
365
{
366
    SoCamera* camera = getCamera();
367
    if (!camera)
368
        return;
369

370
    animator->stop();
371

372
    SbVec3f focalPoint = getFocalPoint();
373
    SbVec3f translation(0, 0, 0);
374

375
    if (moveToCenter) {
376
        SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
377
        action.apply(viewer->getSceneGraph());
378
        SbBox3f box = action.getBoundingBox();
379
        if (!box.isEmpty()) {
380
            translation = box.getCenter() - focalPoint;
381
        }
382
    }
383

384
    // Start an animation or set the pose directly
385
    if (isAnimationEnabled()) {
386
        viewer->startAnimation(orientation, focalPoint, translation);
387
    }
388
    else {
389
        // Distance from rotation center to camera position in camera coordinate system
390
        SbVec3f rotationCenterDistanceCam = camera->focalDistance.getValue() * SbVec3f(0, 0, 1);
391

392
        // Set to the given orientation
393
        camera->orientation = orientation;
394

395
        // Distance from rotation center to new camera position in global coordinate system
396
        SbVec3f newRotationCenterDistance;
397
        camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance);
398

399
        // Reposition camera so the rotation center stays in the same place
400
        // Optionally add translation to move to center
401
        camera->position = focalPoint + newRotationCenterDistance + translation;
402
    }
403
}
404

405
void NavigationStyle::translateCamera(const SbVec3f& translation)
406
{
407
    SoCamera* camera = getCamera();
408
    if (!camera)
409
        return;
410

411
    animator->stop();
412

413
    // Start an animation or set the pose directly
414
    if (isAnimationEnabled()) {
415
        viewer->startAnimation(camera->orientation.getValue(), SbVec3f(0, 0, 0), translation);
416
    }
417
    else {
418
        camera->position = camera->position.getValue() + translation;
419
    }
420
}
421

422
void NavigationStyle::boxZoom(const SbBox2s& box)
423
{
424
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
425
    if (!cam) // no camera
426
        return;
427
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
428
    SbViewVolume vv = cam->getViewVolume(vp.getViewportAspectRatio());
429

430
    short sizeX{},sizeY{};
431
    box.getSize(sizeX, sizeY);
432
    SbVec2s size = vp.getViewportSizePixels();
433

434
    // The bbox must not be empty i.e. width and length is zero, but it is possible that
435
    // either width or length is zero
436
    if (sizeX == 0 && sizeY == 0)
437
        return;
438

439
    // Get the new center in normalized pixel coordinates
440
    short xmin{},xmax{},ymin{},ymax{};
441
    box.getBounds(xmin,ymin,xmax,ymax);
442
    const SbVec2f center((float) ((xmin+xmax)/2) / (float) std::max((int)(size[0] - 1), 1),
443
                         (float) (size[1]-(ymin+ymax)/2) / (float) std::max((int)(size[1] - 1), 1));
444

445
    SbPlane plane = vv.getPlane(cam->focalDistance.getValue());
446
    panCamera(cam,vp.getViewportAspectRatio(),plane, SbVec2f(0.5,0.5), center);
447

448
    // Set height or height angle of the camera
449
    float scaleX = (float)sizeX/(float)size[0];
450
    float scaleY = (float)sizeY/(float)size[1];
451
    float scale = std::max<float>(scaleX, scaleY);
452
    if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
453
        float height = static_cast<SoOrthographicCamera*>(cam)->height.getValue() * scale;
454
        static_cast<SoOrthographicCamera*>(cam)->height = height;
455
    }
456
    else if (cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
457
        float height = static_cast<SoPerspectiveCamera*>(cam)->heightAngle.getValue() / 2.0f;
458
        height = 2.0f * atan(tan(height) * scale);
459
        static_cast<SoPerspectiveCamera*>(cam)->heightAngle = height;
460
    }
461
}
462

463
void NavigationStyle::viewAll()
464
{
465
    // Get the bounding box of the scene
466
    SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
467
    action.apply(viewer->getSceneGraph());
468
    SbBox3f box = action.getBoundingBox();
469
    if (box.isEmpty())
470
        return;
471

472

473
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
474
    if (!cam)
475
        return;
476

477
    SbViewVolume  vol = cam->getViewVolume();
478
    if (vol.ulf == vol.llf)
479
        return; // empty frustum (no view up vector defined)
480
    SbVec2f s = vol.projectBox(box);
481
    SbVec2s size = viewer->getSoRenderManager()->getSize();
482

483
    SbVec3f pt1, pt2, pt3, tmp;
484
    vol.projectPointToLine( SbVec2f(0.0f,0.0f), pt1, tmp );
485
    vol.projectPointToLine( SbVec2f(s[0],0.0f), pt2, tmp );
486
    vol.projectPointToLine( SbVec2f(0.0f,s[1]), pt3, tmp );
487

488
    float cam_width = (pt2-pt1).length();
489
    float cam_height = (pt3-pt1).length();
490

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

494
    float aspect = cam->aspectRatio.getValue();
495

496
    if (cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
497
        auto ocam = static_cast<SoOrthographicCamera *>(cam);
498
        if (aspect < 1.0f)
499
            ocam->height = cam_height / aspect;
500
        else
501
            ocam->height = cam_height;
502
    }
503
}
504

505
void NavigationStyle::findBoundingSphere() {
506
    // Find a bounding sphere for the scene
507
    SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
508
    action.apply(viewer->getSceneGraph());
509
    boundingSphere.circumscribe(action.getBoundingBox());
510
}
511

512
/** Rotate the camera by the given amount, then reposition it so we're still pointing at the same
513
 * focal point
514
 */
515
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation)
516
{
517
    reorientCamera(camera, rotation, getFocalPoint());
518
}
519

520
/** Rotate the camera by the given amount, then reposition it so the rotation center stays in the
521
 * same place
522
 */
523
void NavigationStyle::reorientCamera(SoCamera* camera, const SbRotation& rotation, const SbVec3f& rotationCenter)
524
{
525
    if (!camera) {
526
        return;
527
    }
528

529
    // Distance from rotation center to camera position in camera coordinate system
530
    SbVec3f rotationCenterDistanceCam;
531
    camera->orientation.getValue().inverse().multVec(camera->position.getValue() - rotationCenter, rotationCenterDistanceCam);
532

533
    // Set new orientation value by accumulating the new rotation
534
    camera->orientation = rotation * camera->orientation.getValue();
535

536
    // Distance from rotation center to new camera position in global coordinate system
537
    SbVec3f newRotationCenterDistance;
538
    camera->orientation.getValue().multVec(rotationCenterDistanceCam, newRotationCenterDistance);
539

540
    // Reposition camera so the rotation center stays in the same place
541
    camera->position = rotationCenter + newRotationCenterDistance;
542

543
    // Fix issue with near clipping in orthogonal view
544
     if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
545

546
         // The center of the bounding sphere in camera coordinate system
547
         SbVec3f center;
548
         camera->orientation.getValue().inverse().multVec(boundingSphere.getCenter() - camera->position.getValue(), center);
549

550
         SbVec3f dir;
551
         camera->orientation.getValue().multVec(SbVec3f(0, 0, -1), dir);
552

553
         // Reposition the camera but keep the focal point the same
554
         // nearDistance is 0 and farDistance is the diameter of the bounding sphere
555
         float repositionDistance = -center.getValue()[2] - boundingSphere.getRadius();
556
         camera->position = camera->position.getValue() + repositionDistance * dir;
557
         camera->nearDistance = 0;
558
         camera->farDistance = 2 * boundingSphere.getRadius();
559
         camera->focalDistance = camera->focalDistance.getValue() - repositionDistance;
560
     }
561
}
562

563
void NavigationStyle::panCamera(SoCamera * cam, float aspectratio, const SbPlane & panplane,
564
                                const SbVec2f & currpos, const SbVec2f & prevpos)
565
{
566
    if (!cam) // can happen for empty scenegraph
567
        return;
568
    if (currpos == prevpos) // useless invocation
569
        return;
570

571

572
    // Find projection points for the last and current mouse coordinates.
573
    SbViewVolume vv = cam->getViewVolume(aspectratio);
574

575
    // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping
576
    if(aspectratio < 1.0)
577
        vv.scale(1.0 / aspectratio);
578

579
    SbLine line;
580
    vv.projectPointToLine(currpos, line);
581
    SbVec3f current_planept;
582
    panplane.intersect(line, current_planept);
583
    vv.projectPointToLine(prevpos, line);
584
    SbVec3f old_planept;
585
    panplane.intersect(line, old_planept);
586

587
    // Reposition camera according to the vector difference between the
588
    // projected points.
589
    cam->position = cam->position.getValue() - (current_planept - old_planept);
590

591
    if (this->currentmode != NavigationStyle::IDLE) {
592
        hasPanned = true;
593
    }
594
}
595

596
void NavigationStyle::pan(SoCamera* camera)
597
{
598
    // The plane we're projecting the mouse coordinates to get 3D
599
    // coordinates should stay the same during the whole pan
600
    // operation, so we should calculate this value here.
601
    if (!camera) { // can happen for empty scenegraph
602
        this->panningplane = SbPlane(SbVec3f(0, 0, 1), 0);
603
    }
604
    else {
605
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
606
        float aspectratio = vp.getViewportAspectRatio();
607
        SbViewVolume vv = camera->getViewVolume(aspectratio);
608

609
        // See note in Coin docs for SoCamera::getViewVolume re:viewport mapping
610
        if(aspectratio < 1.0)
611
            vv.scale(1.0 / aspectratio);
612

613
        this->panningplane = vv.getPlane(camera->focalDistance.getValue());
614
    }
615
}
616

617
void NavigationStyle::panToCenter(const SbPlane & pplane, const SbVec2f & currpos)
618
{
619
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
620
    float ratio = vp.getViewportAspectRatio();
621
    panCamera(viewer->getSoRenderManager()->getCamera(), ratio, pplane, SbVec2f(0.5,0.5), currpos);
622
    this->rotationCenterFound = false;
623
}
624

625
/** Dependent on the camera type this will either shrink or expand the
626
 * height of the viewport (orthogonal camera) or move the camera
627
 * closer or further away from the focal point in the scene.
628
 */
629
void NavigationStyle::zoom(SoCamera * cam, float diffvalue)
630
{
631
    if (!cam) // can happen for empty scenegraph
632
        return;
633

634
    animator->stop();
635

636
    SoType t = cam->getTypeId();
637
    SbName tname = t.getName();
638

639
    // This will be in the range of <0, ->>.
640
    auto multiplicator = float(exp(diffvalue));
641

642
    if (t.isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
643

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

649
        auto oc = static_cast<SoOrthographicCamera *>(cam);
650
        oc->height = oc->height.getValue() * multiplicator;
651

652
    }
653
    else {
654
        // FrustumCamera can be found in the SmallChange CVS module (it's
655
        // a camera that lets you specify (for instance) an off-center
656
        // frustum (similar to glFrustum())
657
        if (!t.isDerivedFrom(SoPerspectiveCamera::getClassTypeId()) &&
658
            tname != "FrustumCamera") {
659
 /*         static SbBool first = true;
660
            if (first) {
661
                SoDebugError::postWarning("SoGuiFullViewerP::zoom",
662
                                          "Unknown camera type, "
663
                                          "will zoom by moving position, but this might not be correct.");
664
                first = false;
665
            }*/
666
        }
667

668
        const float oldfocaldist = cam->focalDistance.getValue();
669
        const float newfocaldist = oldfocaldist * multiplicator;
670

671
        SbVec3f direction;
672
        cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
673

674
        const SbVec3f oldpos = cam->position.getValue();
675
        const SbVec3f newpos = oldpos + (newfocaldist - oldfocaldist) * -direction;
676

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

703
    if (this->currentmode != NavigationStyle::IDLE) {
704
        hasZoomed = true;
705
    }
706
}
707

708
// Calculate a zoom/dolly factor from the difference of the current
709
// cursor position and the last.
710
void NavigationStyle::zoomByCursor(const SbVec2f & thispos, const SbVec2f & prevpos)
711
{
712
    // There is no "geometrically correct" value, 20 just seems to give
713
    // about the right "feel".
714
    float value = (thispos[1] - prevpos[1]) * 10.0f/*20.0f*/;
715
    if (this->invertZoom)
716
        value = -value;
717
    zoom(viewer->getSoRenderManager()->getCamera(), value);
718
}
719

720
void NavigationStyle::zoomIn()
721
{
722
    zoom(viewer->getSoRenderManager()->getCamera(), -this->zoomStep);
723
}
724

725
void NavigationStyle::zoomOut()
726
{
727
    zoom(viewer->getSoRenderManager()->getCamera(), this->zoomStep);
728
}
729

730
/*!
731
 * Returns the steps if the mouse wheel is rotated
732
 */
733
int NavigationStyle::getDelta() const
734
{
735
    return 120;
736
}
737

738
void NavigationStyle::doZoom(SoCamera* camera, int wheeldelta, const SbVec2f& pos)
739
{
740
    float value = this->zoomStep * wheeldelta / float(getDelta());
741
    if (this->invertZoom)
742
        value = -value;
743
    doZoom(camera, value, pos);
744
}
745

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

765
    zoom(camera, logfactor);
766

767
    if (zoomAtCur) {
768
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
769
        float ratio = vp.getViewportAspectRatio();
770
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
771
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
772
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5,0.5));
773

774
        // Change the position of the rotation center indicator after zooming at cursor
775
        // Rotation mode is WindowCenter
776
        if (!rotationCenterMode) {
777
            viewer->changeRotationCenterPosition(getFocalPoint());
778
            findBoundingSphere();
779
        }
780
    }
781
}
782

783
void NavigationStyle::doRotate(SoCamera * camera, float angle, const SbVec2f& pos)
784
{
785
    SbBool zoomAtCur = this->zoomAtCursor;
786
    if (zoomAtCur) {
787
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
788
        float ratio = vp.getViewportAspectRatio();
789
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
790
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
791
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, SbVec2f(0.5,0.5), pos);
792
    }
793

794
    SbRotation rotcam = camera->orientation.getValue();
795
    //get view direction
796
    SbVec3f vdir;
797
    rotcam.multVec(SbVec3f(0,0,-1),vdir);
798
    //rotate
799
    SbRotation drot(vdir,angle);
800
    camera->orientation.setValue(rotcam * drot);
801

802
    if (zoomAtCur) {
803
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
804
        float ratio = vp.getViewportAspectRatio();
805
        SbViewVolume vv = camera->getViewVolume(vp.getViewportAspectRatio());
806
        SbPlane panplane = vv.getPlane(camera->focalDistance.getValue());
807
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, pos, SbVec2f(0.5,0.5));
808
    }
809

810
}
811

812
SbVec3f NavigationStyle::getRotationCenter(SbBool& found) const
813
{
814
    found = this->rotationCenterFound;
815
    return this->rotationCenter;
816
}
817

818
void NavigationStyle::setRotationCenter(const SbVec3f& cnt)
819
{
820
    this->rotationCenter = cnt;
821
    this->rotationCenterFound = true;
822
}
823

824
SbVec3f NavigationStyle::getFocalPoint() const
825
{
826
    SoCamera* cam = viewer->getSoRenderManager()->getCamera();
827
    if (!cam)
828
        return {0,0,0};
829

830
    // Find global coordinates of focal point.
831
    SbVec3f direction;
832
    cam->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
833
    SbVec3f focal = cam->position.getValue() +
834
                    cam->focalDistance.getValue() * direction;
835
    return focal;
836
}
837

838
/** Uses the sphere sheet projector to map the mouseposition onto
839
 * a 3D point and find a rotation from this and the last calculated point.
840
 */
841
void NavigationStyle::spin(const SbVec2f & pointerpos)
842
{
843
    if (this->log.historysize < 2)
844
        return;
845
    assert(this->spinprojector);
846

847
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
848
    SbVec2s glsize(vp.getViewportSizePixels());
849
    SbVec2f lastpos;
850
    lastpos[0] = float(this->log.position[1][0]) / float(std::max((int)(glsize[0]-1), 1));
851
    lastpos[1] = float(this->log.position[1][1]) / float(std::max((int)(glsize[1]-1), 1));
852

853
    if (this->rotationCenterMode && this->rotationCenterFound) {
854
        SbVec3f hitpoint = this->rotationCenter;
855

856
        // set to the given position
857
        SbVec3f direction;
858
        viewer->getSoRenderManager()->getCamera()->orientation.getValue().multVec(SbVec3f(0, 0, -1), direction);
859
        viewer->getSoRenderManager()->getCamera()->position = hitpoint - viewer->getSoRenderManager()->getCamera()->focalDistance.getValue() * direction;
860
    }
861

862
    // 0000333: Turntable camera rotation
863
    SbMatrix mat;
864
    viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat);
865
    this->spinprojector->setWorkingSpace(mat);
866

867
    this->spinprojector->project(lastpos);
868
    SbRotation r;
869
    this->spinprojector->projectAndGetRotation(pointerpos, r);
870
    float sensitivity = getSensitivity();
871
    if (sensitivity > 1.0f) {
872
        SbVec3f axis;
873
        float radians{};
874
        r.getValue(axis, radians);
875
        radians = sensitivity * radians;
876
        r.setValue(axis, radians);
877
    }
878
    r.invert();
879
    this->reorientCamera(viewer->getSoRenderManager()->getCamera(), r);
880

881
    if (this->rotationCenterMode && this->rotationCenterFound) {
882
        float ratio = vp.getViewportAspectRatio();
883
        SbViewVolume vv = viewer->getSoRenderManager()->getCamera()->getViewVolume(vp.getViewportAspectRatio());
884
        SbPlane panplane = vv.getPlane(viewer->getSoRenderManager()->getCamera()->focalDistance.getValue());
885
        SbVec2f posn;
886
        posn[0] = float(this->localPos[0]) / float(std::max((int)(glsize[0]-1), 1));
887
        posn[1] = float(this->localPos[1]) / float(std::max((int)(glsize[1]-1), 1));
888
        panCamera(viewer->getSoRenderManager()->getCamera(), ratio, panplane, posn, SbVec2f(0.5,0.5));
889
    }
890

891
    // Calculate an average angle magnitude value to make the transition
892
    // to a possible spin animation mode appear smooth.
893

894
    SbVec3f dummy_axis, newaxis;
895
    float acc_angle{}, newangle{};
896
    this->spinincrement.getValue(dummy_axis, acc_angle);
897
    acc_angle *= this->spinsamplecounter; // weight
898
    r.getValue(newaxis, newangle);
899
    acc_angle += newangle;
900

901
    this->spinsamplecounter++;
902
    acc_angle /= this->spinsamplecounter;
903
    // FIXME: accumulate and average axis vectors as well? 19990501 mortene.
904
    this->spinincrement.setValue(newaxis, acc_angle);
905

906
    // Don't carry too much baggage, as that'll give unwanted results
907
    // when the user quickly trigger (as in "click-drag-release") a spin
908
    // animation.
909
    if (this->spinsamplecounter > 3) this->spinsamplecounter = 3;
910

911
    if (this->currentmode != NavigationStyle::IDLE) {
912
        hasDragged = true;
913
    }
914
}
915

916
/*!
917
 * \brief NavigationStyle::spin_simplified is a simplified version of
918
 * NavigationStyle::spin(..), which uses less global variables. Doesn't support
919
 * starting an animated spinning.
920
 *
921
 * \param cam the camera to affect. The rotation amount is determined by delta
922
 * (curpos-prevpos), and rotation axis is also affected by average pos.
923
 * \param curpos  current normalized position or mouse pointer
924
 * \param prevpos  previous normalized position of mouse pointer
925
 */
926
void NavigationStyle::spin_simplified(SoCamera* cam, SbVec2f curpos, SbVec2f prevpos){
927
    assert(this->spinprojector);
928

929
    // 0000333: Turntable camera rotation
930
    SbMatrix mat;
931
    viewer->getSoRenderManager()->getCamera()->orientation.getValue().getValue(mat);
932
    this->spinprojector->setWorkingSpace(mat);
933

934
    this->spinprojector->project(prevpos);
935
    SbRotation r;
936
    this->spinprojector->projectAndGetRotation(curpos, r);
937
    float sensitivity = getSensitivity();
938
    if (sensitivity > 1.0f) {
939
        SbVec3f axis;
940
        float radians{};
941
        r.getValue(axis, radians);
942
        radians = sensitivity * radians;
943
        r.setValue(axis, radians);
944
    }
945
    r.invert();
946
    this->reorientCamera(cam, r);
947

948
    hasDragged = true;
949
}
950

951
SbBool NavigationStyle::doSpin()
952
{
953
    if (this->log.historysize >= 3) {
954
        SbTime stoptime = (SbTime::getTimeOfDay() - this->log.time[0]);
955
        if (isSpinningAnimationEnabled() && stoptime.getValue() < 0.100) {
956
            const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
957
            const SbVec2s glsize(vp.getViewportSizePixels());
958
            SbVec3f from = this->spinprojector->project(SbVec2f(float(this->log.position[2][0]) / float(std::max(glsize[0]-1, 1)),
959
                                                                float(this->log.position[2][1]) / float(std::max(glsize[1]-1, 1))));
960
            SbVec3f to = this->spinprojector->project(this->lastmouseposition);
961
            SbRotation rot = this->spinprojector->getRotation(from, to);
962

963
            SbTime delta = (this->log.time[0] - this->log.time[2]);
964
            double deltatime = delta.getValue();
965
            rot.invert();
966
            rot.scaleAngle(float(0.200 / deltatime));
967

968
            SbVec3f axis;
969
            float radians{};
970
            rot.getValue(axis, radians);
971
            if ((radians > 0.01f) && (deltatime < 0.300)) {
972
                viewer->startSpinningAnimation(axis, radians * 5);
973
                return true;
974
            }
975
        }
976
    }
977

978
    return false;
979
}
980

981
void NavigationStyle::saveCursorPosition(const SoEvent * const ev)
982
{
983
    this->globalPos.setValue(QCursor::pos().x(), QCursor::pos().y());
984
    this->localPos = ev->getPosition();
985

986
    // mode is WindowCenter
987
    if (!this->rotationCenterMode) {
988
        setRotationCenter(getFocalPoint());
989
    }
990

991
    //Option to get point on model (slow) or always on focal plane (fast)
992
    //
993
    // mode is ScenePointAtCursor to get exact point if possible
994
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::ScenePointAtCursor) {
995
        SoRayPickAction rpaction(viewer->getSoRenderManager()->getViewportRegion());
996
        rpaction.setPoint(this->localPos);
997
        rpaction.setRadius(viewer->getPickRadius());
998
        rpaction.apply(viewer->getSoRenderManager()->getSceneGraph());
999

1000
        SoPickedPoint * picked = rpaction.getPickedPoint();
1001
        if (picked) {
1002
            setRotationCenter(picked->getPoint());
1003
            return;
1004
        }
1005
    }
1006

1007
    // mode is FocalPointAtCursor or a ScenePointAtCursor failed
1008
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::FocalPointAtCursor) {
1009
        // get the intersection point of the ray and the focal plane
1010
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1011
        float ratio = vp.getViewportAspectRatio();
1012

1013
        SoCamera* cam = viewer->getSoRenderManager()->getCamera();
1014
        if (!cam) // no camera
1015
            return;
1016
        SbViewVolume vv = cam->getViewVolume(ratio);
1017

1018
        SbLine line;
1019
        SbVec2f currpos = ev->getNormalizedPosition(vp);
1020
        vv.projectPointToLine(currpos, line);
1021
        SbVec3f current_planept;
1022
        SbPlane panplane = vv.getPlane(cam->focalDistance.getValue());
1023
        panplane.intersect(line, current_planept);
1024

1025
        setRotationCenter(current_planept);
1026
    }
1027

1028
    // mode is BoundingBoxCenter or a ScenePointAtCursor failed
1029
    if (this->rotationCenterMode & NavigationStyle::RotationCenterMode::BoundingBoxCenter) {
1030
        const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1031
        float ratio = vp.getViewportAspectRatio();
1032

1033
        SoCamera* cam = viewer->getSoRenderManager()->getCamera();
1034
        if (!cam) // no camera
1035
            return;
1036

1037
        // Get the bounding box center of the physical object group
1038
        SoGetBoundingBoxAction action(viewer->getSoRenderManager()->getViewportRegion());
1039
        action.apply(viewer->objectGroup);
1040
        SbBox3f boundingBox = action.getBoundingBox();
1041
        SbVec3f boundingBoxCenter = boundingBox.getCenter();
1042
        setRotationCenter(boundingBoxCenter);
1043

1044
        // To drag around the center point of the bbox we have to determine
1045
        // its projection on the screen because this information is used in
1046
        // NavigationStyle::spin() for the panning
1047
        SbViewVolume vv = cam->getViewVolume(ratio);
1048
        vv.projectToScreen(boundingBoxCenter, boundingBoxCenter);
1049
        SbVec2s size = vp.getViewportSizePixels();
1050
        auto tox = static_cast<short>(boundingBoxCenter[0] * size[0]);
1051
        auto toy = static_cast<short>(boundingBoxCenter[1] * size[1]);
1052
        this->localPos.setValue(tox, toy);
1053
    }
1054
}
1055

1056
SbVec2f NavigationStyle::normalizePixelPos(SbVec2s pixpos)
1057
{
1058
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1059
    const SbVec2s size(vp.getViewportSizePixels());
1060
    return {(float) pixpos[0] / (float) std::max((int)(size[0] - 1), 1),
1061
            (float) pixpos[1] / (float) std::max((int)(size[1] - 1), 1)};
1062
}
1063

1064
SbVec2f NavigationStyle::normalizePixelPos(SbVec2f pixpos)
1065
{
1066
    const SbViewportRegion & vp = viewer->getSoRenderManager()->getViewportRegion();
1067
    const SbVec2s size(vp.getViewportSizePixels());
1068
    return {pixpos[0] / (float) std::max((int)(size[0] - 1), 1),
1069
            pixpos[1] / (float) std::max((int)(size[1] - 1), 1)};
1070
}
1071

1072
void NavigationStyle::moveCursorPosition()
1073
{
1074
    if (!isResetCursorPosition())
1075
        return;
1076

1077
    QPoint cpos = QCursor::pos();
1078
    if (abs(cpos.x()-globalPos[0]) > 10 ||
1079
        abs(cpos.y()-globalPos[1]) > 10) {
1080
        QCursor::setPos(globalPos[0], globalPos[1]-1);
1081
        this->log.position[0] = localPos;
1082
    }
1083
}
1084

1085

1086
SbBool NavigationStyle::handleEventInForeground(const SoEvent* const e)
1087
{
1088
    SoHandleEventAction action(viewer->getSoRenderManager()->getViewportRegion());
1089
    action.setEvent(e);
1090
    action.setPickRadius(viewer->getPickRadius());
1091
    action.apply(viewer->foregroundroot);
1092
    return action.isHandled();
1093
}
1094

1095
/**
1096
 * @brief Decide if it should be possible to start any animation
1097
 *
1098
 * If the enable flag is false and we're currently animating, the animation will be stopped
1099
 */
1100
void NavigationStyle::setAnimationEnabled(const SbBool enable)
1101
{
1102
    animationEnabled = enable;
1103
    if (!enable && isAnimating()) {
1104
        animator->stop();
1105
    }
1106
}
1107

1108
/**
1109
 * @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
1110
 *
1111
 * If the enable flag is false and we're currently animating, the spin animation will be stopped
1112
 */
1113
void NavigationStyle::setSpinningAnimationEnabled(const SbBool enable)
1114
{
1115
    spinningAnimationEnabled = enable;
1116
    if (!enable && isSpinning()) {
1117
        animator->stop();
1118
    }
1119
}
1120

1121
/**
1122
 * @return Whether or not it is possible to start any animation
1123
 */
1124
SbBool NavigationStyle::isAnimationEnabled() const
1125
{
1126
    return animationEnabled;
1127
}
1128

1129
/**
1130
 * @return Whether or not it is possible to start a spinning animation e.g. after dragging
1131
 */
1132
SbBool NavigationStyle::isSpinningAnimationEnabled() const
1133
{
1134
    return animationEnabled && spinningAnimationEnabled;
1135
}
1136

1137
/**
1138
 * @return Whether or not any animation is currently active
1139
 */
1140
SbBool NavigationStyle::isAnimating() const
1141
{
1142
    return animator->isAnimating();
1143
}
1144

1145
/**
1146
 * @return Whether or not a spinning animation is currently active e.g. after a user drag
1147
 */
1148
SbBool NavigationStyle::isSpinning() const
1149
{
1150
    return currentmode == NavigationStyle::SPINNING;
1151
}
1152

1153
void NavigationStyle::startAnimating(const std::shared_ptr<NavigationAnimation>& animation, bool wait) const
1154
{
1155
    if (wait) {
1156
        animator->startAndWait(animation);
1157
    }
1158
    else {
1159
        animator->start(animation);
1160
    }
1161
}
1162

1163
void NavigationStyle::stopAnimating() const
1164
{
1165
    animator->stop();
1166
}
1167

1168
void NavigationStyle::setSensitivity(float val)
1169
{
1170
    this->sensitivity = val;
1171
}
1172

1173
float NavigationStyle::getSensitivity() const
1174
{
1175
    return this->sensitivity;
1176
}
1177

1178
void NavigationStyle::setResetCursorPosition(SbBool on)
1179
{
1180
    this->resetcursorpos = on;
1181
}
1182

1183
SbBool NavigationStyle::isResetCursorPosition() const
1184
{
1185
    return this->resetcursorpos;
1186
}
1187

1188
void NavigationStyle::setZoomInverted(SbBool on)
1189
{
1190
    this->invertZoom = on;
1191
}
1192

1193
SbBool NavigationStyle::isZoomInverted() const
1194
{
1195
    return this->invertZoom;
1196
}
1197

1198
void NavigationStyle::setZoomStep(float val)
1199
{
1200
    this->zoomStep = val;
1201
}
1202

1203
void NavigationStyle::setZoomAtCursor(SbBool on)
1204
{
1205
    this->zoomAtCursor = on;
1206
}
1207

1208
SbBool NavigationStyle::isZoomAtCursor() const
1209
{
1210
    return this->zoomAtCursor;
1211
}
1212

1213
void NavigationStyle::setRotationCenterMode(NavigationStyle::RotationCenterModes mode)
1214
{
1215
    this->rotationCenterMode = mode;
1216
}
1217

1218
NavigationStyle::RotationCenterModes NavigationStyle::getRotationCenterMode() const
1219
{
1220
    return this->rotationCenterMode;
1221
}
1222

1223
void NavigationStyle::startSelection(AbstractMouseSelection* mouse)
1224
{
1225
    if (!mouse)
1226
        return;
1227

1228
    if (mouseSelection) {
1229
        SoDebugError::postWarning("NavigationStyle::startSelection",
1230
                                  "Set new mouse selection while an old is still active.");
1231
    }
1232

1233
    mouseSelection = mouse;
1234
    mouseSelection->grabMouseModel(viewer);
1235
}
1236

1237
void NavigationStyle::startSelection(NavigationStyle::SelectionMode mode)
1238
{
1239
    if (mouseSelection)
1240
        return;
1241
    if (isSelecting())
1242
        stopSelection();
1243

1244
    switch (mode)
1245
    {
1246
    case Lasso:
1247
        mouseSelection = new PolyPickerSelection();
1248
        break;
1249
    case Rectangle:
1250
        mouseSelection = new RectangleSelection();
1251
        break;
1252
    case Rubberband:
1253
        mouseSelection = new RubberbandSelection();
1254
        break;
1255
    case BoxZoom:
1256
        mouseSelection = new BoxZoomSelection();
1257
        break;
1258
    case Clip:
1259
        mouseSelection = new PolyClipSelection();
1260
        break;
1261
    default:
1262
        break;
1263
    }
1264

1265
    if (mouseSelection)
1266
        mouseSelection->grabMouseModel(viewer);
1267
}
1268

1269
void NavigationStyle::abortSelection()
1270
{
1271
    pcPolygon.clear();
1272
    if (mouseSelection) {
1273
        mouseSelection->releaseMouseModel(true);
1274
        delete mouseSelection;
1275
        mouseSelection = nullptr;
1276
    }
1277
}
1278

1279
void NavigationStyle::stopSelection()
1280
{
1281
    pcPolygon.clear();
1282
    if (mouseSelection) {
1283
        mouseSelection->releaseMouseModel();
1284
        delete mouseSelection;
1285
        mouseSelection = nullptr;
1286
    }
1287
}
1288

1289
SbBool NavigationStyle::isSelecting() const
1290
{
1291
    return (mouseSelection ? true : false);
1292
}
1293

1294
const std::vector<SbVec2s>& NavigationStyle::getPolygon(SelectionRole* role) const
1295
{
1296
    if (role)
1297
       *role = this->selectedRole;
1298
    return pcPolygon;
1299
}
1300

1301
// This method adds another point to the mouse location log, used for spin
1302
// animation calculations.
1303
void NavigationStyle::addToLog(const SbVec2s pos, const SbTime time)
1304
{
1305
    // In case someone changes the const size setting at the top of this
1306
    // file too small.
1307
    assert (this->log.size > 2 && "mouse log too small!");
1308

1309
    if (this->log.historysize > 0 && pos == this->log.position[0]) {
1310
        return;
1311
    }
1312

1313
    int lastidx = this->log.historysize;
1314
    // If we've filled up the log, we should throw away the last item:
1315
    if (lastidx == this->log.size) { lastidx--; }
1316

1317
    assert(lastidx < this->log.size);
1318
    for (int i = lastidx; i > 0; i--) {
1319
        this->log.position[i] = this->log.position[i-1];
1320
        this->log.time[i] = this->log.time[i-1];
1321
    }
1322

1323
    this->log.position[0] = pos;
1324
    this->log.time[0] = time;
1325
    if (this->log.historysize < this->log.size)
1326
        this->log.historysize += 1;
1327
}
1328

1329
// This method "clears" the mouse location log, used for spin
1330
// animation calculations.
1331
void NavigationStyle::clearLog()
1332
{
1333
    this->log.historysize = 0;
1334
}
1335

1336
void NavigationStyle::syncModifierKeys(const SoEvent * const ev)
1337
{
1338
    // Mismatches in state of the modifier keys happens if the user
1339
    // presses or releases them outside the viewer window.
1340
    if (this->ctrldown != ev->wasCtrlDown()) {
1341
        this->ctrldown = ev->wasCtrlDown();
1342
    }
1343
    if (this->shiftdown != ev->wasShiftDown()) {
1344
        this->shiftdown = ev->wasShiftDown();
1345
    }
1346
    if (this->altdown != ev->wasAltDown()) {
1347
        this->altdown = ev->wasAltDown();
1348
    }
1349
}
1350

1351
// The viewer is a state machine, and all changes to the current state
1352
// are made through this call.
1353
void NavigationStyle::setViewingMode(const ViewerMode newmode)
1354
{
1355
    const ViewerMode oldmode = this->currentmode;
1356
    if (newmode == oldmode) {
1357
        return;
1358
    }
1359

1360
    if (newmode == NavigationStyle::IDLE) {
1361
        hasPanned = false;
1362
        hasDragged = false;
1363
        hasZoomed = false;
1364
    }
1365

1366
    switch (newmode) {
1367
    case DRAGGING:
1368
        // Set up initial projection point for the projector object when
1369
        // first starting a drag operation.
1370
        animator->stop();
1371
        viewer->showRotationCenter(true);
1372
        findBoundingSphere();
1373
        this->spinprojector->project(this->lastmouseposition);
1374
        this->interactiveCountInc();
1375
        this->clearLog();
1376
        break;
1377

1378
    case SPINNING:
1379
        this->interactiveCountInc();
1380
        viewer->getSoRenderManager()->scheduleRedraw();
1381
        break;
1382

1383
    case PANNING:
1384
        animator->stop();
1385
        pan(viewer->getSoRenderManager()->getCamera());
1386
        this->interactiveCountInc();
1387
        break;
1388

1389
    case ZOOMING:
1390
        animator->stop();
1391
        this->interactiveCountInc();
1392
        break;
1393

1394
    case BOXZOOM:
1395
        animator->stop();
1396
        this->interactiveCountInc();
1397
        break;
1398

1399
    default: // include default to avoid compiler warnings.
1400
        break;
1401
    }
1402

1403
    switch (oldmode) {
1404
    case SPINNING:
1405
    case DRAGGING:
1406
        viewer->showRotationCenter(false);
1407
        [[fallthrough]];
1408
    case PANNING:
1409
    case ZOOMING:
1410
    case BOXZOOM:
1411
        this->interactiveCountDec();
1412
        break;
1413

1414
    default:
1415
        break;
1416
    }
1417

1418
    viewer->setCursorRepresentation(newmode);
1419
    this->currentmode = newmode;
1420
}
1421

1422
int NavigationStyle::getViewingMode() const
1423
{
1424
    return (int)this->currentmode;
1425
}
1426

1427
SbBool NavigationStyle::processEvent(const SoEvent * const ev)
1428
{
1429
    // If we're in picking mode then all events must be redirected to the
1430
    // appropriate mouse model.
1431
    if (mouseSelection) {
1432
        int hd=mouseSelection->handleEvent(ev,viewer->getSoRenderManager()->getViewportRegion());
1433
        if (hd==AbstractMouseSelection::Continue||
1434
            hd==AbstractMouseSelection::Restart) {
1435
            return true;
1436
        }
1437
        else if (hd==AbstractMouseSelection::Finish) {
1438
            pcPolygon = mouseSelection->getPositions();
1439
            selectedRole = mouseSelection->selectedRole();
1440
            delete mouseSelection;
1441
            mouseSelection = nullptr;
1442
            syncWithEvent(ev);
1443
            return NavigationStyle::processSoEvent(ev);
1444
        }
1445
        else if (hd==AbstractMouseSelection::Cancel) {
1446
            pcPolygon.clear();
1447
            delete mouseSelection;
1448
            mouseSelection = nullptr;
1449
            syncWithEvent(ev);
1450
            return NavigationStyle::processSoEvent(ev);
1451
        }
1452
    }
1453

1454
    const ViewerMode curmode = this->currentmode;
1455

1456
    SbBool processed = false;
1457
    processed = this->processSoEvent(ev);
1458

1459
    // check for left click without selecting something
1460
    if ((curmode == NavigationStyle::SELECTION || curmode == NavigationStyle::IDLE)
1461
            && !processed) {
1462
        if (SoMouseButtonEvent::isButtonReleaseEvent(ev, SoMouseButtonEvent::BUTTON1)) {
1463
            if (!ev->wasCtrlDown()) {
1464
                Gui::Selection().clearSelection();
1465
            }
1466
        }
1467
    }
1468

1469
    return processed;
1470
}
1471

1472
SbBool NavigationStyle::processSoEvent(const SoEvent * const ev)
1473
{
1474
    bool processed = false;
1475
    bool offeredtoViewerEventBase = false;
1476

1477
    //handle mouse wheel zoom
1478
    if (ev->isOfType(SoMouseWheelEvent::getClassTypeId())) {
1479
        auto const event = static_cast<const SoMouseWheelEvent *>(ev);
1480
        processed = processWheelEvent(event);
1481
        viewer->processSoEventBase(ev);
1482
        offeredtoViewerEventBase = true;
1483
    }
1484

1485
    if (!processed && !offeredtoViewerEventBase) {
1486
        processed = viewer->processSoEventBase(ev);
1487
    }
1488

1489
    return processed;
1490
}
1491

1492
void NavigationStyle::syncWithEvent(const SoEvent * const ev)
1493
{
1494
    // Events when in "ready-to-seek" mode are ignored, except those
1495
    // which influence the seek mode itself -- these are handled further
1496
    // up the inheritance hierarchy.
1497
    if (this->isSeekMode()) {
1498
        return;
1499
    }
1500

1501
    const SoType type(ev->getTypeId());
1502

1503
    // Mismatches in state of the modifier keys happens if the user
1504
    // presses or releases them outside the viewer window.
1505
    syncModifierKeys(ev);
1506

1507
    // Keyboard handling
1508
    if (type.isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
1509
        auto const event = static_cast<const SoKeyboardEvent *>(ev);
1510
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1511
        switch (event->getKey()) {
1512
        case SoKeyboardEvent::LEFT_CONTROL:
1513
        case SoKeyboardEvent::RIGHT_CONTROL:
1514
            this->ctrldown = press;
1515
            break;
1516
        case SoKeyboardEvent::LEFT_SHIFT:
1517
        case SoKeyboardEvent::RIGHT_SHIFT:
1518
            this->shiftdown = press;
1519
            break;
1520
        case SoKeyboardEvent::LEFT_ALT:
1521
        case SoKeyboardEvent::RIGHT_ALT:
1522
            this->altdown = press;
1523
            break;
1524
        default:
1525
            break;
1526
        }
1527
    }
1528

1529
    // Mouse Button / Spaceball Button handling
1530
    if (type.isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
1531
        auto const event = static_cast<const SoMouseButtonEvent *>(ev);
1532
        const int button = event->getButton();
1533
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1534

1535
        // SoDebugError::postInfo("processSoEvent", "button = %d", button);
1536
        switch (button) {
1537
        case SoMouseButtonEvent::BUTTON1:
1538
            this->button1down = press;
1539
            break;
1540
        case SoMouseButtonEvent::BUTTON2:
1541
            this->button2down = press;
1542
            break;
1543
        case SoMouseButtonEvent::BUTTON3:
1544
            this->button3down = press;
1545
            break;
1546
        default:
1547
            break;
1548
        }
1549
    }
1550
}
1551

1552
SbBool NavigationStyle::processMotionEvent(const SoMotion3Event * const ev)
1553
{
1554
    SoCamera * const camera = viewer->getSoRenderManager()->getCamera();
1555
    if (!camera)
1556
        return false;
1557

1558
    SbViewVolume volume(camera->getViewVolume());
1559
    SbVec3f center(volume.getSightPoint(camera->focalDistance.getValue()));
1560
    float scale(volume.getWorldToScreenScale(center, 1.0));
1561
    float translationFactor = scale * .0001;
1562

1563
    SbVec3f dir = ev->getTranslation();
1564

1565
    if (camera->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())){
1566
        auto oCam = static_cast<SoOrthographicCamera *>(camera);
1567
        oCam->scaleHeight(1.0 + (dir[2] * 0.0001));
1568
        dir[2] = 0.0;//don't move the cam for z translation.
1569
    }
1570

1571
    SbRotation newRotation(ev->getRotation() * camera->orientation.getValue());
1572
    SbVec3f newPosition, newDirection;
1573
    newRotation.multVec(SbVec3f(0.0, 0.0, -1.0), newDirection);
1574
    newPosition = center - (newDirection * camera->focalDistance.getValue());
1575

1576
    camera->orientation.setValue(newRotation);
1577
    camera->orientation.getValue().multVec(dir,dir);
1578
    camera->position = newPosition + (dir * translationFactor);
1579

1580
    return true;
1581
}
1582

1583
SbBool NavigationStyle::processKeyboardEvent(const SoKeyboardEvent * const event)
1584
{
1585
    SbBool processed = false;
1586
    const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1587
    switch (event->getKey()) {
1588
    case SoKeyboardEvent::LEFT_CONTROL:
1589
    case SoKeyboardEvent::RIGHT_CONTROL:
1590
        this->ctrldown = press;
1591
        break;
1592
    case SoKeyboardEvent::LEFT_SHIFT:
1593
    case SoKeyboardEvent::RIGHT_SHIFT:
1594
        this->shiftdown = press;
1595
        break;
1596
    case SoKeyboardEvent::LEFT_ALT:
1597
    case SoKeyboardEvent::RIGHT_ALT:
1598
        this->altdown = press;
1599
        break;
1600
    case SoKeyboardEvent::S:
1601
    case SoKeyboardEvent::HOME:
1602
    case SoKeyboardEvent::LEFT_ARROW:
1603
    case SoKeyboardEvent::UP_ARROW:
1604
    case SoKeyboardEvent::RIGHT_ARROW:
1605
    case SoKeyboardEvent::DOWN_ARROW:
1606
        if (!this->isViewing())
1607
            this->setViewing(true);
1608
        break;
1609
    case SoKeyboardEvent::PAGE_UP:
1610
    {
1611
        processed = true;
1612
        const SbVec2f posn = normalizePixelPos(event->getPosition());
1613
        doZoom(viewer->getSoRenderManager()->getCamera(), getDelta(), posn);
1614
        break;
1615
    }
1616
    case SoKeyboardEvent::PAGE_DOWN:
1617
    {
1618
        processed = true;
1619
        const SbVec2f posn = normalizePixelPos(event->getPosition());
1620
        doZoom(viewer->getSoRenderManager()->getCamera(), -getDelta(), posn);
1621
        break;
1622
    }
1623
    default:
1624
        break;
1625
    }
1626

1627
    return processed;
1628
}
1629

1630
SbBool NavigationStyle::processClickEvent(const SoMouseButtonEvent * const event)
1631
{
1632
    // issue #0002433: avoid to swallow the UP event if down the
1633
    // scene graph somewhere a dialog gets opened
1634
    SbBool processed = false;
1635
    const SbBool press = event->getState() == SoButtonEvent::DOWN ? true : false;
1636
    if (press) {
1637
        SbTime tmp = (event->getTime() - mouseDownConsumedEvent.getTime());
1638
        float dci = (float)QApplication::doubleClickInterval()/1000.0f;
1639
        // a double-click?
1640
        if (tmp.getValue() < dci) {
1641
            mouseDownConsumedEvent = *event;
1642
            mouseDownConsumedEvent.setTime(event->getTime());
1643
            processed = true;
1644
        }
1645
        else {
1646
            mouseDownConsumedEvent.setTime(event->getTime());
1647
            // 'ANY' is used to mark that we don't know yet if it will
1648
            // be a double-click event.
1649
            mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY);
1650
        }
1651
    }
1652
    else if (!press) {
1653
        if (mouseDownConsumedEvent.getButton() == SoMouseButtonEvent::BUTTON1) {
1654
            // now handle the postponed event
1655
            NavigationStyle::processSoEvent(&mouseDownConsumedEvent);
1656
            mouseDownConsumedEvent.setButton(SoMouseButtonEvent::ANY);
1657
        }
1658
    }
1659

1660
    return processed;
1661
}
1662

1663
SbBool NavigationStyle::processWheelEvent(const SoMouseWheelEvent * const event)
1664
{
1665
    const SbVec2s pos(event->getPosition());
1666
    const SbVec2f posn = normalizePixelPos(pos);
1667

1668
    //handle mouse wheel zoom
1669
    doZoom(viewer->getSoRenderManager()->getCamera(),
1670
           event->getDelta(), posn);
1671
    return true;
1672
}
1673

1674
void NavigationStyle::setPopupMenuEnabled(const SbBool on)
1675
{
1676
    this->menuenabled = on;
1677
}
1678

1679
SbBool NavigationStyle::isPopupMenuEnabled() const
1680
{
1681
    return this->menuenabled;
1682
}
1683

1684
void NavigationStyle::openPopupMenu(const SbVec2s& position)
1685
{
1686
    Q_UNUSED(position);
1687
    // ask workbenches and view provider, ...
1688
    auto view = new MenuItem;
1689
    Gui::Application::Instance->setupContextMenu("View", view);
1690

1691
    QMenu contextMenu(viewer->getGLWidget());
1692
    QMenu subMenu;
1693
    QActionGroup subMenuGroup(&subMenu);
1694
    subMenuGroup.setExclusive(true);
1695
    subMenu.setTitle(QObject::tr("Navigation styles"));
1696

1697
    MenuManager::getInstance()->setupContextMenu(view, contextMenu);
1698
    contextMenu.addMenu(&subMenu);
1699

1700
    // add submenu at the end to select navigation style
1701
    std::map<Base::Type, std::string> styles = UserNavigationStyle::getUserFriendlyNames();
1702
    for (const auto & style : styles) {
1703
        QByteArray data(style.first.getName());
1704
        QString name = QApplication::translate(style.first.getName(), style.second.c_str());
1705

1706
        QAction* item = subMenuGroup.addAction(name);
1707
        item->setData(data);
1708
        item->setCheckable(true);
1709
        if (style.first == this->getTypeId())
1710
            item->setChecked(true);
1711
        subMenu.addAction(item);
1712
    }
1713

1714
    delete view;
1715
    QAction* used = contextMenu.exec(QCursor::pos());
1716
    if (used && subMenuGroup.actions().indexOf(used) >= 0 && used->isChecked()) {
1717
        QByteArray type = used->data().toByteArray();
1718
        QWidget* widget = viewer->getWidget();
1719
        while (widget && !widget->inherits("Gui::View3DInventor"))
1720
            widget = widget->parentWidget();
1721
        if (widget) {
1722
            // this is the widget where the viewer is embedded
1723
            Base::Type style = Base::Type::fromName((const char*)type);
1724
            if (style != this->getTypeId()) {
1725
                QEvent* event = new NavigationStyleEvent(style);
1726
                QApplication::postEvent(widget, event);
1727
            }
1728
        }
1729
    }
1730
}
1731

1732
// ----------------------------------------------------------------------------------
1733

1734
TYPESYSTEM_SOURCE_ABSTRACT(Gui::UserNavigationStyle,Gui::NavigationStyle)
1735

1736
std::string UserNavigationStyle::userFriendlyName() const
1737
{
1738
    std::string name = this->getTypeId().getName();
1739
    // remove namespaces
1740
    std::size_t pos = name.rfind("::");
1741
    if (pos != std::string::npos)
1742
        name = name.substr(pos + 2);
1743

1744
    // remove 'NavigationStyle'
1745
    pos = name.find("NavigationStyle");
1746
    if (pos != std::string::npos)
1747
        name = name.substr(0, pos);
1748
    return name;
1749
}
1750

1751
std::map<Base::Type, std::string> UserNavigationStyle::getUserFriendlyNames()
1752
{
1753
    std::map<Base::Type, std::string> names;
1754
    std::vector<Base::Type> types;
1755
    Base::Type::getAllDerivedFrom(UserNavigationStyle::getClassTypeId(), types);
1756

1757
    for (auto & type : types) {
1758
        if (type != UserNavigationStyle::getClassTypeId()) {
1759
            std::unique_ptr<UserNavigationStyle> inst(static_cast<UserNavigationStyle*>(type.createInstance()));
1760
            if (inst) {
1761
                names[type] = inst->userFriendlyName();
1762
            }
1763
        }
1764
    }
1765
    return names;
1766
}
1767

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

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

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

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