/*************************************************************************** * Copyright (c) 2017 Kustaa Nyholm * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # ifdef FC_OS_WIN32 # include # endif # ifdef FC_OS_MACOSX # include # else # include # endif # include # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include "NaviCube.h" #include "Application.h" #include "Command.h" #include "Action.h" #include "MainWindow.h" #include "View3DInventorViewer.h" #include "View3DInventor.h" using namespace Eigen; using namespace std; using namespace Gui; class NaviCubeImplementation { public: explicit NaviCubeImplementation(Gui::View3DInventorViewer*); ~NaviCubeImplementation(); void drawNaviCube(); void createContextMenu(const std::vector& cmd); void createCubeFaceTextures(); void moveToCorner(NaviCube::Corner c); void setLabels(const std::vector& labels); bool processSoEvent(const SoEvent* ev); void setSize(int size); private: enum class PickId { None, Front, Top, Right, Rear, Bottom, Left, FrontTop, FrontBottom, FrontRight, FrontLeft, RearTop, RearBottom, RearRight, RearLeft, TopRight, TopLeft, BottomRight, BottomLeft, FrontTopRight, FrontTopLeft, FrontBottomRight, FrontBottomLeft, RearTopRight, RearTopLeft, RearBottomRight, RearBottomLeft, ArrowNorth, ArrowSouth, ArrowEast, ArrowWest, ArrowRight, ArrowLeft, DotBackside, ViewMenu }; enum class DirId{ Custom, Up, Right, Out }; enum class ShapeId{ None, Main, Edge, Corner, Button }; struct Face { ShapeId type; vector vertexArray; // The rotation is the standard orientation for the faces of the cube // For the flat buttons the rotation contains the direction of the rotation // The standard orientation is the desired camera orientation when a face is selected and // rotate to nearest is disabled SbRotation rotation; }; struct LabelTexture { vector vertexArray; qreal fontSize; QOpenGLTexture *texture = nullptr; string label; }; bool mousePressed(short x, short y); bool mouseReleased(short x, short y); bool mouseMoved(short x, short y); PickId pickFace(short x, short y); bool inDragZone(short x, short y); void prepare(); void handleResize(); void handleMenu(); void setHilite(PickId); void addCubeFace(const Vector3f&, const Vector3f&, ShapeId, PickId, float rotZ = 0.0); void addButtonFace(PickId, const SbVec3f& direction = SbVec3f(0, 0, 0)); QString str(const char* str); QMenu* createNaviCubeMenu(); void drawNaviCube(bool picking, float opacity); SbRotation getNearestOrientation(PickId pickId); public: static int m_CubeWidgetSize; QColor m_BaseColor; QColor m_EmphaseColor; QColor m_HiliteColor; bool m_ShowCS = true; PickId m_HiliteId = PickId::None; double m_BorderWidth = 1.1; bool m_RotateToNearest = true; int m_NaviStepByTurn = 8; float m_FontZoom = 0.3F; float m_Chamfer = 0.12F; std::string m_TextFont; int m_FontWeight = 0; int m_FontStretch = 0; float m_InactiveOpacity = 0.5; SbVec2s m_PosOffset = SbVec2s(0,0); bool m_Prepared = false; static vector m_commands; bool m_Draggable = false; SbVec2s m_ViewSize = SbVec2s(0,0); private: bool m_MouseDown = false; bool m_Dragging = false; bool m_MightDrag = false; bool m_Hovering = false; SbVec2f m_RelPos = SbVec2f(1.0f,1.0f); SbVec2s m_PosAreaBase = SbVec2s(0,0); SbVec2s m_PosAreaSize = SbVec2s(0,0); QtGLFramebufferObject* m_PickingFramebuffer; Gui::View3DInventorViewer* m_View3DInventorViewer; map m_Faces; map m_LabelTextures; QMenu* m_Menu; }; int NaviCubeImplementation::m_CubeWidgetSize = 132; int NaviCube::getNaviCubeSize() { return NaviCubeImplementation::m_CubeWidgetSize; } NaviCube::NaviCube(Gui::View3DInventorViewer* viewer) { m_NaviCubeImplementation = new NaviCubeImplementation(viewer); } NaviCube::~NaviCube() { delete m_NaviCubeImplementation; } void NaviCube::drawNaviCube() { m_NaviCubeImplementation->drawNaviCube(); } void NaviCube::createContextMenu(const std::vector& cmd) { m_NaviCubeImplementation->createContextMenu(cmd); } bool NaviCube::processSoEvent(const SoEvent* ev) { return m_NaviCubeImplementation->processSoEvent(ev); } vector NaviCubeImplementation::m_commands; void NaviCube::setCorner(Corner c) { m_NaviCubeImplementation->moveToCorner(c); } void NaviCube::setOffset(int x, int y) { m_NaviCubeImplementation->m_PosOffset = SbVec2s(x, y); m_NaviCubeImplementation->m_ViewSize = SbVec2s(0,0); } bool NaviCube::isDraggable() { return m_NaviCubeImplementation->m_Draggable; } void NaviCube::setDraggable(bool draggable) { m_NaviCubeImplementation->m_Draggable = draggable; } void NaviCube::setSize(int size) { m_NaviCubeImplementation->setSize(size); } void NaviCube::setChamfer(float chamfer) { m_NaviCubeImplementation->m_Chamfer = min(max(0.05f, chamfer), 0.18f); m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setNaviRotateToNearest(bool toNearest) { m_NaviCubeImplementation->m_RotateToNearest = toNearest; } void NaviCube::setNaviStepByTurn(int steps) { m_NaviCubeImplementation->m_NaviStepByTurn = steps; } void NaviCube::setFont(std::string font) { m_NaviCubeImplementation->m_TextFont = font; m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setFontWeight(int weight) { m_NaviCubeImplementation->m_FontWeight = weight; m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setFontStretch(int stretch) { m_NaviCubeImplementation->m_FontStretch = stretch; m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setFontZoom(float zoom) { m_NaviCubeImplementation->m_FontZoom = zoom; m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setBaseColor(QColor bColor) { m_NaviCubeImplementation->m_BaseColor = bColor; } void NaviCube::setEmphaseColor(QColor eColor) { m_NaviCubeImplementation->m_EmphaseColor = eColor; m_NaviCubeImplementation->m_Prepared = false; } void NaviCube::setHiliteColor(QColor HiliteColor) { m_NaviCubeImplementation->m_HiliteColor = HiliteColor; } void NaviCube::setBorderWidth(double BorderWidth) { m_NaviCubeImplementation->m_BorderWidth = BorderWidth; } void NaviCube::setShowCS(bool showCS) { m_NaviCubeImplementation->m_ShowCS = showCS; } void NaviCube::setNaviCubeLabels(const std::vector& labels) { m_NaviCubeImplementation->setLabels(labels); } void NaviCube::setInactiveOpacity(float opacity) { m_NaviCubeImplementation->m_InactiveOpacity = opacity; } void NaviCubeImplementation::setLabels(const std::vector& labels) { m_LabelTextures[PickId::Front].label = labels[0]; m_LabelTextures[PickId::Top].label = labels[1]; m_LabelTextures[PickId::Right].label = labels[2]; m_LabelTextures[PickId::Rear].label = labels[3]; m_LabelTextures[PickId::Bottom].label = labels[4]; m_LabelTextures[PickId::Left].label = labels[5]; m_Prepared = false; } NaviCubeImplementation::NaviCubeImplementation(Gui::View3DInventorViewer* viewer) : m_BaseColor{226, 232, 239} , m_HiliteColor{170, 226, 255} { m_View3DInventorViewer = viewer; m_PickingFramebuffer = nullptr; m_Menu = createNaviCubeMenu(); } NaviCubeImplementation::~NaviCubeImplementation() { delete m_Menu; if (m_PickingFramebuffer) delete m_PickingFramebuffer; for (auto tex: m_LabelTextures) { delete tex.second.texture; } } void NaviCubeImplementation::moveToCorner(NaviCube::Corner c) { if (c == NaviCube::TopLeftCorner) m_RelPos = SbVec2f(0.0f, 1.0f); else if (c == NaviCube::TopRightCorner) m_RelPos = SbVec2f(1.0f, 1.0f); else if (c == NaviCube::BottomLeftCorner) m_RelPos = SbVec2f(0.0f, 0.0f); else if (c == NaviCube::BottomRightCorner) m_RelPos = SbVec2f(1.0f, 0.0f); } auto convertWeights = [](int weight) -> QFont::Weight { if (weight >= 87) return QFont::Black; if (weight >= 81) return QFont::ExtraBold; if (weight >= 75) return QFont::Bold; if (weight >= 63) return QFont::DemiBold; if (weight >= 57) return QFont::Medium; if (weight >= 50) return QFont::Normal; if (weight >= 25) return QFont::Light; if (weight >= 12) return QFont::ExtraLight; return QFont::Thin; }; int imageVerticalBalance(QImage p, int sizeHint) { if (sizeHint < 0) { return 0; } int h = p.height(); int startRow = (h - sizeHint) / 2; bool done = false; int x, bottom, top; for (top = startRow; top < h; top++){ for (x = 0; x < p.width(); x++){ if (qAlpha(p.pixel(x, top))) { done = true; break; } } if (done) break; } for (bottom = startRow; bottom < h; bottom++) { for (x = 0; x < p.width(); x++){ if (qAlpha(p.pixel(x, h-1-bottom))) return (bottom-top)/2; } } return 0; } void NaviCubeImplementation::createCubeFaceTextures() { int texSize = 192; // Works well for the max cube size 1024 QFont font; if (m_TextFont.empty()) font.fromString(QStringLiteral("Arial")); else font.fromString(QString::fromStdString(m_TextFont)); font.setStyleHint(QFont::SansSerif); if (m_FontWeight > 0) { font.setWeight(convertWeights(m_FontWeight)); } if (m_FontStretch > 0) { font.setStretch(m_FontStretch); } font.setPointSizeF(texSize); QFontMetrics fm(font); qreal minFontSize = texSize; qreal maxFontSize = 0.; vector mains = {PickId::Front, PickId::Top, PickId::Right, PickId::Rear, PickId::Bottom, PickId::Left}; for (PickId pickId : mains) { auto t = QString::fromUtf8(m_LabelTextures[pickId].label.c_str()); QRect br = fm.boundingRect(t); float scale = (float)texSize / max(br.width(),br.height()); m_LabelTextures[pickId].fontSize = texSize * scale; minFontSize = std::min(minFontSize, m_LabelTextures[pickId].fontSize); maxFontSize = std::max(maxFontSize, m_LabelTextures[pickId].fontSize); } if (m_FontZoom > 0.0) maxFontSize = minFontSize + (maxFontSize - minFontSize) * m_FontZoom; else { maxFontSize = minFontSize * std::pow(2.0, m_FontZoom); } for (PickId pickId : mains) { QImage image(texSize, texSize, QImage::Format_ARGB32); image.fill(qRgba(255, 255, 255, 0)); if (m_LabelTextures[pickId].fontSize > 0.5) { // 5% margin looks nice and prevents some artifacts font.setPointSizeF(std::min(m_LabelTextures[pickId].fontSize, maxFontSize)*0.9); QPainter paint; paint.begin(&image); paint.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); paint.setPen(Qt::white); QString text = QString::fromUtf8(m_LabelTextures[pickId].label.c_str()); paint.setFont(font); paint.drawText(QRect(0, 0, texSize, texSize), Qt::AlignCenter, text); int offset = imageVerticalBalance(image, font.pointSize()); image.fill(qRgba(255, 255, 255, 0)); paint.drawText(QRect(0, offset, texSize, texSize), Qt::AlignCenter, text); paint.end(); } if (m_LabelTextures[pickId].texture) { delete m_LabelTextures[pickId].texture; } m_LabelTextures[pickId].texture = new QOpenGLTexture(image.mirrored()); m_LabelTextures[pickId].texture->setMaximumAnisotropy(4.0); m_LabelTextures[pickId].texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); m_LabelTextures[pickId].texture->setMagnificationFilter(QOpenGLTexture::Linear); m_LabelTextures[pickId].texture->generateMipMaps(); } } void NaviCubeImplementation::addButtonFace(PickId pickId, const SbVec3f& direction) { if (m_Faces[pickId].vertexArray.size()) m_Faces[pickId].vertexArray.clear(); float scale = 0.005F; float offx = 0.5F; float offy = 0.5F; vector pointData; switch (pickId) { default: break; case PickId::ArrowRight: case PickId::ArrowLeft: { pointData = { 66.6F, -66.6F,//outer curve 58.3F, -74.0F, 49.2F ,-80.3F, 39.4F ,-85.5F, 29.0F, -89.5F, 25.3F, -78.1F,//inner curve 34.3F, -74.3F, 42.8F, -69.9F, 50.8F, -64.4F, 58.1F, -58.1F, 53.8F, -53.8F,//arrowhead 74.7F, -46.8F, 70.7F, -70.4F }; break; } case PickId::ArrowWest: case PickId::ArrowNorth: case PickId::ArrowSouth: case PickId::ArrowEast: { pointData = { 100., 0., 80.,-18., 80., 18. }; break; } case PickId::ViewMenu: { offx = 0.84F; offy = 0.84F; pointData = { 0., 0.,//top rhombus 15., -6., 0., -12., -15., -6., 0., 0.,//left rhombus -15., -6., -15., 12., 0., 18., 0., 0.,//right rhombus 0., 18., 15., 12., 15., -6. }; break; } case PickId::DotBackside: { int steps = 16; for (int i = 0; i < steps; i++) { float angle = 2.0f * M_PI * ((float)i+0.5) / (float)steps; pointData.emplace_back(10. * cos(angle) + 87.); pointData.emplace_back(10. * sin(angle) - 87.); } break; } } int count = static_cast(pointData.size())/2; m_Faces[pickId].vertexArray.reserve(count); for (int i = 0; i < count; i++) { float x = pointData[i*2] * scale + offx; float y = pointData[i*2+1] * scale + offy; if (pickId == PickId::ArrowNorth || pickId == PickId::ArrowWest || pickId == PickId::ArrowLeft) x = 1.0 - x; if (pickId == PickId::ArrowSouth || pickId == PickId::ArrowNorth) m_Faces[pickId].vertexArray.emplace_back(Vector3f(y, x, 0.0)); else m_Faces[pickId].vertexArray.emplace_back(Vector3f(x, y, 0.0)); } m_Faces[pickId].type = ShapeId::Button; m_Faces[pickId].rotation = SbRotation(direction, 1).inverse(); } void NaviCubeImplementation::addCubeFace(const Vector3f& x, const Vector3f& z, ShapeId shapeType, PickId pickId, float rotZ) { m_Faces[pickId].vertexArray.clear(); m_Faces[pickId].type = shapeType; Vector3f y = x.cross(-z); // Determine the standard orientations based on vector x and vector z // Rotate by an additional rotZ if vector x and vector z are not already the standard orientation // Create normalized vectors for x, y and z SbVec3f xN(x.x(), x.y(), x.z()); SbVec3f yN(y.x(), y.y(), y.z()); SbVec3f zN(z.x(), z.y(), z.z()); xN.normalize(); yN.normalize(); zN.normalize(); // Create a rotation matrix SbMatrix R(xN[0], yN[0], zN[0], 0, xN[1], yN[1], zN[1], 0, xN[2], yN[2], zN[2], 0, 0, 0, 0, 1); // Store the standard orientation m_Faces[pickId].rotation = (SbRotation(R) * SbRotation(SbVec3f(0, 0, 1), rotZ)).inverse(); if (shapeType == ShapeId::Corner) { auto xC = x * m_Chamfer; auto yC = y * m_Chamfer; auto zC = (1 - 2 * m_Chamfer) * z; m_Faces[pickId].vertexArray.reserve(6); m_Faces[pickId].vertexArray.emplace_back(zC - 2 * xC); m_Faces[pickId].vertexArray.emplace_back(zC - xC - yC); m_Faces[pickId].vertexArray.emplace_back(zC + xC - yC); m_Faces[pickId].vertexArray.emplace_back(zC + 2 * xC); m_Faces[pickId].vertexArray.emplace_back(zC + xC + yC); m_Faces[pickId].vertexArray.emplace_back(zC - xC + yC); } else if (shapeType == ShapeId::Edge) { auto x4 = x * (1 - m_Chamfer * 4); auto yE = y * m_Chamfer; auto zE = z * (1 - m_Chamfer); m_Faces[pickId].vertexArray.reserve(4); m_Faces[pickId].vertexArray.emplace_back(zE - x4 - yE); m_Faces[pickId].vertexArray.emplace_back(zE + x4 - yE); m_Faces[pickId].vertexArray.emplace_back(zE + x4 + yE); m_Faces[pickId].vertexArray.emplace_back(zE - x4 + yE); } else if (shapeType == ShapeId::Main) { auto x2 = x * (1 - m_Chamfer * 2); auto y2 = y * (1 - m_Chamfer * 2); auto x4 = x * (1 - m_Chamfer * 4); auto y4 = y * (1 - m_Chamfer * 4); m_Faces[pickId].vertexArray.reserve(8); m_Faces[pickId].vertexArray.emplace_back(z - x2 - y4); m_Faces[pickId].vertexArray.emplace_back(z - x4 - y2); m_Faces[pickId].vertexArray.emplace_back(z + x4 - y2); m_Faces[pickId].vertexArray.emplace_back(z + x2 - y4); m_Faces[pickId].vertexArray.emplace_back(z + x2 + y4); m_Faces[pickId].vertexArray.emplace_back(z + x4 + y2); m_Faces[pickId].vertexArray.emplace_back(z - x4 + y2); m_Faces[pickId].vertexArray.emplace_back(z - x2 + y4); m_LabelTextures[pickId].vertexArray.clear(); m_LabelTextures[pickId].vertexArray.emplace_back(z - x2 - y2); m_LabelTextures[pickId].vertexArray.emplace_back(z + x2 - y2); m_LabelTextures[pickId].vertexArray.emplace_back(z + x2 + y2); m_LabelTextures[pickId].vertexArray.emplace_back(z - x2 + y2); } } void NaviCubeImplementation::setSize(int size) { m_CubeWidgetSize = size; m_ViewSize = SbVec2s(0,0); m_Prepared = false; } void NaviCubeImplementation::prepare() { static const float pi = boost::math::constants::pi(); static const float pi1_2 = boost::math::constants::half_pi(); createCubeFaceTextures(); Vector3f x(1, 0, 0); Vector3f y(0, 1, 0); Vector3f z(0, 0, 1); // create the main faces addCubeFace( x, z, ShapeId::Main, PickId::Top); addCubeFace( x,-y, ShapeId::Main, PickId::Front); addCubeFace(-y,-x, ShapeId::Main, PickId::Left); addCubeFace(-x, y, ShapeId::Main, PickId::Rear); addCubeFace( y, x, ShapeId::Main, PickId::Right); addCubeFace( x,-z, ShapeId::Main, PickId::Bottom); // create corner faces addCubeFace(-x-y, x-y+z, ShapeId::Corner, PickId::FrontTopRight, pi); addCubeFace(-x+y,-x-y+z, ShapeId::Corner, PickId::FrontTopLeft, pi); addCubeFace(x+y, x-y-z, ShapeId::Corner, PickId::FrontBottomRight); addCubeFace(x-y,-x-y-z, ShapeId::Corner, PickId::FrontBottomLeft); addCubeFace(x-y, x+y+z, ShapeId::Corner, PickId::RearTopRight, pi); addCubeFace(x+y,-x+y+z, ShapeId::Corner, PickId::RearTopLeft, pi); addCubeFace(-x+y, x+y-z, ShapeId::Corner, PickId::RearBottomRight); addCubeFace(-x-y,-x+y-z, ShapeId::Corner, PickId::RearBottomLeft); // create edge faces addCubeFace(x, z-y, ShapeId::Edge, PickId::FrontTop); addCubeFace(x,-z-y, ShapeId::Edge, PickId::FrontBottom); addCubeFace(x, y-z, ShapeId::Edge, PickId::RearBottom, pi); addCubeFace(x, y+z, ShapeId::Edge, PickId::RearTop, pi); addCubeFace(z, x+y, ShapeId::Edge, PickId::RearRight, pi1_2); addCubeFace(z, x-y, ShapeId::Edge, PickId::FrontRight, pi1_2); addCubeFace(z,-x-y, ShapeId::Edge, PickId::FrontLeft, pi1_2); addCubeFace(z, y-x, ShapeId::Edge, PickId::RearLeft, pi1_2); addCubeFace(y, z-x, ShapeId::Edge, PickId::TopLeft, pi); addCubeFace(y, x+z, ShapeId::Edge, PickId::TopRight); addCubeFace(y, x-z, ShapeId::Edge, PickId::BottomRight); addCubeFace(y,-z-x, ShapeId::Edge, PickId::BottomLeft, pi); // create the flat buttons addButtonFace(PickId::ArrowNorth, SbVec3f(-1, 0, 0)); addButtonFace(PickId::ArrowSouth, SbVec3f(1, 0, 0)); addButtonFace(PickId::ArrowEast, SbVec3f(0, 1, 0)); addButtonFace(PickId::ArrowWest, SbVec3f(0, -1, 0)); addButtonFace(PickId::ArrowLeft, SbVec3f(0, 0, 1)); addButtonFace(PickId::ArrowRight, SbVec3f(0, 0, -1)); addButtonFace(PickId::DotBackside, SbVec3f(0, 1, 0)); addButtonFace(PickId::ViewMenu); if (m_PickingFramebuffer) delete m_PickingFramebuffer; m_PickingFramebuffer = new QtGLFramebufferObject(2 * m_CubeWidgetSize, 2 * m_CubeWidgetSize, QtGLFramebufferObject::CombinedDepthStencil); m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw(); } void NaviCubeImplementation::drawNaviCube() { handleResize(); int posX = (int)(m_RelPos[0] * m_PosAreaSize[0]) + m_PosAreaBase[0] - m_CubeWidgetSize / 2; int posY = (int)(m_RelPos[1] * m_PosAreaSize[1]) + m_PosAreaBase[1] - m_CubeWidgetSize / 2; glViewport(posX, posY, m_CubeWidgetSize, m_CubeWidgetSize); drawNaviCube(false, m_Hovering ? 1.f : m_InactiveOpacity); } void NaviCubeImplementation::createContextMenu(const std::vector& cmd) { CommandManager& rcCmdMgr = Application::Instance->commandManager(); m_Menu->clear(); for (const auto & i : cmd) { Command* cmd = rcCmdMgr.getCommandByName(i.c_str()); if (cmd) cmd->addTo(m_Menu); } } void NaviCubeImplementation::handleResize() { SbVec2s viewSize = m_View3DInventorViewer->getSoRenderManager()->getSize(); if (viewSize != m_ViewSize) { m_PosAreaBase[0] = std::min((int)(m_PosOffset[0] + m_CubeWidgetSize * 0.55), viewSize[0] / 2); m_PosAreaBase[1] = std::min((int)(m_PosOffset[1] + m_CubeWidgetSize * 0.55), viewSize[1] / 2); m_PosAreaSize[0] = viewSize[0] - 2 * m_PosAreaBase[0]; m_PosAreaSize[1] = viewSize[1] - 2 * m_PosAreaBase[1]; m_ViewSize = viewSize; } } void NaviCubeImplementation::drawNaviCube(bool pickMode, float opacity) { if (!m_Prepared) { if (!m_View3DInventorViewer->viewport()) return; prepare(); m_Prepared = true; m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw(); return; } SoCamera* cam = m_View3DInventorViewer->getSoRenderManager()->getCamera(); if (!cam) return; // Store GL state. glPushAttrib(GL_ALL_ATTRIB_BITS); // configure glEnable(GL_DEPTH_TEST); glDepthMask(GL_TRUE); glDepthRange(0.f, 1.f); glClearDepth(1.f); glClear(GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_LEQUAL); glDisable(GL_LIGHTING); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glFrontFace(GL_CCW); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); if (pickMode) { glDisable(GL_BLEND); glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_POLYGON_SMOOTH); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); } else { glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0f, 1.0f); glEnable(GL_BLEND); glShadeModel(GL_SMOOTH); } // mimic 3d view projection glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); const float NEARVAL = 0.1f; const float FARVAL = 10.1f; if (cam->getTypeId().isDerivedFrom(SoOrthographicCamera::getClassTypeId())) { glOrtho(-2.1, 2.1, -2.1, 2.1, NEARVAL, FARVAL); } else { const float dim = NEARVAL * float(tan(M_PI / 8.0)) * 1.1; glFrustum(-dim, dim, -dim, dim, NEARVAL, FARVAL); } glMatrixMode(GL_MODELVIEW); glPushMatrix(); SbMatrix mx; mx = cam->orientation.getValue(); mx = mx.inverse(); mx[3][2] = -5.1F; glLoadMatrixf((float*)mx); glEnableClientState(GL_VERTEX_ARRAY); QColor& cb = m_EmphaseColor; // Draw coordinate system if (!pickMode && m_ShowCS) { glLineWidth(m_BorderWidth*2.f); glPointSize(m_BorderWidth*2.f); float a = -1.1f; float b = -1.05f; float c = 0.5f; float pointData[] = { b, a, a, // X1 c, a, a, // X2 a, b, a, // Y1 a, c, a, // Y2 a, a, b, // Z1 a, a, c, // Z2 a, a, a // 0 }; glVertexPointer(3, GL_FLOAT, 0, pointData); glColor4f(1, 0, 0, opacity); glDrawArrays(GL_LINES, 0, 2); glDrawArrays(GL_POINTS, 0, 2); glColor4f(0, 1, 0, opacity); glDrawArrays(GL_LINES, 2, 2); glDrawArrays(GL_POINTS, 2, 2); glColor4f(0, 0, 1, opacity); glDrawArrays(GL_LINES, 4, 2); glDrawArrays(GL_POINTS, 4, 2); } // cube faces for (const auto& pair : m_Faces) { auto f = pair.second; if (f.type == ShapeId::Button) continue; auto pickId = pair.first; if (pickMode) { glColor3ub(static_cast(pickId), 0, 0); } else { QColor& c = m_HiliteId == pickId ? m_HiliteColor : m_BaseColor; glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity); } glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data()); glDrawArrays(GL_TRIANGLE_FAN, 0, f.vertexArray.size()); } if (!pickMode) { // cube borders glLineWidth(m_BorderWidth); for (const auto& pair : m_Faces) { auto f = pair.second; if (f.type == ShapeId::Button) continue; glColor4f(cb.redF(), cb.greenF(), cb.blueF(), cb.alphaF() * opacity); glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data()); glDrawArrays(GL_LINES, 0, f.vertexArray.size()); } // Label textures glDisable(GL_POLYGON_OFFSET_FILL); // make sure labels are on top glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); float texCoords[] = {0.f,0.f,1.f,0.f,1.f,1.f,0.f,1.f}; glTexCoordPointer(2, GL_FLOAT, 0, texCoords); QColor& c = m_EmphaseColor; glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity); for (const auto& pair : m_LabelTextures) { auto f = pair.second; PickId pickId = pair.first; glVertexPointer(3, GL_FLOAT, 0, m_LabelTextures[pickId].vertexArray.data()); glBindTexture(GL_TEXTURE_2D, f.texture->textureId()); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_TEXTURE_2D); glEnable(GL_POLYGON_OFFSET_FILL); } // Draw the flat buttons glDisable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); for (const auto& pair : m_Faces) { auto f = pair.second; if (f.type != ShapeId::Button) continue; PickId pickId = pair.first; if (pickMode) { glColor3ub(static_cast(pickId), 0, 0); } else { QColor& c = m_HiliteId == pickId ? m_HiliteColor : m_BaseColor; glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF() * opacity); } glVertexPointer(3, GL_FLOAT, 0, f.vertexArray.data()); glDrawArrays(GL_TRIANGLE_FAN, 0, f.vertexArray.size()); if (!pickMode) { glColor4f(cb.redF(), cb.greenF(), cb.blueF(), cb.alphaF() * opacity); glDrawArrays(GL_LINE_LOOP, 0, f.vertexArray.size()); } } // Restore original state. glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); } NaviCubeImplementation::PickId NaviCubeImplementation::pickFace(short x, short y) { GLubyte pixels[4] = {0}; if (m_PickingFramebuffer && std::abs(x) <= m_CubeWidgetSize / 2 && std::abs(y) <= m_CubeWidgetSize / 2) { m_PickingFramebuffer->bind(); glViewport(0, 0, m_CubeWidgetSize * 2, m_CubeWidgetSize * 2); drawNaviCube(true, 1.f); glFinish(); glReadPixels(2 * x + m_CubeWidgetSize, 2 * y + m_CubeWidgetSize, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixels); m_PickingFramebuffer->release(); } return pixels[3] == 255 ? static_cast(pixels[0]) : PickId::None; } bool NaviCubeImplementation::mousePressed(short x, short y) { m_MouseDown = true; m_MightDrag = inDragZone(x, y); PickId pick = pickFace(x, y); setHilite(pick); return pick != PickId::None; } void NaviCubeImplementation::handleMenu() { m_Menu->exec(QCursor::pos()); } SbRotation NaviCubeImplementation::getNearestOrientation(PickId pickId) { SbRotation cameraOrientation = m_View3DInventorViewer->getCameraOrientation(); SbRotation standardOrientation = m_Faces[pickId].rotation; SbVec3f cameraZ; cameraOrientation.multVec(SbVec3f(0, 0, 1), cameraZ); SbVec3f standardZ; standardOrientation.multVec(SbVec3f(0, 0, 1), standardZ); // Cleanup near zero values for (int i = 0; i < 3; i++) { if (abs(standardZ[i]) < 1e-6) standardZ[i] = 0.0F; } standardZ.normalize(); // Rotate the camera to the selected face by the smallest angle to align the z-axis SbRotation intermediateOrientation = cameraOrientation * SbRotation(cameraZ, standardZ); // Find an axis and angle to go from the intermediateOrientation to the standardOrientation SbVec3f axis; float angle; SbRotation rotation = intermediateOrientation.inverse() * standardOrientation; rotation.getValue(axis, angle); // Make sure the found axis aligns with the standardZ axis if (standardZ.dot(axis) < 0) { axis.negate(); angle *= -1; } static const float pi = boost::math::constants::pi(); static const float pi2 = boost::math::constants::two_pi(); static const float pi1_2 = boost::math::constants::half_pi(); static const float pi1_3 = boost::math::constants::third_pi(); static const float pi2_3 = boost::math::constants::two_thirds_pi(); // Make angle positive if (angle < 0) { angle += pi2; } // f is a small value used to control orientation priority when the camera is almost exactly between two // orientations (e.g. +45 and -45 degrees). The standard orientation is preferred compared to // +90 and -90 degree orientations and the +90 and -90 degree orientations are preferred compared to an // upside down standard orientation float f = 0.00001F; // Find the angle to rotate to the nearest orientation if (m_Faces[pickId].type == ShapeId::Corner) { // 6 possible orientations for the corners if (angle <= (M_PI / 6 + f)) { angle = 0; } else if (angle <= (M_PI_2 + f)) { angle = pi1_3; } else if (angle < (5 * M_PI / 6 - f)) { angle = pi2_3; } else if (angle <= (M_PI + M_PI / 6 + f)) { angle = pi; } else if (angle < (M_PI + M_PI_2 - f)) { angle = pi + pi1_3; } else if (angle < (M_PI + 5 * M_PI / 6 - f)) { angle = pi + pi2_3; } else { angle = 0; } } else { // 4 possible orientations for the main and edge faces if (angle <= (M_PI_4 + f)) { angle = 0; } else if (angle <= (3 * M_PI_4 + f)) { angle = pi1_2; } else if (angle < (M_PI + M_PI_4 - f)) { angle = pi; } else if (angle < (M_PI + 3 * M_PI_4 - f)) { angle = pi + pi1_2; } else { angle = 0; } } // Set the rotation to go from the standard orientation to the nearest orientation rotation.setValue(standardZ, angle); return standardOrientation * rotation.inverse(); } bool NaviCubeImplementation::mouseReleased(short x, short y) { static const float pi = boost::math::constants::pi(); setHilite(PickId::None); m_MouseDown = false; if (m_Dragging) { m_Dragging = false; } else { PickId pickId = pickFace(x, y); long step = Base::clamp(long(m_NaviStepByTurn), 4L, 36L); float rotStepAngle = (2 * M_PI) / step; if (m_Faces[pickId].type == ShapeId::Main || m_Faces[pickId].type == ShapeId::Edge || m_Faces[pickId].type == ShapeId::Corner) { // Handle the cube faces SbRotation orientation; if (m_RotateToNearest) { orientation = getNearestOrientation(pickId); } else { orientation = m_Faces[pickId].rotation; } m_View3DInventorViewer->setCameraOrientation(orientation); } else if (m_Faces[pickId].type == ShapeId::Button) { // Handle the menu if (pickId == PickId::ViewMenu) { handleMenu(); return true; } // Handle the flat buttons SbRotation rotation = m_Faces[pickId].rotation; if (pickId == PickId::DotBackside) { rotation.scaleAngle(pi); } else { rotation.scaleAngle(rotStepAngle); } m_View3DInventorViewer->setCameraOrientation(rotation * m_View3DInventorViewer->getCameraOrientation()); } else { return false; } } return true; } void NaviCubeImplementation::setHilite(PickId hilite) { if (hilite != m_HiliteId) { m_HiliteId = hilite; m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw(); } } bool NaviCubeImplementation::inDragZone(short x, short y) { int limit = m_CubeWidgetSize / 4; return std::abs(x) < limit && std::abs(y) < limit; } bool NaviCubeImplementation::mouseMoved(short x, short y) { bool hovering = std::abs(x) <= m_CubeWidgetSize / 2 && std::abs(y) <= m_CubeWidgetSize / 2; if (hovering != m_Hovering) { m_Hovering = hovering; m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw(); } if (!m_Dragging) setHilite(pickFace(x, y)); if (m_MouseDown && m_Draggable) { if (m_MightDrag && !m_Dragging) { m_Dragging = true; setHilite(PickId::None); } if (m_Dragging && (std::abs(x) || std::abs(y))) { float newX = m_RelPos[0] + (float)(x) / m_PosAreaSize[0]; float newY = m_RelPos[1] + (float)(y) / m_PosAreaSize[1]; m_RelPos[0] = std::min(std::max(newX, 0.0f), 1.0f); m_RelPos[1] = std::min(std::max(newY, 0.0f), 1.0f); m_View3DInventorViewer->getSoRenderManager()->scheduleRedraw(); return true; } } return false; } bool NaviCubeImplementation::processSoEvent(const SoEvent* ev) { short x, y; ev->getPosition().getValue(x, y); // translate to internal cube center based coordinates short rx = x - (short)(m_PosAreaSize[0]*m_RelPos[0]) - m_PosAreaBase[0]; short ry = y - (short)(m_PosAreaSize[1]*m_RelPos[1]) - m_PosAreaBase[1]; if (ev->getTypeId().isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) { const auto mbev = static_cast(ev); if (mbev->isButtonPressEvent(mbev, SoMouseButtonEvent::BUTTON1)) return mousePressed(rx, ry); if (mbev->isButtonReleaseEvent(mbev, SoMouseButtonEvent::BUTTON1)) return mouseReleased(rx, ry); } if (ev->getTypeId().isDerivedFrom(SoLocation2Event::getClassTypeId())) { return mouseMoved(rx, ry); } return false; } QString NaviCubeImplementation::str(const char* str) { return QString::fromLatin1(str); } void NaviCube::setNaviCubeCommands(const std::vector& cmd) { NaviCubeImplementation::m_commands = cmd; } DEF_STD_CMD_AC(NaviCubeDraggableCmd) NaviCubeDraggableCmd::NaviCubeDraggableCmd() : Command("NaviCubeDraggableCmd") { sGroup = ""; sMenuText = QT_TR_NOOP("Movable navigation cube"); sToolTipText = QT_TR_NOOP("Drag and place NaviCube"); sWhatsThis = ""; sStatusTip = sToolTipText; eType = Alter3DView; } void NaviCubeDraggableCmd::activated(int iMsg) { auto view = qobject_cast(getMainWindow()->activeWindow()); view->getViewer()->getNaviCube()->setDraggable(iMsg == 1 ? true : false); } bool NaviCubeDraggableCmd::isActive() { Gui::MDIView* view = Gui::getMainWindow()->activeWindow(); if (view && view->isDerivedFrom(Gui::View3DInventor::getClassTypeId())) { bool check = _pcAction->isChecked(); auto view = qobject_cast(getMainWindow()->activeWindow()); bool mode = view->getViewer()->getNaviCube()->isDraggable(); if (mode != check) _pcAction->setChecked(mode); return true; } return false; } Gui::Action * NaviCubeDraggableCmd::createAction() { Gui::Action *pcAction = Command::createAction(); pcAction->setCheckable(true); return pcAction; } QMenu* NaviCubeImplementation::createNaviCubeMenu() { auto menu = new QMenu(getMainWindow()); menu->setObjectName(str("NaviCube_Menu")); CommandManager& rcCmdMgr = Application::Instance->commandManager(); static bool init = true; if (init) { init = false; rcCmdMgr.addCommand(new NaviCubeDraggableCmd); } vector commands = NaviCubeImplementation::m_commands; if (commands.empty()) { commands.emplace_back("Std_OrthographicCamera"); commands.emplace_back("Std_PerspectiveCamera"); commands.emplace_back("Std_ViewIsometric"); commands.emplace_back("Separator"); commands.emplace_back("Std_ViewFitAll"); commands.emplace_back("Std_ViewFitSelection"); commands.emplace_back("Separator"); commands.emplace_back("NaviCubeDraggableCmd"); } for (const auto & command : commands) { if (command == "Separator") { menu->addSeparator(); } else { Command* cmd = rcCmdMgr.getCommandByName(command.c_str()); if (cmd) cmd->addTo(menu); } } return menu; }