1
/***************************************************************************
2
* Copyright (c) 2017 Kustaa Nyholm <kustaa.nyholm@sparetimelabs.com> *
4
* This file is part of the FreeCAD CAx development system. *
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. *
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. *
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 *
21
***************************************************************************/
23
#include "PreCompiled.h"
31
# include <OpenGL/gl.h>
35
# include <boost/math/constants/constants.hpp>
36
# include <Inventor/nodes/SoOrthographicCamera.h>
37
# include <Inventor/events/SoEvent.h>
38
# include <Inventor/events/SoLocation2Event.h>
39
# include <Inventor/events/SoMouseButtonEvent.h>
40
# include <QApplication>
44
# include <QOpenGLTexture>
45
# include <QPainterPath>
49
#include <Base/Tools.h>
53
#include "Application.h"
56
#include "MainWindow.h"
57
#include "View3DInventorViewer.h"
58
#include "View3DInventor.h"
69
class NaviCubeImplementation {
71
explicit NaviCubeImplementation(Gui::View3DInventorViewer*);
72
~NaviCubeImplementation();
74
void createContextMenu(const std::vector<std::string>& cmd);
75
void createCubeFaceTextures();
77
void moveToCorner(NaviCube::Corner c);
78
void setLabels(const std::vector<std::string>& labels);
80
bool processSoEvent(const SoEvent* ev);
81
void setSize(int size);
122
Custom, Up, Right, Out
125
None, Main, Edge, Corner, Button
129
vector<Vector3f> vertexArray;
130
// The rotation is the standard orientation for the faces of the cube
131
// For the flat buttons the rotation contains the direction of the rotation
132
// The standard orientation is the desired camera orientation when a face is selected and
133
// rotate to nearest is disabled
136
struct LabelTexture {
137
vector<Vector3f> vertexArray;
139
QOpenGLTexture *texture = nullptr;
142
bool mousePressed(short x, short y);
143
bool mouseReleased(short x, short y);
144
bool mouseMoved(short x, short y);
145
PickId pickFace(short x, short y);
146
bool inDragZone(short x, short y);
152
void setHilite(PickId);
154
void addCubeFace(const Vector3f&, const Vector3f&, ShapeId, PickId, float rotZ = 0.0);
155
void addButtonFace(PickId, const SbVec3f& direction = SbVec3f(0, 0, 0));
157
QString str(const char* str);
158
QMenu* createNaviCubeMenu();
159
void drawNaviCube(bool picking, float opacity);
161
SbRotation getNearestOrientation(PickId pickId);
165
static int m_CubeWidgetSize;
167
QColor m_EmphaseColor;
168
QColor m_HiliteColor;
169
bool m_ShowCS = true;
170
PickId m_HiliteId = PickId::None;
171
double m_BorderWidth = 1.1;
172
bool m_RotateToNearest = true;
173
int m_NaviStepByTurn = 8;
174
float m_FontZoom = 0.3F;
175
float m_Chamfer = 0.12F;
176
std::string m_TextFont;
177
int m_FontWeight = 0;
178
int m_FontStretch = 0;
179
float m_InactiveOpacity = 0.5;
180
SbVec2s m_PosOffset = SbVec2s(0,0);
182
bool m_Prepared = false;
183
static vector<string> m_commands;
184
bool m_Draggable = false;
185
SbVec2s m_ViewSize = SbVec2s(0,0);
188
bool m_MouseDown = false;
189
bool m_Dragging = false;
190
bool m_MightDrag = false;
191
bool m_Hovering = false;
193
SbVec2f m_RelPos = SbVec2f(1.0f,1.0f);
194
SbVec2s m_PosAreaBase = SbVec2s(0,0);
195
SbVec2s m_PosAreaSize = SbVec2s(0,0);
197
QtGLFramebufferObject* m_PickingFramebuffer;
198
Gui::View3DInventorViewer* m_View3DInventorViewer;
200
map<PickId, Face> m_Faces;
201
map<PickId, LabelTexture> m_LabelTextures;
206
int NaviCubeImplementation::m_CubeWidgetSize = 132;
208
int NaviCube::getNaviCubeSize()
210
return NaviCubeImplementation::m_CubeWidgetSize;
213
NaviCube::NaviCube(Gui::View3DInventorViewer* viewer) {
214
m_NaviCubeImplementation = new NaviCubeImplementation(viewer);
217
NaviCube::~NaviCube() {
218
delete m_NaviCubeImplementation;
221
void NaviCube::drawNaviCube() {
222
m_NaviCubeImplementation->drawNaviCube();
225
void NaviCube::createContextMenu(const std::vector<std::string>& cmd) {
226
m_NaviCubeImplementation->createContextMenu(cmd);
229
bool NaviCube::processSoEvent(const SoEvent* ev) {
230
return m_NaviCubeImplementation->processSoEvent(ev);
233
vector<string> NaviCubeImplementation::m_commands;
235
void NaviCube::setCorner(Corner c) {
236
m_NaviCubeImplementation->moveToCorner(c);
239
void NaviCube::setOffset(int x, int y) {
240
m_NaviCubeImplementation->m_PosOffset = SbVec2s(x, y);
241
m_NaviCubeImplementation->m_ViewSize = SbVec2s(0,0);
244
bool NaviCube::isDraggable() {
245
return m_NaviCubeImplementation->m_Draggable;
248
void NaviCube::setDraggable(bool draggable) {
249
m_NaviCubeImplementation->m_Draggable = draggable;
252
void NaviCube::setSize(int size)
254
m_NaviCubeImplementation->setSize(size);
257
void NaviCube::setChamfer(float chamfer)
259
m_NaviCubeImplementation->m_Chamfer = min(max(0.05f, chamfer), 0.18f);
260
m_NaviCubeImplementation->m_Prepared = false;
263
void NaviCube::setNaviRotateToNearest(bool toNearest)
265
m_NaviCubeImplementation->m_RotateToNearest = toNearest;
268
void NaviCube::setNaviStepByTurn(int steps)
270
m_NaviCubeImplementation->m_NaviStepByTurn = steps;
273
void NaviCube::setFont(std::string font)
275
m_NaviCubeImplementation->m_TextFont = font;
276
m_NaviCubeImplementation->m_Prepared = false;
279
void NaviCube::setFontWeight(int weight)
281
m_NaviCubeImplementation->m_FontWeight = weight;
282
m_NaviCubeImplementation->m_Prepared = false;
285
void NaviCube::setFontStretch(int stretch)
287
m_NaviCubeImplementation->m_FontStretch = stretch;
288
m_NaviCubeImplementation->m_Prepared = false;
291
void NaviCube::setFontZoom(float zoom)
293
m_NaviCubeImplementation->m_FontZoom = zoom;
294
m_NaviCubeImplementation->m_Prepared = false;
297
void NaviCube::setBaseColor(QColor bColor)
299
m_NaviCubeImplementation->m_BaseColor = bColor;
302
void NaviCube::setEmphaseColor(QColor eColor)
304
m_NaviCubeImplementation->m_EmphaseColor = eColor;
305
m_NaviCubeImplementation->m_Prepared = false;
308
void NaviCube::setHiliteColor(QColor HiliteColor)
310
m_NaviCubeImplementation->m_HiliteColor = HiliteColor;
313
void NaviCube::setBorderWidth(double BorderWidth)
315
m_NaviCubeImplementation->m_BorderWidth = BorderWidth;
318
void NaviCube::setShowCS(bool showCS)
320
m_NaviCubeImplementation->m_ShowCS = showCS;
323
void NaviCube::setNaviCubeLabels(const std::vector<std::string>& labels)
325
m_NaviCubeImplementation->setLabels(labels);
328
void NaviCube::setInactiveOpacity(float opacity)
330
m_NaviCubeImplementation->m_InactiveOpacity = opacity;
333
void NaviCubeImplementation::setLabels(const std::vector<std::string>& labels)
335
m_LabelTextures[PickId::Front].label = labels[0];
336
m_LabelTextures[PickId::Top].label = labels[1];
337
m_LabelTextures[PickId::Right].label = labels[2];
338
m_LabelTextures[PickId::Rear].label = labels[3];
339
m_LabelTextures[PickId::Bottom].label = labels[4];
340
m_LabelTextures[PickId::Left].label = labels[5];
344
NaviCubeImplementation::NaviCubeImplementation(Gui::View3DInventorViewer* viewer)
345
: m_BaseColor{226, 232, 239}
346
, m_HiliteColor{170, 226, 255}
348
m_View3DInventorViewer = viewer;
349
m_PickingFramebuffer = nullptr;
350
m_Menu = createNaviCubeMenu();
353
NaviCubeImplementation::~NaviCubeImplementation()
356
if (m_PickingFramebuffer)
357
delete m_PickingFramebuffer;
358
for (auto tex: m_LabelTextures) {
359
delete tex.second.texture;
363
void NaviCubeImplementation::moveToCorner(NaviCube::Corner c) {
364
if (c == NaviCube::TopLeftCorner) m_RelPos = SbVec2f(0.0f, 1.0f);
365
else if (c == NaviCube::TopRightCorner) m_RelPos = SbVec2f(1.0f, 1.0f);
366
else if (c == NaviCube::BottomLeftCorner) m_RelPos = SbVec2f(0.0f, 0.0f);
367
else if (c == NaviCube::BottomRightCorner) m_RelPos = SbVec2f(1.0f, 0.0f);
370
auto convertWeights = [](int weight) -> QFont::Weight {
374
return QFont::ExtraBold;
378
return QFont::DemiBold;
380
return QFont::Medium;
382
return QFont::Normal;
386
return QFont::ExtraLight;
390
int imageVerticalBalance(QImage p, int sizeHint) {
396
int startRow = (h - sizeHint) / 2;
399
for (top = startRow; top < h; top++){
400
for (x = 0; x < p.width(); x++){
401
if (qAlpha(p.pixel(x, top))) {
408
for (bottom = startRow; bottom < h; bottom++) {
409
for (x = 0; x < p.width(); x++){
410
if (qAlpha(p.pixel(x, h-1-bottom)))
411
return (bottom-top)/2;
417
void NaviCubeImplementation::createCubeFaceTextures() {
418
int texSize = 192; // Works well for the max cube size 1024
420
if (m_TextFont.empty()) font.fromString(QStringLiteral("Arial"));
421
else font.fromString(QString::fromStdString(m_TextFont));
422
font.setStyleHint(QFont::SansSerif);
423
if (m_FontWeight > 0) {
424
font.setWeight(convertWeights(m_FontWeight));
426
if (m_FontStretch > 0) {
427
font.setStretch(m_FontStretch);
429
font.setPointSizeF(texSize);
430
QFontMetrics fm(font);
431
qreal minFontSize = texSize;
432
qreal maxFontSize = 0.;
433
vector<PickId> mains = {PickId::Front, PickId::Top, PickId::Right, PickId::Rear, PickId::Bottom, PickId::Left};
434
for (PickId pickId : mains) {
435
auto t = QString::fromUtf8(m_LabelTextures[pickId].label.c_str());
436
QRect br = fm.boundingRect(t);
437
float scale = (float)texSize / max(br.width(),br.height());
438
m_LabelTextures[pickId].fontSize = texSize * scale;
439
minFontSize = std::min(minFontSize, m_LabelTextures[pickId].fontSize);
440
maxFontSize = std::max(maxFontSize, m_LabelTextures[pickId].fontSize);
442
if (m_FontZoom > 0.0)
443
maxFontSize = minFontSize + (maxFontSize - minFontSize) * m_FontZoom;
445
maxFontSize = minFontSize * std::pow(2.0, m_FontZoom);
447
for (PickId pickId : mains) {
448
QImage image(texSize, texSize, QImage::Format_ARGB32);
449
image.fill(qRgba(255, 255, 255, 0));
450
if (m_LabelTextures[pickId].fontSize > 0.5) {
451
// 5% margin looks nice and prevents some artifacts
452
font.setPointSizeF(std::min(m_LabelTextures[pickId].fontSize, maxFontSize)*0.9);
455
paint.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
456
paint.setPen(Qt::white);
457
QString text = QString::fromUtf8(m_LabelTextures[pickId].label.c_str());
459
paint.drawText(QRect(0, 0, texSize, texSize), Qt::AlignCenter, text);
460
int offset = imageVerticalBalance(image, font.pointSize());
461
image.fill(qRgba(255, 255, 255, 0));
462
paint.drawText(QRect(0, offset, texSize, texSize), Qt::AlignCenter, text);
466
if (m_LabelTextures[pickId].texture) {
467
delete m_LabelTextures[pickId].texture;
469
m_LabelTextures[pickId].texture = new QOpenGLTexture(image.mirrored());
470
m_LabelTextures[pickId].texture->setMaximumAnisotropy(4.0);
471
m_LabelTextures[pickId].texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
472
m_LabelTextures[pickId].texture->setMagnificationFilter(QOpenGLTexture::Linear);
473
m_LabelTextures[pickId].texture->generateMipMaps();
477
void NaviCubeImplementation::addButtonFace(PickId pickId, const SbVec3f& direction)
479
if (m_Faces[pickId].vertexArray.size())
480
m_Faces[pickId].vertexArray.clear();
481
float scale = 0.005F;
484
vector<float> pointData;
489
case PickId::ArrowRight:
490
case PickId::ArrowLeft: {
492
66.6F, -66.6F,//outer curve
497
25.3F, -78.1F,//inner curve
502
53.8F, -53.8F,//arrowhead
508
case PickId::ArrowWest:
509
case PickId::ArrowNorth:
510
case PickId::ArrowSouth:
511
case PickId::ArrowEast: {
519
case PickId::ViewMenu: {
527
0., 0.,//left rhombus
531
0., 0.,//right rhombus
538
case PickId::DotBackside: {
540
for (int i = 0; i < steps; i++) {
541
float angle = 2.0f * M_PI * ((float)i+0.5) / (float)steps;
542
pointData.emplace_back(10. * cos(angle) + 87.);
543
pointData.emplace_back(10. * sin(angle) - 87.);
549
int count = static_cast<int>(pointData.size())/2;
550
m_Faces[pickId].vertexArray.reserve(count);
551
for (int i = 0; i < count; i++) {
552
float x = pointData[i*2] * scale + offx;
553
float y = pointData[i*2+1] * scale + offy;
554
if (pickId == PickId::ArrowNorth || pickId == PickId::ArrowWest || pickId == PickId::ArrowLeft)
556
if (pickId == PickId::ArrowSouth || pickId == PickId::ArrowNorth)
557
m_Faces[pickId].vertexArray.emplace_back(Vector3f(y, x, 0.0));
559
m_Faces[pickId].vertexArray.emplace_back(Vector3f(x, y, 0.0));
561
m_Faces[pickId].type = ShapeId::Button;
562
m_Faces[pickId].rotation = SbRotation(direction, 1).inverse();
565
void NaviCubeImplementation::addCubeFace(const Vector3f& x, const Vector3f& z, ShapeId shapeType, PickId pickId, float rotZ) {
566
m_Faces[pickId].vertexArray.clear();
567
m_Faces[pickId].type = shapeType;
569
Vector3f y = x.cross(-z);
571
// Determine the standard orientations based on vector x and vector z
572
// Rotate by an additional rotZ if vector x and vector z are not already the standard orientation
574
// Create normalized vectors for x, y and z
575
SbVec3f xN(x.x(), x.y(), x.z());
576
SbVec3f yN(y.x(), y.y(), y.z());
577
SbVec3f zN(z.x(), z.y(), z.z());
582
// Create a rotation matrix
583
SbMatrix R(xN[0], yN[0], zN[0], 0,
584
xN[1], yN[1], zN[1], 0,
585
xN[2], yN[2], zN[2], 0,
588
// Store the standard orientation
589
m_Faces[pickId].rotation = (SbRotation(R) * SbRotation(SbVec3f(0, 0, 1), rotZ)).inverse();
591
if (shapeType == ShapeId::Corner) {
592
auto xC = x * m_Chamfer;
593
auto yC = y * m_Chamfer;
594
auto zC = (1 - 2 * m_Chamfer) * z;
595
m_Faces[pickId].vertexArray.reserve(6);
596
m_Faces[pickId].vertexArray.emplace_back(zC - 2 * xC);
597
m_Faces[pickId].vertexArray.emplace_back(zC - xC - yC);
598
m_Faces[pickId].vertexArray.emplace_back(zC + xC - yC);
599
m_Faces[pickId].vertexArray.emplace_back(zC + 2 * xC);
600
m_Faces[pickId].vertexArray.emplace_back(zC + xC + yC);
601
m_Faces[pickId].vertexArray.emplace_back(zC - xC + yC);
603
else if (shapeType == ShapeId::Edge) {
604
auto x4 = x * (1 - m_Chamfer * 4);
605
auto yE = y * m_Chamfer;
606
auto zE = z * (1 - m_Chamfer);
607
m_Faces[pickId].vertexArray.reserve(4);
608
m_Faces[pickId].vertexArray.emplace_back(zE - x4 - yE);
609
m_Faces[pickId].vertexArray.emplace_back(zE + x4 - yE);
610
m_Faces[pickId].vertexArray.emplace_back(zE + x4 + yE);
611
m_Faces[pickId].vertexArray.emplace_back(zE - x4 + yE);
613
else if (shapeType == ShapeId::Main) {
614
auto x2 = x * (1 - m_Chamfer * 2);
615
auto y2 = y * (1 - m_Chamfer * 2);
616
auto x4 = x * (1 - m_Chamfer * 4);
617
auto y4 = y * (1 - m_Chamfer * 4);
618
m_Faces[pickId].vertexArray.reserve(8);
619
m_Faces[pickId].vertexArray.emplace_back(z - x2 - y4);
620
m_Faces[pickId].vertexArray.emplace_back(z - x4 - y2);
621
m_Faces[pickId].vertexArray.emplace_back(z + x4 - y2);
622
m_Faces[pickId].vertexArray.emplace_back(z + x2 - y4);
624
m_Faces[pickId].vertexArray.emplace_back(z + x2 + y4);
625
m_Faces[pickId].vertexArray.emplace_back(z + x4 + y2);
626
m_Faces[pickId].vertexArray.emplace_back(z - x4 + y2);
627
m_Faces[pickId].vertexArray.emplace_back(z - x2 + y4);
629
m_LabelTextures[pickId].vertexArray.clear();
630
m_LabelTextures[pickId].vertexArray.emplace_back(z - x2 - y2);
631
m_LabelTextures[pickId].vertexArray.emplace_back(z + x2 - y2);
632
m_LabelTextures[pickId].vertexArray.emplace_back(z + x2 + y2);
633
m_LabelTextures[pickId].vertexArray.emplace_back(z - x2 + y2);
638
void NaviCubeImplementation::setSize(int size)
640
m_CubeWidgetSize = size;
641
m_ViewSize = SbVec2s(0,0);
645
void NaviCubeImplementation::prepare()
647
static const float pi = boost::math::constants::pi<float>();
648
static const float pi1_2 = boost::math::constants::half_pi<float>();
650
createCubeFaceTextures();
656
// create the main faces
657
addCubeFace( x, z, ShapeId::Main, PickId::Top);
658
addCubeFace( x,-y, ShapeId::Main, PickId::Front);
659
addCubeFace(-y,-x, ShapeId::Main, PickId::Left);
660
addCubeFace(-x, y, ShapeId::Main, PickId::Rear);
661
addCubeFace( y, x, ShapeId::Main, PickId::Right);
662
addCubeFace( x,-z, ShapeId::Main, PickId::Bottom);
664
// create corner faces
665
addCubeFace(-x-y, x-y+z, ShapeId::Corner, PickId::FrontTopRight, pi);
666
addCubeFace(-x+y,-x-y+z, ShapeId::Corner, PickId::FrontTopLeft, pi);
667
addCubeFace(x+y, x-y-z, ShapeId::Corner, PickId::FrontBottomRight);
668
addCubeFace(x-y,-x-y-z, ShapeId::Corner, PickId::FrontBottomLeft);
669
addCubeFace(x-y, x+y+z, ShapeId::Corner, PickId::RearTopRight, pi);
670
addCubeFace(x+y,-x+y+z, ShapeId::Corner, PickId::RearTopLeft, pi);
671
addCubeFace(-x+y, x+y-z, ShapeId::Corner, PickId::RearBottomRight);
672
addCubeFace(-x-y,-x+y-z, ShapeId::Corner, PickId::RearBottomLeft);
675
addCubeFace(x, z-y, ShapeId::Edge, PickId::FrontTop);
676
addCubeFace(x,-z-y, ShapeId::Edge, PickId::FrontBottom);
677
addCubeFace(x, y-z, ShapeId::Edge, PickId::RearBottom, pi);
678
addCubeFace(x, y+z, ShapeId::Edge, PickId::RearTop, pi);
679
addCubeFace(z, x+y, ShapeId::Edge, PickId::RearRight, pi1_2);
680
addCubeFace(z, x-y, ShapeId::Edge, PickId::FrontRight, pi1_2);
681
addCubeFace(z,-x-y, ShapeId::Edge, PickId::FrontLeft, pi1_2);
682
addCubeFace(z, y-x, ShapeId::Edge, PickId::RearLeft, pi1_2);
683
addCubeFace(y, z-x, ShapeId::Edge, PickId::TopLeft, pi);
684
addCubeFace(y, x+z, ShapeId::Edge, PickId::TopRight);
685
addCubeFace(y, x-z, ShapeId::Edge, PickId::BottomRight);
686
addCubeFace(y,-z-x, ShapeId::Edge, PickId::BottomLeft, pi);
688
// create the flat buttons
689
addButtonFace(PickId::ArrowNorth, SbVec3f(-1, 0, 0));
690
addButtonFace(PickId::ArrowSouth, SbVec3f(1, 0, 0));
691
addButtonFace(PickId::ArrowEast, SbVec3f(0, 1, 0));
692
addButtonFace(PickId::ArrowWest, SbVec3f(0, -1, 0));
693
addButtonFace(PickId::ArrowLeft, SbVec3f(0, 0, 1));
694
addButtonFace(PickId::ArrowRight, SbVec3f(0, 0, -1));
695
addButtonFace(PickId::DotBackside, SbVec3f(0, 1, 0));
696
addButtonFace(PickId::ViewMenu);
698
if (m_PickingFramebuffer)
699
delete m_PickingFramebuffer;
700
m_PickingFramebuffer =
701
new QtGLFramebufferObject(2 * m_CubeWidgetSize, 2 * m_CubeWidgetSize,
702
QtGLFramebufferObject::CombinedDepthStencil);
703
m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw();
706
void NaviCubeImplementation::drawNaviCube() {
708
int posX = (int)(m_RelPos[0] * m_PosAreaSize[0]) + m_PosAreaBase[0] - m_CubeWidgetSize / 2;
709
int posY = (int)(m_RelPos[1] * m_PosAreaSize[1]) + m_PosAreaBase[1] - m_CubeWidgetSize / 2;
710
glViewport(posX, posY, m_CubeWidgetSize, m_CubeWidgetSize);
711
drawNaviCube(false, m_Hovering ? 1.f : m_InactiveOpacity);
714
void NaviCubeImplementation::createContextMenu(const std::vector<std::string>& cmd) {
715
CommandManager& rcCmdMgr = Application::Instance->commandManager();
718
for (const auto & i : cmd) {
719
Command* cmd = rcCmdMgr.getCommandByName(i.c_str());
725
void NaviCubeImplementation::handleResize() {
726
SbVec2s viewSize = m_View3DInventorViewer->getSoRenderManager()->getSize();
727
if (viewSize != m_ViewSize) {
728
m_PosAreaBase[0] = std::min((int)(m_PosOffset[0] + m_CubeWidgetSize * 0.55), viewSize[0] / 2);
729
m_PosAreaBase[1] = std::min((int)(m_PosOffset[1] + m_CubeWidgetSize * 0.55), viewSize[1] / 2);
730
m_PosAreaSize[0] = viewSize[0] - 2 * m_PosAreaBase[0];
731
m_PosAreaSize[1] = viewSize[1] - 2 * m_PosAreaBase[1];
732
m_ViewSize = viewSize;
736
void NaviCubeImplementation::drawNaviCube(bool pickMode, float opacity)
739
if (!m_View3DInventorViewer->viewport())
743
m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw();
747
SoCamera* cam = m_View3DInventorViewer->getSoRenderManager()->getCamera();
752
glPushAttrib(GL_ALL_ATTRIB_BITS);
755
glEnable(GL_DEPTH_TEST);
756
glDepthMask(GL_TRUE);
757
glDepthRange(0.f, 1.f);
759
glClear(GL_DEPTH_BUFFER_BIT);
760
glDepthFunc(GL_LEQUAL);
762
glDisable(GL_LIGHTING);
764
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
765
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
766
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
768
glEnable(GL_CULL_FACE);
772
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
776
glShadeModel(GL_FLAT);
777
glDisable(GL_DITHER);
778
glDisable(GL_POLYGON_SMOOTH);
779
glClearColor(0, 0, 0, 1);
780
glClear(GL_COLOR_BUFFER_BIT);
783
glEnable(GL_POLYGON_OFFSET_FILL);
784
glPolygonOffset(1.0f, 1.0f);
786
glShadeModel(GL_SMOOTH);
789
// mimic 3d view projection
790
glMatrixMode(GL_PROJECTION);
793
const float NEARVAL = 0.1f;
794
const float FARVAL = 10.1f;
795
if (cam->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) {
796
glOrtho(-2.1, 2.1, -2.1, 2.1, NEARVAL, FARVAL);
799
const float dim = NEARVAL * float(tan(M_PI / 8.0)) * 1.1;
800
glFrustum(-dim, dim, -dim, dim, NEARVAL, FARVAL);
802
glMatrixMode(GL_MODELVIEW);
805
mx = cam->orientation.getValue();
808
glLoadMatrixf((float*)mx);
810
glEnableClientState(GL_VERTEX_ARRAY);
811
QColor& cb = m_EmphaseColor;
813
// Draw coordinate system
814
if (!pickMode && m_ShowCS) {
815
glLineWidth(m_BorderWidth*2.f);
816
glPointSize(m_BorderWidth*2.f);
821
float pointData[] = {
830
glVertexPointer(3, GL_FLOAT, 0, pointData);
831
glColor4f(1, 0, 0, opacity);
832
glDrawArrays(GL_LINES, 0, 2);
833
glDrawArrays(GL_POINTS, 0, 2);
834
glColor4f(0, 1, 0, opacity);
835
glDrawArrays(GL_LINES, 2, 2);
836
glDrawArrays(GL_POINTS, 2, 2);
837
glColor4f(0, 0, 1, opacity);
838
glDrawArrays(GL_LINES, 4, 2);
839
glDrawArrays(GL_POINTS, 4, 2);
843
for (const auto& pair : m_Faces) {
844
auto f = pair.second;
845
if (f.type == ShapeId::Button)
847
auto pickId = pair.first;
849
glColor3ub(static_cast<GLubyte>(pickId), 0, 0);
852
QColor& c = m_HiliteId == pickId ? m_HiliteColor : m_BaseColor;
853
glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity);
855
glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data());
856
glDrawArrays(GL_TRIANGLE_FAN, 0, f.vertexArray.size());
860
glLineWidth(m_BorderWidth);
861
for (const auto& pair : m_Faces) {
862
auto f = pair.second;
863
if (f.type == ShapeId::Button)
865
glColor4f(cb.redF(), cb.greenF(), cb.blueF(), cb.alphaF() * opacity);
866
glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data());
867
glDrawArrays(GL_LINES, 0, f.vertexArray.size());
871
glDisable(GL_POLYGON_OFFSET_FILL); // make sure labels are on top
872
glEnable(GL_TEXTURE_2D);
873
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
874
float texCoords[] = {0.f,0.f,1.f,0.f,1.f,1.f,0.f,1.f};
875
glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
876
QColor& c = m_EmphaseColor;
877
glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity);
878
for (const auto& pair : m_LabelTextures) {
879
auto f = pair.second;
880
PickId pickId = pair.first;
881
glVertexPointer(3, GL_FLOAT, 0, m_LabelTextures[pickId].vertexArray.data());
882
glBindTexture(GL_TEXTURE_2D, f.texture->textureId());
883
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
885
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
886
glDisable(GL_TEXTURE_2D);
887
glEnable(GL_POLYGON_OFFSET_FILL);
890
// Draw the flat buttons
891
glDisable(GL_CULL_FACE);
892
glMatrixMode(GL_PROJECTION);
894
glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0);
895
glMatrixMode(GL_MODELVIEW);
898
for (const auto& pair : m_Faces) {
899
auto f = pair.second;
900
if (f.type != ShapeId::Button)
902
PickId pickId = pair.first;
904
glColor3ub(static_cast<GLubyte>(pickId), 0, 0);
907
QColor& c = m_HiliteId == pickId ? m_HiliteColor : m_BaseColor;
908
glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity);
910
glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data());
911
glDrawArrays(GL_TRIANGLE_FAN, 0, f.vertexArray.size());
913
glColor4f(cb.redF(), cb.greenF(), cb.blueF(), cb.alphaF() * opacity);
914
glDrawArrays(GL_LINE_LOOP, 0, f.vertexArray.size());
918
// Restore original state.
920
glMatrixMode(GL_PROJECTION);
925
NaviCubeImplementation::PickId NaviCubeImplementation::pickFace(short x, short y) {
926
GLubyte pixels[4] = {0};
927
if (m_PickingFramebuffer && std::abs(x) <= m_CubeWidgetSize / 2 &&
928
std::abs(y) <= m_CubeWidgetSize / 2) {
929
static_cast<QtGLWidget*>(m_View3DInventorViewer->viewport())->makeCurrent();
930
m_PickingFramebuffer->bind();
932
glViewport(0, 0, m_CubeWidgetSize * 2, m_CubeWidgetSize * 2);
934
drawNaviCube(true, 1.f);
937
glReadPixels(2 * x + m_CubeWidgetSize, 2 * y + m_CubeWidgetSize, 1, 1,
938
GL_RGBA, GL_UNSIGNED_BYTE, &pixels);
939
m_PickingFramebuffer->release();
940
static_cast<QtGLWidget*>(m_View3DInventorViewer->viewport())->doneCurrent();
942
return pixels[3] == 255 ? static_cast<PickId>(pixels[0]) : PickId::None;
945
bool NaviCubeImplementation::mousePressed(short x, short y) {
947
m_MightDrag = inDragZone(x, y);
948
PickId pick = pickFace(x, y);
950
return pick != PickId::None;
953
void NaviCubeImplementation::handleMenu() {
954
m_Menu->exec(QCursor::pos());
957
SbRotation NaviCubeImplementation::getNearestOrientation(PickId pickId) {
958
SbRotation cameraOrientation = m_View3DInventorViewer->getCameraOrientation();
959
SbRotation standardOrientation = m_Faces[pickId].rotation;
962
cameraOrientation.multVec(SbVec3f(0, 0, 1), cameraZ);
965
standardOrientation.multVec(SbVec3f(0, 0, 1), standardZ);
967
// Cleanup near zero values
968
for (int i = 0; i < 3; i++) {
969
if (abs(standardZ[i]) < 1e-6) standardZ[i] = 0.0F;
971
standardZ.normalize();
973
// Rotate the camera to the selected face by the smallest angle to align the z-axis
974
SbRotation intermediateOrientation = cameraOrientation * SbRotation(cameraZ, standardZ);
976
// Find an axis and angle to go from the intermediateOrientation to the standardOrientation
979
SbRotation rotation = intermediateOrientation.inverse() * standardOrientation;
980
rotation.getValue(axis, angle);
982
// Make sure the found axis aligns with the standardZ axis
983
if (standardZ.dot(axis) < 0) {
988
static const float pi = boost::math::constants::pi<float>();
989
static const float pi2 = boost::math::constants::two_pi<float>();
990
static const float pi1_2 = boost::math::constants::half_pi<float>();
991
static const float pi1_3 = boost::math::constants::third_pi<float>();
992
static const float pi2_3 = boost::math::constants::two_thirds_pi<float>();
994
// Make angle positive
999
// f is a small value used to control orientation priority when the camera is almost exactly between two
1000
// orientations (e.g. +45 and -45 degrees). The standard orientation is preferred compared to
1001
// +90 and -90 degree orientations and the +90 and -90 degree orientations are preferred compared to an
1002
// upside down standard orientation
1005
// Find the angle to rotate to the nearest orientation
1006
if (m_Faces[pickId].type == ShapeId::Corner) {
1007
// 6 possible orientations for the corners
1008
if (angle <= (M_PI / 6 + f)) {
1011
else if (angle <= (M_PI_2 + f)) {
1014
else if (angle < (5 * M_PI / 6 - f)) {
1017
else if (angle <= (M_PI + M_PI / 6 + f)) {
1020
else if (angle < (M_PI + M_PI_2 - f)) {
1023
else if (angle < (M_PI + 5 * M_PI / 6 - f)) {
1031
// 4 possible orientations for the main and edge faces
1032
if (angle <= (M_PI_4 + f)) {
1035
else if (angle <= (3 * M_PI_4 + f)) {
1038
else if (angle < (M_PI + M_PI_4 - f)) {
1041
else if (angle < (M_PI + 3 * M_PI_4 - f)) {
1049
// Set the rotation to go from the standard orientation to the nearest orientation
1050
rotation.setValue(standardZ, angle);
1052
return standardOrientation * rotation.inverse();
1055
bool NaviCubeImplementation::mouseReleased(short x, short y)
1057
static const float pi = boost::math::constants::pi<float>();
1059
setHilite(PickId::None);
1060
m_MouseDown = false;
1065
PickId pickId = pickFace(x, y);
1066
long step = Base::clamp(long(m_NaviStepByTurn), 4L, 36L);
1067
float rotStepAngle = (2 * M_PI) / step;
1069
if (m_Faces[pickId].type == ShapeId::Main || m_Faces[pickId].type == ShapeId::Edge || m_Faces[pickId].type == ShapeId::Corner) {
1070
// Handle the cube faces
1071
SbRotation orientation;
1072
if (m_RotateToNearest) {
1073
orientation = getNearestOrientation(pickId);
1076
orientation = m_Faces[pickId].rotation;
1078
m_View3DInventorViewer->setCameraOrientation(orientation);
1080
else if (m_Faces[pickId].type == ShapeId::Button) {
1083
if (pickId == PickId::ViewMenu) {
1088
// Handle the flat buttons
1089
SbRotation rotation = m_Faces[pickId].rotation;
1090
if (pickId == PickId::DotBackside) {
1091
rotation.scaleAngle(pi);
1094
rotation.scaleAngle(rotStepAngle);
1096
m_View3DInventorViewer->setCameraOrientation(rotation * m_View3DInventorViewer->getCameraOrientation());
1105
void NaviCubeImplementation::setHilite(PickId hilite) {
1106
if (hilite != m_HiliteId) {
1107
m_HiliteId = hilite;
1108
m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw();
1112
bool NaviCubeImplementation::inDragZone(short x, short y) {
1113
int limit = m_CubeWidgetSize / 4;
1114
return std::abs(x) < limit && std::abs(y) < limit;
1117
bool NaviCubeImplementation::mouseMoved(short x, short y) {
1118
bool hovering = std::abs(x) <= m_CubeWidgetSize / 2 &&
1119
std::abs(y) <= m_CubeWidgetSize / 2;
1121
if (hovering != m_Hovering) {
1122
m_Hovering = hovering;
1123
m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw();
1127
setHilite(pickFace(x, y));
1129
if (m_MouseDown && m_Draggable) {
1130
if (m_MightDrag && !m_Dragging) {
1132
setHilite(PickId::None);
1134
if (m_Dragging && (std::abs(x) || std::abs(y))) {
1135
float newX = m_RelPos[0] + (float)(x) / m_PosAreaSize[0];
1136
float newY = m_RelPos[1] + (float)(y) / m_PosAreaSize[1];
1137
m_RelPos[0] = std::min(std::max(newX, 0.0f), 1.0f);
1138
m_RelPos[1] = std::min(std::max(newY, 0.0f), 1.0f);
1140
m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw();
1147
bool NaviCubeImplementation::processSoEvent(const SoEvent* ev) {
1149
ev->getPosition().getValue(x, y);
1150
// translate to internal cube center based coordinates
1151
short rx = x - (short)(m_PosAreaSize[0]*m_RelPos[0]) - m_PosAreaBase[0];
1152
short ry = y - (short)(m_PosAreaSize[1]*m_RelPos[1]) - m_PosAreaBase[1];
1153
if (ev->getTypeId().isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
1154
const auto mbev = static_cast<const SoMouseButtonEvent*>(ev);
1155
if (mbev->isButtonPressEvent(mbev, SoMouseButtonEvent::BUTTON1))
1156
return mousePressed(rx, ry);
1157
if (mbev->isButtonReleaseEvent(mbev, SoMouseButtonEvent::BUTTON1))
1158
return mouseReleased(rx, ry);
1160
if (ev->getTypeId().isDerivedFrom(SoLocation2Event::getClassTypeId())) {
1161
return mouseMoved(rx, ry);
1166
QString NaviCubeImplementation::str(const char* str) {
1167
return QString::fromLatin1(str);
1170
void NaviCube::setNaviCubeCommands(const std::vector<std::string>& cmd)
1172
NaviCubeImplementation::m_commands = cmd;
1175
DEF_STD_CMD_AC(NaviCubeDraggableCmd)
1177
NaviCubeDraggableCmd::NaviCubeDraggableCmd()
1178
: Command("NaviCubeDraggableCmd")
1181
sMenuText = QT_TR_NOOP("Movable navigation cube");
1182
sToolTipText = QT_TR_NOOP("Drag and place NaviCube");
1184
sStatusTip = sToolTipText;
1185
eType = Alter3DView;
1187
void NaviCubeDraggableCmd::activated(int iMsg)
1189
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
1190
view->getViewer()->getNaviCube()->setDraggable(iMsg == 1 ? true : false);
1192
bool NaviCubeDraggableCmd::isActive()
1194
Gui::MDIView* view = Gui::getMainWindow()->activeWindow();
1195
if (view && view->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) {
1196
bool check = _pcAction->isChecked();
1197
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
1198
bool mode = view->getViewer()->getNaviCube()->isDraggable();
1200
_pcAction->setChecked(mode);
1205
Gui::Action * NaviCubeDraggableCmd::createAction()
1207
Gui::Action *pcAction = Command::createAction();
1208
pcAction->setCheckable(true);
1213
QMenu* NaviCubeImplementation::createNaviCubeMenu() {
1214
auto menu = new QMenu(getMainWindow());
1215
menu->setObjectName(str("NaviCube_Menu"));
1217
CommandManager& rcCmdMgr = Application::Instance->commandManager();
1218
static bool init = true;
1221
rcCmdMgr.addCommand(new NaviCubeDraggableCmd);
1224
vector<string> commands = NaviCubeImplementation::m_commands;
1225
if (commands.empty()) {
1226
commands.emplace_back("Std_OrthographicCamera");
1227
commands.emplace_back("Std_PerspectiveCamera");
1228
commands.emplace_back("Std_ViewIsometric");
1229
commands.emplace_back("Separator");
1230
commands.emplace_back("Std_ViewFitAll");
1231
commands.emplace_back("Std_ViewFitSelection");
1232
commands.emplace_back("Std_AlignToSelection");
1233
commands.emplace_back("Separator");
1234
commands.emplace_back("NaviCubeDraggableCmd");
1237
for (const auto & command : commands) {
1238
if (command == "Separator") {
1239
menu->addSeparator();
1242
Command* cmd = rcCmdMgr.getCommandByName(command.c_str());