/* * (c) LSIIT, UMR CNRS/UdS * Authors: O. Génevaux, F. Larue. * * See licence.txt for additional information. */ #include "GLViewer.h" #include #include #include #include "InfoBarManager.h" #include #include #include #include void GLViewer::DisplayableInfo::Initialize( DisplayableInterface *displayable, DisplayableList::iterator priorityPos, UIParamSet ¶ms ) { this->displayable = displayable; this->posInPriorityList = priorityPos; displayable->initialize( params ); displayable->transformation( this->userTransform ); this->packingTransform.setToIdentity(); this->fullTransform = this->userTransform * this->packingTransform; displayable->boundingBox( this->boundingBox ); this->selectionMgr.clear(); displayable->selectionManagers( this->selectionMgr ); currentSelectionMgr = NULL; } const char* GLViewer::s_PickingVPG = "#version 330\n" "uniform mat4 projectionMatrix;" "uniform mat4 viewMatrix;" "in vec3 vertexPosition;" "out float depth;" "out vec3 vertex;" "void main()" "{" " depth = length( (viewMatrix * vec4(vertexPosition,1.0)).xyz );" " vertex = vertexPosition;" " gl_Position = projectionMatrix * viewMatrix * vec4(vertexPosition,1.0);" "}"; const char* GLViewer::s_PickingFPG = "uniform int u_ObjectId;" "in float depth;" "in vec3 vertex;" "void main()" "{" " gl_FragData[0].x = float( u_ObjectId & 255) / 255.0;" " gl_FragData[0].y = float((u_ObjectId>> 8) & 255) / 255.0;" " gl_FragData[0].z = float((u_ObjectId>>16) & 255) / 255.0;" " gl_FragData[0].w = float((u_ObjectId>>24) & 255) / 255.0;" " gl_FragData[1] = vec4( vertex, depth );" "}"; const char* GLViewer::s_ZBufferVPG = "#version 330\n" "uniform mat4 fullTransformMatrix;" "in vec3 vertexPosition;" "void main()" "{" " gl_Position = fullTransformMatrix * vec4(vertexPosition,1.0);" "}"; const char* GLViewer::s_ZBufferFPG = "void main()" "{" " gl_FragData[0] = vec4( 0.0, 0.0, 0.0, 1.0 );" "}"; GLViewer::GLViewer( DisplayDoF dof, QWidget *parent, Qt::WindowFlags f ) : QOpenGLWidget( parent, f ), m_DisplayOptionsLayout( NULL ), m_InfoBarManager( NULL ) { initMetricGrids(); init( dof ); } GLViewer::GLViewer( DisplayDoF dof, const QSurfaceFormat &format, QWidget *parent, Qt::WindowFlags f ) : QOpenGLWidget( parent, f ), m_DisplayOptionsLayout( NULL ), m_InfoBarManager( NULL ) { setFormat( format ); initMetricGrids(); init( dof ); } GLViewer::~GLViewer() { for( std::vector::iterator tool=m_SelectionTools.begin(); tool!=m_SelectionTools.end(); ++tool ) delete *tool; delete m_InfoBarManager; } void GLViewer::initMetricGrids() { m_View.metricGrid2D.disableAxis( MetricGrid::ALL ); m_View.metricGrid2D.disableGrid( MetricGrid::ALL ); m_View.metricGrid3D.enableAxis( MetricGrid::ALL ); m_View.metricGrid3D.enableGrid( MetricGrid::ZX ); } void GLViewer::init( DisplayDoF dof ) { m_BelowCursor = NULL; m_MustUpdateFocal = true; m_MustUpdateProjectionMatrix = true; m_MustUpdateViewMatrix = true; m_SpeedFactor = 1.0; m_CtrlSpeedFactor = 4.0; m_ShiftSpeedFactor = 0.25; m_View.focusPoint = QVector3D(-1.0f,-1.0f,-1.0f); setFocusPoint( QVector3D(0.0f,0.0f,0.0f) ); m_View.DoF = dof; QMatrix4x4 rot; if( dof == DISPLAY_DOF_2D ) rot.setToIdentity(); else { rot.rotate( 30.0f, 1,0,0 ); rot.rotate( -30.0f, 0,1,0 ); } setViewRotationMatrix( rot ); m_View.fovY = 0.0f; setFovY( 65.0f ); m_ChangeNearPlaneMode = false; m_ChangeFarPlaneMode = false; m_View.nearPlaneFactor = 0.01f; m_View.farPlaneFactor = 5.0f; m_View.distToFocusPoint = 0.0f; setDistToFocusPoint( 400.0f ); m_View.pointSize = 2; m_View.isCullingEnabled = false; if( !m_InfoBarManager ) { m_InfoBarManager = new InfoBarManager( this ); m_InfoBarManager->setInfoBar( new InfoBar(), InfoBar::BOTTOM ); } // Initialize selection tools. m_SelectionEnabled = false; if( m_SelectionTools.empty() ) { m_SelectionTools.resize( SELECTION_TOOL_END ); m_SelectionTools[SELECTION_TOOL_BOX ] = new SelectionToolBox ( *this, m_SelectionContext ); m_SelectionTools[SELECTION_TOOL_DISK ] = new SelectionToolDisk ( *this, m_SelectionContext ); m_SelectionTools[SELECTION_TOOL_LASSO] = new SelectionToolLasso( *this, m_SelectionContext ); } m_SelectionCurrentToolName = SELECTION_TOOL_DISK; m_SelectionCurrentTool = m_SelectionTools[ m_SelectionCurrentToolName ]; m_View.lightRotationMatrix.setToIdentity(); m_View.isLightTrackingEnabled = true; m_View.isLightingEnabled = true; m_View.isWireframeEnabled = false; m_AnimationsRunningAtPreviousFrame = false; // Initialize the layout that will contains options for all active displayables. if( !m_DisplayOptionsLayout ) { m_DisplayOptionsLayout = new QVBoxLayout(); m_DisplayOptionsLayout->setAlignment( Qt::AlignTop ); QWidget *w = new QWidget( this ); w->setLayout( m_DisplayOptionsLayout ); QGridLayout *viewerLayout = new QGridLayout(); viewerLayout->setRowStretch( 1, 10 ); viewerLayout->setColumnStretch( 0, 10 ); viewerLayout->addWidget( w, 0,1 ); this->setLayout( viewerLayout ); } } void GLViewer::setRenderState( const RenderState& state ) { m_View = state; m_MustUpdateFocal = true; m_MustUpdateProjectionMatrix = true; m_MustUpdateViewMatrix = true; update(); } void GLViewer::populateDisplayerFactories( DisplayableFactoryInterface *plugin ) { const unsigned int NB_ROWS = 3; QVector types; plugin->acceptedDataTypes( types ); for( auto &type : types ) if( m_Factories.find(type) == m_Factories.end() && (plugin->viewerDoF() & dof()) ) { m_Factories[type] = plugin; QStringList primNames; plugin->primitiveNames( primNames ); foreach( QString name, primNames ) { if( !m_PrimitiveCountMap.contains(name) ) { PrimitiveInfo pInfo; pInfo.count = 0; pInfo.row = m_PrimitiveCountMap.size() % (NB_ROWS-1); if( !pInfo.row ) { pInfo.panel = new Panel(NB_ROWS,2,15,2); pInfo.panel->setColAlignment( 0, Panel::LEFT ); pInfo.panel->setColAlignment( 1, Panel::RIGHT ); pInfo.panel->hideFrame(); pInfo.panel->setColMinWidth( 1, 30 ); for( unsigned int i=1; iset( new Label(" "), i,0 ); m_InfoBarManager->infoBar( InfoBar::BOTTOM )->append( pInfo.panel ); } else pInfo.panel = m_InfoBarManager->infoBar( InfoBar::BOTTOM )->panels().back(); pInfo.panel->set( new Label(name+":",QVector4D(1.0f,1.0f,1.0f,1.0f)), pInfo.row, 0 ); pInfo.panel->set( new Label("0",QVector4D(1.0f,1.0f,1.0f,1.0f)) , pInfo.row, 1 ); m_PrimitiveCountMap[ name ] = pInfo; } } } } void GLViewer::populateDisplayerFactories( QList &plugins ) { foreach( DisplayableFactoryInterface *plugin, plugins ) populateDisplayerFactories( plugin ); } void GLViewer::updatePrimitiveCounts() { #if 0 // Reset all primitive counts to zero. for( QMap::iterator prim=m_PrimitiveCountMap.begin(); prim!=m_PrimitiveCountMap.end(); ++prim ) prim->count = 0; // Checks every displayable and update primitive counts accordingly. for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) { // Recovers the factory of the current displayable. FactoryMap::iterator factory = m_Factories.find( d->first->TypeString() ); if( factory == m_Factories.end() ) continue; // Updates the primitive counts. QStringList primNames; factory->second->primitiveNames( primNames ); foreach( QString name, primNames ) m_PrimitiveCountMap[ name ].count += d->second.displayable->primitiveCount( name ); } // Updates the display of primitive counts in the viewer UI. for( QMap::iterator prim=m_PrimitiveCountMap.begin(); prim!=m_PrimitiveCountMap.end(); ++prim ) { QString countStr; if( prim->count ) { countStr = ""; for( unsigned int c=prim->count; c; c/=1000 ) countStr.prepend( c<1000? QString().setNum(c) : QString().sprintf(" %03i",c%1000) ); } else countStr = "0"; prim->panel->set( new Label(countStr), prim->row,1 ); m_InfoBarManager->repackInfoBars(); } #endif } bool GLViewer::addDisplayable( GenericUIData *m ) { // If a displayable already exists for this manageable, remove it before creating a new one. removeDisplayable( m ); // Recovers the displayable factory for the corresponding datatype. FactoryMap::iterator factory = m_Factories.find( m->GetTypeString() ); if( factory == m_Factories.end() ) return false; // Asks the factory to create a displayable of the correct type. It is then // initialized and inserted in the list of all displayables. DisplayableInterface *displayable = factory->second->newDisplayer( m, this ); DisplayableInfo *dispInfo = NULL; if( displayable ) { makeCurrent(); if( !m->GetDisplayOptions() ) { UIParamSet *params = new UIParamSet( m->GetBaseName() + " display options:", NULL ); displayable->declareParameters( *params ); if( displayable->isAnimated() ) params->Add( new UIParamAnimation() ); params->updateChildrenLists( m ); m->SetDisplayOptions( params ); } connect( m->GetDisplayOptions(), SIGNAL(parameterUpdated(UIParam*)), this, SLOT(update()) ); connect( m->GetDisplayOptions(), SIGNAL(parameterUpdated(UIParam*)), displayable, SLOT(updateParameter(UIParam*)) ); if( displayable->isAnimated() ) { UIParamAnimation *animCtrl = m->GetDisplayOptions()->GetAnimationCtrl(); connect( animCtrl, SIGNAL(animationStartRequired()), displayable, SLOT(startAnimation()) ); connect( animCtrl, SIGNAL(animationPauseRequired()), displayable, SLOT(pauseAnimation()) ); connect( animCtrl, SIGNAL(animationStopRequired ()), displayable, SLOT(stopAnimation ()) ); connect( animCtrl, SIGNAL(animationStepForwardRequired ()), displayable, SLOT(animationStepForward ()) ); connect( animCtrl, SIGNAL(animationStepBackwardRequired()), displayable, SLOT(animationStepBackward()) ); connect( displayable, SIGNAL(registerAnimation (DisplayableInterface*)), this, SLOT(registerAnimation (DisplayableInterface*)) ); connect( displayable, SIGNAL(unregisterAnimation(DisplayableInterface*)), this, SLOT(unregisterAnimation(DisplayableInterface*)) ); connect( displayable, SIGNAL(displayNeedUpdate()), this, SLOT(update()) ); } DisplayableList &priorityList = m_DisplayablesByPriority[ factory->second->displayPriority() ]; dispInfo = &m_Displayables[ m ]; dispInfo->Initialize( displayable, priorityList.insert( priorityList.end(), dispInfo ), *m->GetDisplayOptions() ); if( !m->GetDisplayOptions()->IsEmpty() ) { m_DisplayOptionsLayout->addWidget( m->GetDisplayOptions() ); m->GetDisplayOptions()->show(); } if( isSelectionEnabled() ) { for( auto mngr : dispInfo->selectionMgr ) if( mngr->selectableEntity() == m_SelectionCurrentTool->entityToSelect() ) { dispInfo->currentSelectionMgr = mngr; mngr->onRecompilingSelectionShader( m_SelectionCurrentTool ); break; } } updatePrimitiveCounts(); update(); } if( dof() == DISPLAY_DOF_2D && displayable ) { m_AddingOrder.push_back( dispInfo ); repackDisplayables(); //frameAll(); } return displayable != NULL; } //void GLViewer::setTransform( GenericUIData *m, const QMatrix4x4 &xf ) //{ // DisplayableMap::iterator d = m_Displayables.find( m ); // if( d != m_Displayables.end() ) // d->second.packingTransform = xf; //} void GLViewer::updateDisplayable( GenericUIData *m ) { DisplayableMap::iterator d = m_Displayables.find( m ); if( d != m_Displayables.end() ) { // Recovers the info and the factory related to the given manageable. DisplayableFactoryInterface *factory = m_Factories[ m->GetTypeString() ]; DisplayableInfo *dinfo = &d->second; // Re-initializes the displayable. dinfo->displayable->release( *m->GetDisplayOptions() ); dinfo->displayable->initialize( *m->GetDisplayOptions() ); // Update the displayable bounding box. dinfo->displayable->boundingBox( dinfo->boundingBox ); // Update the displayable transform. QMatrix4x4 tr; dinfo->displayable->transformation( tr ); dinfo->SetUserTransform( tr ); // Re-generate the selection managers. dinfo->currentSelectionMgr = NULL; for( auto mngr : dinfo->selectionMgr ) delete mngr; dinfo->selectionMgr.clear(); dinfo->displayable->selectionManagers( dinfo->selectionMgr ); if( isSelectionEnabled() ) { for( auto mngr : dinfo->selectionMgr ) if( mngr->selectableEntity() == m_SelectionCurrentTool->entityToSelect() ) { dinfo->currentSelectionMgr = mngr; mngr->onRecompilingSelectionShader( m_SelectionCurrentTool ); break; } } updatePrimitiveCounts(); update(); } } void GLViewer::frameBox( Box3f &box, const QMatrix4x4 *xf ) { if( !box.IsNull() ) { double boxHalfDiag = box.Dimensions().length() * 0.5; double depth = ( 2.0*zFar()*zNear() - distToFocusPoint()*(zFar()+zNear()) ) / (zNear()-zFar()); double projectedRadius = boxHalfDiag * focal() / depth; if( xf ) setFocusPoint( xf->map(box.Center()) ); else setFocusPoint( box.Center() ); if( projectedRadius > 0.000001 ) setDistToFocusPoint( distToFocusPoint() * (width()second.boundingBox, &dispInfo->second.GetTransform() ); } void GLViewer::frameItems( std::list &items ) { Box3f box; for( auto m=items.begin(); m!=items.end(); ++m ) { DisplayableMap::iterator dispInfo = m_Displayables.find( *m ); if( dispInfo != m_Displayables.end() ) if( !dispInfo->second.boundingBox.IsNull() ) { Box3f::CornerSet objectBoxCorners; dispInfo->second.boundingBox.Corners( objectBoxCorners ); for( auto c=objectBoxCorners.begin(); c!=objectBoxCorners.end(); ++c ) box.Add( dispInfo->second.GetTransform().map(*c) ); } } frameBox( box ); } void GLViewer::frameAll() { if( !m_Displayables.empty() ) { Box3f box; for( DisplayableMap::iterator dispInfo=m_Displayables.begin(); dispInfo!=m_Displayables.end(); ++dispInfo ) { Box3f objectBox; dispInfo->second.displayable->boundingBox( objectBox ); if( !objectBox.IsNull() ) { Box3f::CornerSet objectBoxCorners; objectBox.Corners( objectBoxCorners ); for( auto c=objectBoxCorners.begin(); c!=objectBoxCorners.end(); ++c ) box.Add( dispInfo->second.GetTransform().map(*c) ); } } frameBox( box ); } } // All displayables are arranged along a grid layout. All grid row have the same height (displayables are scaled so as to fit it) // but each column has a different width, that corresponds to the width of its largest element. Displayables are horizontaly centered // in their respective grid cells. void GLViewer::repackDisplayables() { if( m_AddingOrder.empty() ) return; // Computes the number of columns of the grid. int nCols = (int) std::ceil( std::sqrt( (float)m_AddingOrder.size() ) ); // Recovers the bounding boxes of all items. std::vector boxes( m_AddingOrder.size() ); auto d = m_AddingOrder.begin(); for( int i=0; idisplayable->boundingBox( boxes[i] ); // Initializes the row height by looking at the bounding box height of the first displayable in the list. float rowHeight = boxes[0].Dimensions().y(); float gridSpacing = 0.05f * rowHeight; // The width of each column is determined by looking for the largest rescaled width of all the elements it contains. std::vector columnWidth( nCols ); for( int c=0; c columnWidth[c] ) columnWidth[c] = rescaledWidth; } // The scaling factor to be applied to each displayable so as to match the grid row height is computed, // and the layout arrangement is finally performed by setting up the translation part of each displayable transform. float xRowBeginning = (boxes[0].Min().x() + 0.5f*boxes[0].Dimensions().x()) * rowHeight / boxes[0].Dimensions().y() - 0.5f*columnWidth[0]; float x = xRowBeginning; float y = boxes[0].Min().y() * rowHeight / boxes[0].Dimensions().y(); d = m_AddingOrder.begin(); for( int i=0, c=0; iSetPackingTransform( packTr ); x += columnWidth[c] + gridSpacing; if( !(c = (c+1) % nCols) ) { x = xRowBeginning; y -= rowHeight + gridSpacing; } } } GLViewer::DisplayableMap::iterator GLViewer::removeDisplayable_Internal( DisplayableMap::iterator &dmapIt ) { const GenericUIData *item = dmapIt->first; DisplayableInfo &dinfo = dmapIt->second; if( dinfo.displayable->isAnimated() ) { auto foundAnim = m_ActiveAnimations.find( dinfo.displayable ); if( foundAnim != m_ActiveAnimations.end() ) { m_ActiveAnimations.erase( foundAnim ); dinfo.displayable->onAnimationStop(); } } disconnect( item->GetDisplayOptions(), SIGNAL(parameterUpdated(UIParam*)), this, SLOT(update()) ); disconnect( item->GetDisplayOptions(), SIGNAL(parameterUpdated(UIParam*)), dinfo.displayable, SLOT(updateParameter(UIParam*)) ); if( !item->GetDisplayOptions()->IsEmpty() ) { item->GetDisplayOptions()->hide(); m_DisplayOptionsLayout->removeWidget( item->GetDisplayOptions() ); } for( auto mngr : dinfo.selectionMgr ) delete mngr; dinfo.selectionMgr.clear(); dinfo.displayable->release( *item->GetDisplayOptions() ); delete dinfo.displayable; m_DisplayablesByPriority[ m_Factories[item->GetTypeString()]->displayPriority() ].erase( dinfo.posInPriorityList ); return m_Displayables.erase( dmapIt ); } void GLViewer::removeDisplayable( GenericUIData *m ) { DisplayableMap::iterator foundDisp = m_Displayables.find( m ); if( foundDisp != m_Displayables.end() ) { makeCurrent(); if( dof() == DISPLAY_DOF_2D ) { m_AddingOrder.remove( &foundDisp->second ); removeDisplayable_Internal( foundDisp ); repackDisplayables(); frameAll(); } else removeDisplayable_Internal( foundDisp ); updatePrimitiveCounts(); update(); } } void GLViewer::removeAllDisplayables() { makeCurrent(); DisplayableMap::iterator disp = m_Displayables.begin(); while( disp != m_Displayables.end() ) disp = removeDisplayable_Internal( disp ); m_AddingOrder.clear(); updatePrimitiveCounts(); update(); } void GLViewer::initializeGL() { makeCurrent(); glewInit(); setFocusPolicy( Qt::ClickFocus ); resizeGL( width(), height() ); update(); } void GLViewer::mouseMotionNavigationEvent( float dx, float dy, QMouseEvent *evt ) { if( dof() == DISPLAY_DOF_2D ) { if( evt->buttons() & Qt::MidButton ) { double proj[16]; glGetDoublev( GL_PROJECTION_MATRIX, proj ); double scaleFactor = 2.0*distToFocusPoint() / (width()*proj[0]); double scaledDX = dx * scaleFactor; double scaledDY = dy * scaleFactor; QVector3D fpoint = focusPoint(); fpoint[0] += scaledDY*viewRotationMatrix()(1,0) - scaledDX*viewRotationMatrix()(0,0); fpoint[1] += scaledDY*viewRotationMatrix()(1,1) - scaledDX*viewRotationMatrix()(0,1); setFocusPoint( fpoint ); update(); } #if 0 else if( evt->buttons() & Qt::LeftButton ) { const double orientation = (evt->x()-0.5*width())*dy - (evt->y()-0.5*height())*dx; Matrix4Dd deltaRot; if( orientation > 0.0 ) deltaRot.SetRotation( Vector3Dd(0,0,1), -0.5*M_PI/180.0*std::sqrt(dx*dx+dy*dy) ); else deltaRot.SetRotation( Vector3Dd(0,0,1), 0.5*M_PI/180.0*std::sqrt(dx*dx+dy*dy) ); setViewRotationMatrix( viewRotationMatrix() * deltaRot ); update(); } #endif } else { if( evt->buttons() & Qt::LeftButton && evt->buttons() & Qt::RightButton ) { QVector3D axis( dy, dx, 0.0f ); double axisLen = axis.length(); QMatrix4x4 deltaRot; deltaRot.rotate( 0.5f*axisLen, axis/axisLen ); if( m_View.isLightTrackingEnabled ) m_View.lightRotationMatrix = deltaRot * m_View.lightRotationMatrix; else m_View.lightRotationMatrix = viewRotationMatrix() * deltaRot * viewRotationMatrix().inverted() * m_View.lightRotationMatrix; update(); } else if( evt->buttons() & Qt::MidButton ) { double proj[16]; glGetDoublev( GL_PROJECTION_MATRIX, proj ); double scaleFactor = 2.0*distToFocusPoint() / (width()*proj[0]); double scaledDX = dx * scaleFactor; double scaledDY = dy * scaleFactor; QVector3D fpoint = focusPoint(); fpoint[0] += scaledDY*viewRotationMatrix()(1,0) - scaledDX*viewRotationMatrix()(0,0); fpoint[1] += scaledDY*viewRotationMatrix()(1,1) - scaledDX*viewRotationMatrix()(0,1); fpoint[2] += scaledDY*viewRotationMatrix()(1,2) - scaledDX*viewRotationMatrix()(0,2); setFocusPoint( fpoint ); emit screenSpaceTranslation( QVector2D(-scaledDX,scaledDY) ); update(); } else if( evt->buttons() & Qt::LeftButton ) { QMatrix4x4 deltaRot; if( evt->pos().x() < 0.15f*width() ) deltaRot.rotate( 0.5f*dy, 0,0,1 ); else if( evt->pos().x() > 0.85f*width() ) deltaRot.rotate( -0.5f*dy, 0,0,1 ); else { QVector3D axis( dy, dx, 0.0f ); double axisLen = axis.length(); deltaRot.rotate( 0.5f*axisLen, axis/axisLen ); } emit viewRotationMatrixUpdated( deltaRot ); setViewRotationMatrix( deltaRot * viewRotationMatrix() ); update(); } } } QMatrix4x4 GLViewer::viewportMatrix( bool flipY ) const { const float xOff = 0.5f*width(), yOff = 0.5f*height(); const float xAmp = xOff, yAmp = flipY? 1.0f - yOff : yOff; return QMatrix4x4( xAmp, 0.0f, 0.0f, xOff, 0.0f, yAmp, 0.0f, yOff, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f ); } QMatrix4x4 GLViewer::modelMatrix( const DisplayableInterface *d ) const { auto dispInfo = m_Displayables.find(d->getSource()); assert( dispInfo != m_Displayables.end() ); return dispInfo->second.GetTransform(); } void GLViewer::manageAnimations() { // Check if animated objects waiting to be started are pending. while( !m_AnimationStartingQueue.isEmpty() ) { auto anim = m_AnimationStartingQueue.front(); m_AnimationStartingQueue.pop_front(); if( !m_ActiveAnimations.contains(anim) ) { m_ActiveAnimations[anim] = 0.0f; anim->onAnimationStart(); } } // Check if animated objects waiting to be stopped are pending. while( !m_AnimationStoppingQueue.isEmpty() ) { auto anim = m_AnimationStoppingQueue.front(); m_AnimationStoppingQueue.pop_front(); auto foundAnim = m_ActiveAnimations.find( anim ); if( foundAnim != m_ActiveAnimations.end() ) { m_ActiveAnimations.erase( foundAnim ); anim->onAnimationStop(); } } // Check if at least one of the current animations is not paused. bool animationsRunning = false; for( auto anim=m_ActiveAnimations.begin(); anim!=m_ActiveAnimations.end(); ++anim ) if( !anim.key()->isAnimationPaused() ) { animationsRunning = true; break; } // Make all active animated objects to execute a new animation step. if( animationsRunning ) { QTime currentTime = QTime::currentTime(); float dt = 0.001f * m_AnimationFrameTime.msecsTo( currentTime ); m_AnimationFrameTime = currentTime; if( m_AnimationsRunningAtPreviousFrame ) for( auto anim=m_ActiveAnimations.begin(); anim!=m_ActiveAnimations.end(); ++anim ) if( !anim.key()->isAnimationPaused() ) { if( anim.key()->animationTimeStepValue() != 0.0f ) { anim.value() += dt; if( anim.value() >= anim.key()->animationTimeStepValue() ) { anim.key()->onAnimationStep( anim.value() ); anim.value() -= anim.key()->animationTimeStepValue(); } } else anim.key()->onAnimationStep( dt ); } update(); } m_AnimationsRunningAtPreviousFrame = animationsRunning; } void GLViewer::paintGL() { manageAnimations(); // Setting up the OpenGL viewport. glViewport( 0, 0, width(), height() ); // Clear the frame buffer. glMatrixMode( GL_PROJECTION ); glLoadIdentity(); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); glDisable( GL_LIGHTING ); glDisable( GL_DEPTH_TEST ); glDepthMask( GL_FALSE ); glBegin( GL_QUADS ); //glColor3ub( 144, 144, 144 ); glColor3ub( 132, 132, 132 ); glVertex2i( -1, -1 ); glVertex2i( 1, -1 ); glColor3ub( 208, 208, 208 ); glVertex2i( 1, 1 ); glVertex2i( -1, 1 ); glEnd(); glDepthMask( GL_TRUE ); glClear( GL_DEPTH_BUFFER_BIT ); // Update the projection and view matrices, if needed. if( m_MustUpdateFocal ) { m_Focal = 1.0 / std::tan( fovY()*M_PI/360.0 ); m_MustUpdateFocal = false; m_MustUpdateProjectionMatrix = true; } bool mustUpdateViewProjectionMatrix = false; if( m_MustUpdateProjectionMatrix ) { m_ProjectionMatrix.fill( 0.0f ); m_ProjectionMatrix(0,0) = focal() * height() / width(); m_ProjectionMatrix(1,1) = focal(); m_ProjectionMatrix(2,2) = (zNear() + zFar()) / (zNear() - zFar()); m_ProjectionMatrix(2,3) = 2.0f*zNear()*zFar() / (zNear() - zFar()); m_ProjectionMatrix(3,2) = -1.0f; m_ProjectionMatrixInverse = m_ProjectionMatrix.inverted(); m_MustUpdateProjectionMatrix = false; mustUpdateViewProjectionMatrix = true; } if( m_MustUpdateViewMatrix ) { m_ViewMatrix.setToIdentity(); m_ViewMatrix.translate( 0.0, 0.0, -distToFocusPoint() ); m_ViewMatrix *= viewRotationMatrix(); m_ViewMatrix.translate( -focusPoint() ); m_ViewMatrixInverse = m_ViewMatrix.inverted(); m_ViewRotationMatrixInverse = viewRotationMatrix().inverted(); m_MustUpdateViewMatrix = false; mustUpdateViewProjectionMatrix = true; } if( mustUpdateViewProjectionMatrix ) { m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix; m_ViewProjectionMatrixInverse = m_ViewMatrixInverse * m_ProjectionMatrixInverse; m_PixelToRayMatrix = viewRotationMatrixInverse() * projectionMatrixInverse(); m_Frustum.UpdateFrustumPlanes( m_ViewMatrix, m_ProjectionMatrix ); } // Setup the OpenGL transforms and viewport. glMatrixMode( GL_PROJECTION ); glLoadMatrixf( projectionMatrix().data() ); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( viewMatrix().data() ); glPointSize( (GLfloat) m_View.pointSize ); glColor3ub( 255, 255, 255 ); setupScene(); // Display the scene content by increasing priority. for( PriorityMap::iterator dlist=m_DisplayablesByPriority.begin(); dlist!=m_DisplayablesByPriority.end(); ++dlist ) for( DisplayableList::iterator dinfo=dlist->second.begin(); dinfo!=dlist->second.end(); ++dinfo ) if( !m_Frustum.IsOutside( (*dinfo)->boundingBox, (*dinfo)->GetTransform() ) ) { glPushMatrix(); glMultMatrixf( (*dinfo)->GetTransform().data() ); glPushAttrib( GL_ALL_ATTRIB_BITS ); (*dinfo)->displayable->onDisplay( *(*dinfo)->displayable->getSource()->GetDisplayOptions() ); displayLabel( (*dinfo)->displayable ); if( (*dinfo)->currentSelectionMgr ) (*dinfo)->currentSelectionMgr->onDisplay( this, (*dinfo)->GetTransform() ); glPopAttrib(); glPopMatrix(); } // Finalize the rendering. releaseScene(); // Display the metric grid. glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); glDepthMask( GL_FALSE ); glEnable( GL_DEPTH_TEST ); if( dof() == DISPLAY_DOF_2D ) m_View.metricGrid2D.display( this ); else m_View.metricGrid3D.display( this ); glDepthMask( GL_TRUE ); // Display the label of the object under the mouse cursor. if( dof() == DISPLAY_DOF_3D && m_BelowCursor ) { double textX, textY; //QVector3D c = m_BelowCursor->GetTransform().map( m_BelowCursor->boundingBox.Center() ); //renderCoords( c.x(), c.y(), c.z(), &textX, &textY ); QPoint c = mapFromGlobal( cursor().pos() ); textX = c.x(); textY = c.y(); QString label = m_BelowCursor->displayable->label(); QFont f( font().family(), 10, QFont::Bold ); Qt::Alignment align = static_cast( Qt::AlignHCenter | Qt::AlignTop ); glColor3ub( 220, 220, 220 ); renderText( textX-1, textY-1, label, f, align ); renderText( textX-1, textY+1, label, f, align ); renderText( textX+1, textY-1, label, f, align ); renderText( textX+1, textY+1, label, f, align ); glColor3ub( 64, 64, 64 ); renderText( textX, textY, label, f, align ); } // Display the selection tool, if selection mode is enabled. if( isSelectionEnabled() ) { m_SelectionCurrentTool->displaySelectionState(); m_SelectionCurrentTool->display(); } // Display the information bar. m_InfoBarManager->display(); } void GLViewer::setupScene() { // Setup the OpenGL states. if( dof() == DISPLAY_DOF_2D ) { glDisable( GL_DEPTH_TEST ); glEnable( GL_MULTISAMPLE ); glDisable( GL_LIGHTING ); } else { // Place the light into the scene. glMatrixMode( GL_MODELVIEW ); glPushMatrix(); if( m_View.isLightTrackingEnabled ) glLoadIdentity(); glMultMatrixf( m_View.lightRotationMatrix.data() ); GLfloat lightPos[4] = { 0.0f, 0.0f, 1.0f, 0.0f }; glLightfv( GL_LIGHT0, GL_POSITION, lightPos ); glPopMatrix(); // Setup the OpenGL states. glEnable( GL_DEPTH_TEST ); glEnable( GL_MULTISAMPLE ); if( m_View.isLightingEnabled ) { glEnable( GL_LIGHTING ); glEnable( GL_LIGHT0 ); glEnable( GL_COLOR_MATERIAL ); } else { glDisable( GL_LIGHTING ); glDisable( GL_LIGHT0 ); glDisable( GL_COLOR_MATERIAL ); } glPolygonMode( GL_FRONT_AND_BACK, m_View.isWireframeEnabled && !m_SelectionEnabled? GL_LINE : GL_FILL ); if( m_View.isCullingEnabled ) { glEnable( GL_CULL_FACE ); glCullFace( GL_BACK ); } else glDisable( GL_CULL_FACE ); } } void GLViewer::displayLabel( DisplayableInterface *d ) { if( dof() == DISPLAY_DOF_2D ) { Box3f box; d->boundingBox( box ); // Display the black border. glColor3ub( 0, 0, 0 ); glBegin( GL_LINE_LOOP ); glVertex2f( box.Min().x(), box.Min().y() ); glVertex2f( box.Min().x(), box.Max().y() ); glVertex2f( box.Max().x(), box.Max().y() ); glVertex2f( box.Max().x(), box.Min().y() ); glEnd(); // Display the name of the image on overlay in the bottom-left corner, by constraining it to the displayed image rectangle. QMatrix4x4 toViewportCoord = viewportMatrix(true) * MVPMatrix(d); QVector3D bottomLeft = toViewportCoord.map( QVector3D( box.Min().x(), box.Min().y(), 0.0f ) ); QVector3D topRight = toViewportCoord.map( QVector3D( box.Max().x(), box.Max().y(), 0.0f ) ); QFont f( font().family(), 10, QFont::Bold ); QFontMetrics fm( f ); QString imgName; int imgNameWidth; if( fm.height() < bottomLeft.y()-topRight.y()-4 ) { imgName = d->label(); imgNameWidth = fm.width( imgName ); if( imgNameWidth > topRight.x()-bottomLeft.x()-8 ) { int imgNameLength = imgName.length(); do { -- imgNameLength; imgNameWidth -= fm.width( imgName.at(imgNameLength) ); } while( imgNameWidth > topRight.x()-bottomLeft.x()-8 ); imgName = imgName.left( imgNameLength ); } } if( imgName.length() > 0 ) { int textX = bottomLeft.x() + 4; int textY = bottomLeft.y() - 2; glColor3ub( 220, 220, 220 ); renderText( textX-1, textY-1, imgName, f, Qt::AlignTop ); renderText( textX-1, textY+1, imgName, f, Qt::AlignTop ); renderText( textX+1, textY-1, imgName, f, Qt::AlignTop ); renderText( textX+1, textY+1, imgName, f, Qt::AlignTop ); glColor3ub( 64, 64, 64 ); renderText( textX, textY, imgName, f, Qt::AlignTop ); } } } void GLViewer::resizeGL( int width, int height ) { m_MustUpdateProjectionMatrix = true; glViewport( 0, 0, width, height ); m_InfoBarManager->onReshape(); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) d->second.displayable->onReshape( *d->first->GetDisplayOptions() ); if( isSelectionEnabled() ) m_SelectionCurrentTool->onResize(); updateSelectionZBuffer(); } void GLViewer::updateSelection( SelectionTool &tool ) { makeCurrent(); // Initializes the GL transformation so as to match the current view. glMatrixMode( GL_PROJECTION ); glLoadMatrixf( projectionMatrix().data() ); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( viewMatrix().data() ); // Initializes the selection context. tool.setDepthBuffer( m_SelectionDepthTexture ); tool.selectBackFacingGeometry( m_View.isCullingEnabled? GL_FALSE : GL_TRUE ); tool.preSelectionUpdate(); // Checks every item that can be selected. for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) if( d->second.currentSelectionMgr ) { glPushMatrix(); glMultMatrixf( d->second.GetTransform().data() ); d->second.currentSelectionMgr->onUpdatingSelection( &tool ); glPopMatrix(); } tool.postSelectionUpdate(); } void GLViewer::updateSelectionMode( const Qt::KeyboardModifiers &modifiers ) { if( isSelectionEnabled() ) { SelectionTool::SelectionMode newMode; if( modifiers & Qt::ShiftModifier ) newMode = SelectionTool::SELECTION_MODE_SET; else if( modifiers & Qt::ControlModifier ) newMode = SelectionTool::SELECTION_MODE_REMOVE; else newMode = SelectionTool::SELECTION_MODE_ADD; if( m_SelectionCurrentTool->selectionMode() != newMode ) { m_SelectionCurrentTool->setSelectionMode( newMode ); update(); } } } void GLViewer::updateSelectionZBuffer() { if( !isSelectionEnabled() ) return; makeCurrent(); // Creates the shader that makes it possible to recover the scene Z-buffer. if( !m_ZBufferShader.IsCreated() ) { std::string logs; if( !GPU::CreateShaderFromSources( m_ZBufferShader, s_ZBufferVPG, s_ZBufferFPG, &logs ) ) { std::cout << "Z-buffer shader compilation error:" << std::endl << logs << std::endl; return; } } // Recovers the depth buffer matching the current view for depth test during selection. if( !m_SelectionDepthTexture.IsInstantiated() ) { m_SelectionDepthTexture.Create( GL_DEPTH_COMPONENT, width(), height(), GL_DEPTH_COMPONENT, GL_INT, NULL ); m_SelectionDepthTexture.SetFiltering( GL_LINEAR ); m_SelectionDepthTexture.SetBaseAndMaxLevels( 0 ); m_SelectionDepthTexture.SetCompareMode( GL_COMPARE_R_TO_TEXTURE ); m_SelectionDepthTexture.SetCompareFunc( GL_LEQUAL ); m_SelectionDepthTexture.SetDepthTextureMode( GL_INTENSITY ); m_SelectionDepthBuffer.Create( width(), height() ); m_SelectionDepthBuffer.Attach( GL_DEPTH_ATTACHMENT, m_SelectionDepthTexture ); } m_SelectionDepthBuffer.Bind(); glClear( GL_DEPTH_BUFFER_BIT ); // Setting up the OpenGL transforms and viewport. glMatrixMode( GL_PROJECTION ); glLoadMatrixf( projectionMatrix().data() ); glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( viewMatrix().data() ); glPointSize( (GLfloat) m_View.pointSize ); setupScene(); // Display the scene content by increasing priority. for( PriorityMap::iterator dlist=m_DisplayablesByPriority.begin(); dlist!=m_DisplayablesByPriority.end(); ++dlist ) for( DisplayableList::iterator dinfo=dlist->second.begin(); dinfo!=dlist->second.end(); ++dinfo ) if( !m_Frustum.IsOutside( (*dinfo)->boundingBox, (*dinfo)->GetTransform() ) ) { m_ZBufferShader.SetUniform( "fullTransformMatrix", (viewProjectionMatrix() * (*dinfo)->GetTransform()).data() ); glPushAttrib( GL_ALL_ATTRIB_BITS ); (*dinfo)->displayable->onPicking( *(*dinfo)->displayable->getSource()->GetDisplayOptions(), m_ZBufferShader, "vertexPosition" ); glPopAttrib(); } // Finalize the rendering. releaseScene(); m_SelectionDepthBuffer.Unbind(); } void GLViewer::processSelection( BaseSelectionProcessor &proc ) { QList procList; procList << &proc; processSelection( procList ); } void GLViewer::processSelection( QList &procList ) { QMap< QString, BaseSelectionProcessor* > processorByType; for( BaseSelectionProcessor* p : procList ) { QStringList acceptedTypes; p->acceptedDataType( acceptedTypes ); for( QString& type : acceptedTypes ) processorByType[type] = p; } for( auto& d : m_Displayables ) { QMap< QString, BaseSelectionProcessor* >::iterator processorFound = processorByType.find( d.first->GetTypeString() ); if( processorFound != processorByType.end() ) processSelection( d.second, *processorFound.value() ); } } void GLViewer::processSelection( GenericUIData *m, BaseSelectionProcessor &proc ) { DisplayableMap::iterator displayableFound = m_Displayables.find( m ); if( displayableFound != m_Displayables.end() ) processSelection( displayableFound->second, proc ); } void GLViewer::processSelection( DisplayableInterface *d, BaseSelectionProcessor &proc ) { processSelection( d->getSource(), proc ); } void GLViewer::processSelection( DisplayableInfo &dInfo, BaseSelectionProcessor &proc ) { DisplayableInterface *d = dInfo.displayable; GenericUIData *m = d->getSource(); SelectionManager *mgr = dInfo.currentSelectionMgr; if( !mgr || mgr->selectableEntity() != proc.entityToProcess() ) return; QStringList acceptedTypes; proc.acceptedDataType( acceptedTypes ); if( acceptedTypes.contains(m->GetTypeString()) ) { proc.init( m ); d->processSelection( mgr, proc ); proc.release( m ); if( proc.isSelectionResetRequired() ) mgr->clearSelection(); if( proc.isDisplayableUpdateRequired() ) { updateDisplayable( m ); updateSelectionZBuffer(); } } } void GLViewer::renderCoords( double x, double y, double z, double *screenX, double *screenY ) { // Identify x and y locations to render text within widget QMatrix4x4 model, proj; GLint viewport[4]; glGetFloatv( GL_MODELVIEW_MATRIX, model.data() ); glGetFloatv( GL_PROJECTION_MATRIX, proj.data() ); glGetIntegerv( GL_VIEWPORT, viewport ); *screenX = *screenY = 0.0; QVector4D out = proj.map( model.map( QVector4D(x,y,z,1.0f) ) ); if( out.w() != 0.0f ) { float wInv = 1.0f / out.w(); out.setX( out.x() * wInv ); out.setY( out.y() * wInv ); *screenX = viewport[0] + (1 + out.x()) * 0.5 * viewport[2]; *screenY = viewport[1] + (1 + out.y()) * 0.5 * viewport[3]; } *screenY = this->height() - *screenY; // y is inverted } void GLViewer::renderText( double x, double y, const QString &text, const QFont &font, Qt::Alignment align ) { QFontMetrics fm( font ); // Extract lines from the input strings. QStringList textLines; int beg = 0, end; while( (end = text.indexOf('\n',beg)) >= 0 ) { textLines.push_back( text.mid(beg,end-beg) ); beg = end + 1; } textLines.push_back( (beg==0)? text : text.mid(beg) ); // Save OpenGL states. GLint buffer; GLboolean depthTest; GLboolean blend; GLint blendSrcRGB, blendSrcAlpha; GLint blendDstRGB, blendDstAlpha; glGetIntegerv( GL_FRAMEBUFFER_BINDING, &buffer ); glGetBooleanv( GL_DEPTH_TEST, &depthTest ); glGetBooleanv( GL_BLEND, &blend ); glGetIntegerv( GL_BLEND_SRC_RGB , &blendSrcRGB ); glGetIntegerv( GL_BLEND_SRC_ALPHA, &blendSrcAlpha ); glGetIntegerv( GL_BLEND_DST_RGB , &blendDstRGB ); glGetIntegerv( GL_BLEND_DST_ALPHA, &blendDstAlpha ); // Retrieve last OpenGL color to use as a font color. GLfloat glColor[4]; glGetFloatv( GL_CURRENT_COLOR, glColor ); QColor fontColor = QColor( 255.0f*glColor[0], 255.0f*glColor[1], 255.0f*glColor[2], 255.0f*glColor[3] ); // Paint text. if( align & Qt::AlignTop ) y -= (textLines.size()-1) * fm.height() + fm.descent(); else if( align & Qt::AlignCenter ) y -= ((textLines.size()-2) * fm.height())/2/* + fm.descent()*/; QPainter painter; painter.begin( this ); painter.setPen( fontColor ); painter.setFont( font ); for( auto &l : textLines ) { if( align & Qt::AlignRight ) painter.drawText( x - fm.width(l), y, l ); else if( align & Qt::AlignHCenter ) painter.drawText( x - fm.width(l)/2, y, l ); else painter.drawText( x, y, l ); y += fm.height(); } painter.end(); // Restore OpenGL states. glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, buffer ); depthTest? glEnable( GL_DEPTH_TEST ) : glDisable( GL_DEPTH_TEST ); blend? glEnable( GL_BLEND ) : glDisable( GL_BLEND ); glBlendFuncSeparateEXT( blendSrcRGB, blendDstRGB, blendSrcAlpha, blendDstAlpha ); glColor4fv( glColor ); } void GLViewer::renderText( double x, double y, double z, const QString &text, const QFont &font, Qt::Alignment align ) { double screenX, screenY; renderCoords( x, y, z, &screenX, &screenY ); renderText( screenX, screenY, text, font, align ); } bool GLViewer::imageOfCurrentDisplay( const GenericUIData *m, QImage &renderResult ) { // Check if the provided item is currently displayed. DisplayableInterface *d = getDisplayable( m ); if( !d ) return false; // Recover its dimensions so as to determine the size of the rendering output buffer. Box3f dBox; d->boundingBox( dBox ); int w = (int) dBox.Dimensions().x(); int h = (int) dBox.Dimensions().y(); // Perform off-screen rendering. makeCurrent(); GPU::FrameBuffer fb( w, h ); fb.Attach( GL_COLOR_ATTACHMENT0, GL_RGBA ); fb.Bind(); glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity(); glOrtho( dBox.Min().x(), dBox.Max().x(), dBox.Max().y(), dBox.Min().y(), -1.0f, 1.0f ); glMultMatrixf( m_ViewMatrixInverse.data() ); glMultMatrixf( modelMatrixInverse(d).data() ); QMatrix4x4 projBackup = m_ProjectionMatrix; glGetFloatv( GL_PROJECTION_MATRIX, m_ProjectionMatrix.data() ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadMatrixf( m_ViewMatrix.data() ); glMultMatrixf( modelMatrix(d).data() ); d->onDisplay( *m->GetDisplayOptions() ); glMatrixMode( GL_MODELVIEW ); glPopMatrix(); glMatrixMode( GL_PROJECTION ); glPopMatrix(); m_ProjectionMatrix = projBackup; fb.Unbind(); // Recover the render result and store it to the provided image object. GLubyte *fbData = new GLubyte [ 4*w*h ]; fb.DumpTo( GL_COLOR_ATTACHMENT0, fbData, GL_RGBA, GL_UNSIGNED_BYTE ); renderResult = QImage( w, h, QImage::Format_RGBA8888 ); for( int y=0, n=0; ybutton() == Qt::RightButton && getPickedPoint( evt->pos().x(), height()-evt->pos().y(), p, true ) ) { QMenu ctxMenu; ctxMenu.addAction( "Save current render" ); if( ctxMenu.exec( evt->globalPos() ) ) { QStringList filters; QString allImages; foreach( QByteArray ext, QImageWriter::supportedImageFormats() ) { filters << QString(ext).toUpper() + " image files (*." + ext + ")"; allImages += " *." + ext; } filters.push_front( "All image files (" + allImages + " )" ); QFileDialog dlg( this, "Save image" ); dlg.setAcceptMode( QFileDialog::AcceptSave ); dlg.setFileMode( QFileDialog::AnyFile ); dlg.setNameFilters( filters ); if( dlg.exec() ) { QImage img; imageOfCurrentDisplay( p.srcObject, img ); img.save( dlg.selectedFiles().front() ); } } } } if( isSelectionEnabled() ) { if( evt->button() == Qt::RightButton ) toggleSelection(); else { makeCurrent(); if( m_SelectionZBufferUpdateMode & SelectionZBufferUpdateMode::ON_MOUSE_PRESSED ) updateSelectionZBuffer(); m_SelectionCurrentTool->mousePressEvent( evt ); } } m_ClickMousePos = m_PrevMousePos = evt->pos(); update(); } void GLViewer::mouseReleaseEvent( QMouseEvent *evt ) { if( isSelectionEnabled() ) { makeCurrent(); if( m_SelectionZBufferUpdateMode & SelectionZBufferUpdateMode::ON_MOUSE_RELEASED ) updateSelectionZBuffer(); m_SelectionCurrentTool->mouseReleaseEvent( evt ); updateSelectionMode( evt->modifiers() ); } else if( evt->button() == Qt::RightButton && evt->pos() == m_ClickMousePos && m_BelowCursor ) { emit dataSelected( m_BelowCursor->displayable->getSource(), evt->modifiers() ); } update(); } void GLViewer::findObjectUnderCursor( const QPoint& cursor ) { PickedPoint pp; if( getPickedPoint( cursor.x(), height()-cursor.y(), pp, true ) ) { auto disp = &m_Displayables[ pp.srcObject ]; //if( disp != m_BelowCursor ) { m_BelowCursor = disp; update(); } } else if( m_BelowCursor ) { m_BelowCursor = NULL; update(); } } void GLViewer::clearObjectUnderCursor() { if( m_BelowCursor ) { m_BelowCursor = NULL; update(); } } void GLViewer::mouseMoveEvent( QMouseEvent *evt ) { if( isSelectionEnabled() ) { makeCurrent(); if( evt->buttons() != Qt::MouseButton::NoButton && (m_SelectionZBufferUpdateMode & SelectionZBufferUpdateMode::ON_MOUSE_MOVED) ) updateSelectionZBuffer(); m_SelectionCurrentTool->mouseMoveEvent( evt ); update(); } else if( evt->buttons() != Qt::NoButton ) { double dx = (evt->pos().x() - m_PrevMousePos.x()); double dy = (evt->pos().y() - m_PrevMousePos.y()); m_PrevMousePos = evt->pos(); if( evt->modifiers() & Qt::ShiftModifier ) { dx *= m_ShiftSpeedFactor; dy *= m_ShiftSpeedFactor; } else if( evt->modifiers() & Qt::ControlModifier ) { dx *= m_CtrlSpeedFactor; dy *= m_CtrlSpeedFactor; } else { dx *= m_SpeedFactor; dy *= m_SpeedFactor; } mouseMotionNavigationEvent( dx, dy, evt ); } else if( !isSelectionEnabled() ) findObjectUnderCursor( evt->pos() ); } void GLViewer::wheelEvent( QWheelEvent* evt ) { if( evt->orientation() == Qt::Vertical ) { if( !isSelectionEnabled() ) { if( (evt->modifiers() & Qt::ShiftModifier ) && (evt->modifiers() & Qt::ControlModifier) ) { setFovY( fovY() + evt->delta()*M_PI/180.0f ); } else if( m_ChangeNearPlaneMode ) { double delta = 0.0005 * m_View.nearPlaneFactor * evt->delta(); if( m_View.nearPlaneFactor + delta > 0.0 ) { m_View.nearPlaneFactor += delta; m_View.zNear = m_View.nearPlaneFactor * m_View.distToFocusPoint; m_MustUpdateProjectionMatrix = true; update(); } } else if( m_ChangeFarPlaneMode ) { double delta = 0.0005 * m_View.farPlaneFactor * evt->delta(); if( m_View.farPlaneFactor + delta > 0.0 ) { m_View.farPlaneFactor += delta; m_View.zFar = m_View.farPlaneFactor * m_View.distToFocusPoint; m_MustUpdateProjectionMatrix = true; update(); } } else { double deltaD = -0.0005 * m_SpeedFactor * distToFocusPoint() * evt->delta(); if( evt->modifiers() & Qt::ShiftModifier ) deltaD *= m_ShiftSpeedFactor; else if( evt->modifiers() & Qt::ControlModifier ) deltaD *= m_CtrlSpeedFactor; if( distToFocusPoint() + deltaD > 0.0 ) setDistToFocusPoint( distToFocusPoint() + deltaD ); } } else { makeCurrent(); m_SelectionCurrentTool->wheelEvent( evt ); } update(); } } void GLViewer::mouseDoubleClickEvent( QMouseEvent *evt ) { if( isSelectionEnabled() ) { makeCurrent(); m_SelectionCurrentTool->mouseDoubleClickEvent( evt ); } else if( evt->buttons() == Qt::LeftButton ) { PickedPoint point; if( getPickedPoint( evt->x(), height()-evt->y(), point, false ) ) { if( evt->modifiers() & Qt::ControlModifier ) frameItem( point.srcObject ); else setFocusPoint( point.global ); } } } bool GLViewer::event( QEvent *evt ) { if( evt->type() == QEvent::KeyPress ) { QKeyEvent *kevt = static_cast( evt ); if( kevt->key() == Qt::Key_Tab ) { makeCurrent(); if( !isSelectionEnabled() ) { m_SelectionContext.isSelecting = false; m_SelectionContext.selectionMode = SelectionTool::SELECTION_MODE_SET; toggleSelection(); } else if( !(kevt->modifiers() & Qt::Modifier::CTRL) ) { m_SelectionCurrentTool->onDisabled(); m_SelectionCurrentToolName = (SelectionToolName)( (m_SelectionCurrentToolName + 1) % SELECTION_TOOL_END ); m_SelectionCurrentTool = m_SelectionTools[ m_SelectionCurrentToolName ]; m_SelectionCurrentTool->onEnabled(); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) if( d->second.currentSelectionMgr ) d->second.currentSelectionMgr->onRecompilingSelectionShader( m_SelectionCurrentTool ); } if( kevt->modifiers() & Qt::Modifier::CTRL ) { int availableEntities = 0; for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) for( auto mngr : d->second.selectionMgr ) availableEntities |= mngr->selectableEntity(); if( availableEntities ) { int newEntity = m_SelectionCurrentTool->entityToSelect(); do { newEntity = (newEntity&4)? 1 : (newEntity << 1); } while( !(newEntity & availableEntities) ); setSelectionEntity( (SelectableEntity) newEntity ); } } update(); return true; } else if( kevt->key() == Qt::Key_Shift && !isSelectionEnabled() ) { setMouseTracking( true ); findObjectUnderCursor( mapFromGlobal( cursor().pos() ) ); return true; } } else if( evt->type() == QEvent::KeyRelease ) { QKeyEvent *kevt = static_cast( evt ); if( kevt->key() == Qt::Key_Shift && !isSelectionEnabled() ) { setMouseTracking( false ); clearObjectUnderCursor(); return true; } } return QOpenGLWidget::event( evt ); } void GLViewer::keyPressEvent( QKeyEvent *evt ) { bool defaultBehaviour = false; if( dof() == DISPLAY_DOF_2D ) { switch( evt->key() ) { case Qt::Key_Space: centerView(); break; case Qt::Key_4: { QMatrix4x4 rot; rot.setToIdentity(); rot.rotate( -15.0f, 0,0,1 ); setViewRotationMatrix( rot * viewRotationMatrix() ); update(); break; } case Qt::Key_5: { QMatrix4x4 rot; rot.setToIdentity(); setViewRotationMatrix( rot ); update(); break; } case Qt::Key_6: { QMatrix4x4 rot; rot.setToIdentity(); rot.rotate( 15.0f, 0,0,1 ); setViewRotationMatrix( rot * viewRotationMatrix() ); update(); break; } case Qt::Key_G: { if( m_View.metricGrid2D.isGridEnabled(MetricGrid::XY) ) { m_View.metricGrid2D.disableGrid( MetricGrid::ALL ); m_View.metricGrid2D.disableAxis( MetricGrid::X | MetricGrid::Y ); } else { m_View.metricGrid2D.enableAxis ( MetricGrid::X | MetricGrid::Y ); m_View.metricGrid2D.enableGrid ( MetricGrid::XY ); } update(); break; } default: defaultBehaviour = true; } } else { switch( evt->key() ) { case Qt::Key_W: toggleWireframe(); break; case Qt::Key_L: toggleLighting(); break; case Qt::Key_G: { if( m_View.metricGrid3D.isGridEnabled(MetricGrid::ZX) ) { m_View.metricGrid3D.disableAxis( MetricGrid::ALL ); m_View.metricGrid3D.disableGrid( MetricGrid::ALL ); } else { m_View.metricGrid3D.enableAxis( MetricGrid::ALL ); m_View.metricGrid3D.enableGrid( MetricGrid::ZX ); } update(); break; } case Qt::Key_1: { QMatrix4x4 rot; if( evt->modifiers() & Qt::CTRL ) rot.rotate( 180.0f, 0,1,0 ); setViewRotationMatrix( rot ); update(); break; } case Qt::Key_3: { QMatrix4x4 rot; rot.rotate( -90.0f, 0,1,0 ); if( evt->modifiers() & Qt::CTRL ) rot.rotate( 180.0f, 0,1,0 ); setViewRotationMatrix( rot ); update(); break; } case Qt::Key_7: { QMatrix4x4 rot; rot.rotate( 90.0f, 1,0,0 ); if( evt->modifiers() & Qt::CTRL ) rot.rotate( 180.0f, 1,0,0 ); setViewRotationMatrix( rot ); update(); break; } case Qt::Key_4: { QMatrix4x4 rot; rot.rotate( 15.0f, 0,1,0 ); setViewRotationMatrix( viewRotationMatrix() * rot ); update(); break; } case Qt::Key_6: { QMatrix4x4 rot; rot.rotate( -15.0f, 0,1,0 ); setViewRotationMatrix( viewRotationMatrix() * rot ); update(); break; } case Qt::Key_2: { QMatrix4x4 rot; rot.rotate( -15.0f, 1,0,0 ); setViewRotationMatrix( rot * viewRotationMatrix() ); update(); break; } case Qt::Key_8: { QMatrix4x4 rot; rot.rotate( 15.0f, 1,0,0 ); setViewRotationMatrix( rot * viewRotationMatrix() ); update(); break; } case Qt::Key_Plus: { increasePointSize(); update(); break; } case Qt::Key_Minus: { decreasePointSize(); update(); break; } default: defaultBehaviour = true; } } if( defaultBehaviour ) { switch( evt->key() ) { case Qt::Key_C: toggleCulling(); break; case Qt::Key_Escape: { if( isSelectionEnabled() ) { toggleSelection(); update(); } break; } case Qt::Key_F1: case Qt::Key_F2: case Qt::Key_F3: case Qt::Key_F4: case Qt::Key_F5: case Qt::Key_F6: case Qt::Key_F7: case Qt::Key_F8: case Qt::Key_F9: case Qt::Key_F10: case Qt::Key_F11: case Qt::Key_F12: { qint32 viewId = evt->key() - Qt::Key_F1; std::ostringstream filename; filename << ".viewer" << viewId << ".cfg"; if( evt->modifiers() == Qt::Modifier::SHIFT ) { std::ofstream file( filename.str(), std::ios::binary ); file.write( (char*) &m_View, sizeof(RenderState) ); } else { std::ifstream file( filename.str(), std::ios::binary ); if( file.is_open() ) { RenderState state; file.read( (char*) &state, sizeof(RenderState) ); setRenderState( state ); } } break; } case Qt::Key_N: m_ChangeNearPlaneMode = true; break; case Qt::Key_F: m_ChangeFarPlaneMode = true; break; default: emit keyPressed( evt ); } if( !m_SelectionCurrentTool->isSelecting() ) { makeCurrent(); updateSelectionMode( evt->modifiers() ); } } } void GLViewer::keyReleaseEvent( QKeyEvent *evt ) { if( evt->key() == Qt::Key_N ) m_ChangeNearPlaneMode = false; if( evt->key() == Qt::Key_F ) m_ChangeFarPlaneMode = false; if( !m_SelectionCurrentTool->isSelecting() ) { makeCurrent(); updateSelectionMode( evt->modifiers() ); } } QMatrix4x4 GLViewer::reframedProjection( float viewportX, float viewportY, float viewportW, float viewportH ) { QMatrix4x4 projMat = projectionMatrix(); double frustumL = zNear() * (projMat(0,2)-1.0f) / projMat(0,0); double frustumR = zNear() * (projMat(0,2)+1.0f) / projMat(0,0); double frustumB = zNear() * (projMat(1,2)-1.0f) / projMat(1,1); double frustumT = zNear() * (projMat(1,2)+1.0f) / projMat(1,1); double newFrustumL = frustumL + (frustumR-frustumL) * (viewportX+0.5f)/width(); double newFrustumR = frustumL + (frustumR-frustumL) * (viewportX+viewportW+0.5f)/width(); double newFrustumB = frustumB + (frustumT-frustumB) * (viewportY+0.5f)/height(); double newFrustumT = frustumB + (frustumT-frustumB) * (viewportY+viewportH+0.5f)/height(); projMat.fill( 0.0f ); projMat(0,0) = 2.0f * zNear() / (newFrustumR - newFrustumL); projMat(0,2) = (newFrustumR + newFrustumL) / (newFrustumR - newFrustumL); projMat(1,1) = 2.0f * zNear() / (newFrustumT - newFrustumB); projMat(1,2) = (newFrustumT + newFrustumB) / (newFrustumT - newFrustumB); projMat(2,2) = (zNear() + zFar()) / (zNear() - zFar()); projMat(2,3) = 2.0f * zNear() * zFar() / (zNear() - zFar()); projMat(3,2) = -1.0f; return projMat; } bool GLViewer::getPickedPoint( const float x, const float y, PickedPoint &picked, bool accurate ) { makeCurrent(); glPushAttrib( GL_ALL_ATTRIB_BITS ); // Creates the shader that generates data required for point picking. if( !m_PickingShader.IsCreated() ) { std::string logs; if( !GPU::CreateShaderFromSources( m_PickingShader, s_PickingVPG, s_PickingFPG, &logs ) ) { std::cout << "Picking shader compilation error:" << std::endl << logs << std::endl; return false; } } // Creates the offscreen buffer used for point picking rendering. const int pickingWinDiam = 2*PICKING_WIN_RADIUS + 1; const int pickingWinArea = pickingWinDiam * pickingWinDiam; GPU::FrameBuffer pickingBuffer( pickingWinDiam, pickingWinDiam ); pickingBuffer.Attach( GL_COLOR_ATTACHMENT0, GL_RGBA ); pickingBuffer.Attach( GL_COLOR_ATTACHMENT1, GL_RGBA32F ); pickingBuffer.Attach( GL_DEPTH_ATTACHMENT , GL_DEPTH_COMPONENT ); pickingBuffer.Bind(); glDisable( GL_MULTISAMPLE ); glDisable( GL_POINT_SMOOTH ); glDisable( GL_BLEND ); glEnable( GL_DEPTH_TEST ); // The projection matrix is retargeted so as to focus only on a small window around the mouse cursor. // Moreover, a frustum is created from this new projection matrix and used for culling. QMatrix4x4 pickingProjMat = reframedProjection( x-PICKING_WIN_RADIUS, y-PICKING_WIN_RADIUS, 2*PICKING_WIN_RADIUS, 2*PICKING_WIN_RADIUS ); Frustum pickingFrustum; pickingFrustum.UpdateFrustumPlanes( viewMatrix(), pickingProjMat ); // Performs the rendering of every displayable that is flaged as pickable. This rendering will result to two buffers: // - GL_COLOR_ATTACHMENT0, that contains the indices of the rendered objects (RGBA interpreted as a 32-bits integer) // in the array "orderedManageables"; // - GL_COLOR_ATTACHMENT1, that contains the object space coordinates of the rendered points (in the RGB channels), // as well as their distances to the eye (in the Alpha channel). std::vector orderedManageables; glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); QVector3D rayOrig = viewpointLocation(); QVector3D rayDir = pixelRay( x, y ).normalized(); DisplayableInfo *closestBoxObject = NULL; float closestBoxDist = std::numeric_limits::max(); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) { if( pickingFrustum.IsOutside( d->second.boundingBox, d->second.GetTransform() ) ) continue; QMatrix4x4 cameraToBox = d->second.GetTransform().inverted(); QVector3D localRayOrig = cameraToBox.map( rayOrig ); QVector3D localRayDir = cameraToBox.mapVector( rayDir ); QVector3D boxHitPointCoords; float boxHitPointDist; if( !d->second.boundingBox.Intersects(localRayOrig,localRayDir,boxHitPointCoords,boxHitPointDist) ) continue; if( d->second.displayable->isPickable( *d->first->GetDisplayOptions() ) ) { m_PickingShader.SetUniform( "viewMatrix", (viewMatrix() * d->second.GetTransform()).data() ); m_PickingShader.SetUniform( "projectionMatrix", pickingProjMat.data() ); orderedManageables.push_back( d->second.displayable->getSource() ); int objId = (int) orderedManageables.size(); m_PickingShader.SetUniform( "u_ObjectId", &objId ); d->second.displayable->onPicking( *d->first->GetDisplayOptions(), m_PickingShader, "vertexPosition" ); } else if( boxHitPointDist < closestBoxDist ) { closestBoxObject = &d->second; closestBoxDist = boxHitPointDist; } } pickingBuffer.Unbind(); bool pickingFound = false; if( closestBoxObject ) { picked.srcObject = closestBoxObject->displayable->getSource(); picked.position = closestBoxObject->boundingBox.Center(); picked.global = closestBoxObject->GetTransform().map( picked.position ); pickingFound = true; } if( !orderedManageables.empty() ) { // Reads back the content of the buffers. struct PickingBufferElement { QVector3D position; float depth; }; PickingBufferElement pickedPointBuffer[ pickingWinArea ]; qint32 pickedObjIdBuffer[ pickingWinArea ]; pickingBuffer.DumpTo( GL_COLOR_ATTACHMENT0, pickedObjIdBuffer, GL_RGBA, GL_UNSIGNED_BYTE, 4 ); pickingBuffer.DumpTo( GL_COLOR_ATTACHMENT1, pickedPointBuffer, GL_RGBA, GL_FLOAT, 8 ); // Looks for the buffer pixel that corresponds to the valid point that has the smallest distance to the viewpoint. float nearestDepth; int nearestId = -1; if( accurate ) { // In order to improve the picking accuracy, the buffer is analysed by looking first at the pixel located at // the center of the window, and by increasing progressively the search radius . for( int radius=0, diam=1; radius<=PICKING_WIN_RADIUS; ++radius, diam+=2 ) { nearestDepth = std::numeric_limits::max(); for( int a=0, n=(PICKING_WIN_RADIUS-radius)*(pickingWinDiam+1); a= 0 ) break; } } else { nearestDepth = std::numeric_limits::max(); for( int i=0; i= 0 && nearestDepth < closestBoxDist ) { picked.srcObject = orderedManageables[ pickedObjIdBuffer[nearestId]-1 ]; picked.position = pickedPointBuffer[nearestId].position; picked.global = m_Displayables[picked.srcObject].GetTransform().map( picked.position ); pickingFound = true; } } glPopAttrib(); return pickingFound; } void GLViewer::centerView() { setFocusPoint( QVector3D(0.0,0.0,0.0) ); } void GLViewer::setSpeedFactor( const float speedFactor, const float ctrlSpeedFactor, const float shiftSpeedFactor ) { m_SpeedFactor = speedFactor; m_CtrlSpeedFactor = ctrlSpeedFactor; m_ShiftSpeedFactor = shiftSpeedFactor; } void GLViewer::setSpeedFactor( const float speedFactor ) { m_SpeedFactor = speedFactor; } void GLViewer::setCtrlSpeedFactor( const float ctrlSpeedFactor ) { m_CtrlSpeedFactor = ctrlSpeedFactor; } void GLViewer::setShiftSpeedFactor( const float shiftSpeedFactor ) { m_ShiftSpeedFactor = shiftSpeedFactor; } void GLViewer::increasePointSize() { ++ m_View.pointSize; update(); } void GLViewer::decreasePointSize() { if( m_View.pointSize > 1 ) { -- m_View.pointSize; update(); } } void GLViewer::setFocusPoint( const QVector3D& p ) { if( p.x() != m_View.focusPoint.x() || p.y() != m_View.focusPoint.y() || p.z() != m_View.focusPoint.z() ) { m_View.focusPoint = p; m_MustUpdateViewMatrix = true; emit focusPointChanged( m_View.focusPoint ); update(); } } void GLViewer::setDistToFocusPoint( const float d ) { if( d != m_View.distToFocusPoint ) { m_View.distToFocusPoint = d; m_View.zNear = m_View.nearPlaneFactor * m_View.distToFocusPoint; m_View.zFar = m_View.farPlaneFactor * m_View.distToFocusPoint; m_MustUpdateProjectionMatrix = true; m_MustUpdateViewMatrix = true; emit distToFocusPointChanged( m_View.distToFocusPoint ); update(); } } void GLViewer::setViewRotationMatrix( const QMatrix4x4& m ) { m_View.viewRotationMatrix = m; m_MustUpdateViewMatrix = true; update(); } void GLViewer::setFovY( float fovY ) { if( fovY != m_View.fovY ) { m_View.fovY = fovY; m_MustUpdateFocal = true; m_MustUpdateProjectionMatrix = true; emit fovYChanged( m_View.fovY ); update(); } } void GLViewer::setSelectionEnabled( bool enabled ) { makeCurrent(); if( enabled != m_SelectionEnabled ) { if( enabled ) { int availableEntities = 0; for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) for( auto mngr : d->second.selectionMgr ) availableEntities |= mngr->selectableEntity(); if( !availableEntities ) return; int newEntity = m_SelectionCurrentTool->entityToSelect(); while( !(newEntity & availableEntities) ) newEntity = (newEntity&4)? 1 : (newEntity << 1); setSelectionEntity( SelectableEntity::SELECTABLE_ENTITY_NONE ); setSelectionEntity( (SelectableEntity) newEntity ); } m_SelectionEnabled = enabled; setCursor( enabled? QCursor(Qt::CrossCursor) : QCursor(Qt::ArrowCursor) ); setMouseTracking( enabled ); if( enabled ) { setSelectionEntity( m_SelectionCurrentTool->entityToSelect() ); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) { if( d->first->GetDisplayOptions() ) d->first->GetDisplayOptions()->setDisabled( true ); } updateSelectionZBuffer(); m_SelectionCurrentTool->onEnabled(); } else { // Releases the selection depth buffer. m_SelectionDepthTexture.Release(); m_SelectionDepthBuffer.Release(); m_SelectionCurrentTool->postSelectionUpdate(); m_SelectionCurrentTool->onDisabled(); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) if( d->first->GetDisplayOptions() ) d->first->GetDisplayOptions()->setEnabled( true ); } } } void GLViewer::setSelectionDisabled( bool disabled ) { setSelectionEnabled( !disabled ); } void GLViewer::toggleSelection() { setSelectionEnabled( !m_SelectionEnabled ); } void GLViewer::setSelectionEntity( SelectableEntity entity ) { if( m_SelectionCurrentTool->entityToSelect() != entity ) { makeCurrent(); m_SelectionCurrentTool->setEntityToSelect( entity ); m_SelectionZBufferUpdateMode = ON_SELECTION_ENABLED; for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) { d->second.currentSelectionMgr = NULL; for( auto mngr : d->second.selectionMgr ) if( mngr->selectableEntity() == entity ) { d->second.currentSelectionMgr = mngr; d->second.currentSelectionMgr->onRecompilingSelectionShader( m_SelectionCurrentTool ); if( mngr->isRealTimeSelectionEnabled() ) m_SelectionZBufferUpdateMode = SelectionZBufferUpdateMode( m_SelectionZBufferUpdateMode | mngr->realTimeListener()->zBufferUpdateMode() ); break; } } update(); } } void GLViewer::clearSelection( SelectableEntity entities ) { makeCurrent(); for( DisplayableMap::iterator d=m_Displayables.begin(); d!=m_Displayables.end(); ++d ) if( d->second.currentSelectionMgr ) d->second.currentSelectionMgr->clearSelection(); } void GLViewer::setCullingEnabled( const bool enabled ) { if( m_View.isCullingEnabled != enabled ) toggleCulling(); } void GLViewer::toggleCulling() { m_View.isCullingEnabled = !m_View.isCullingEnabled; emit cullingToggled( m_View.isCullingEnabled ); update(); } void GLViewer::setWireframeEnabled( const bool enabled ) { if( m_View.isWireframeEnabled != enabled ) toggleWireframe(); } void GLViewer::setLightingEnabled( const bool enabled ) { if( m_View.isLightingEnabled != enabled ) toggleLighting(); } void GLViewer::setLightTrackingEnabled( const bool enabled ) { if( m_View.isLightTrackingEnabled != enabled ) toggleLightTracking(); } void GLViewer::toggleWireframe() { m_View.isWireframeEnabled = !m_View.isWireframeEnabled; emit wireframeToggled( m_View.isWireframeEnabled ); update(); } void GLViewer::toggleLighting() { m_View.isLightingEnabled = !m_View.isLightingEnabled; emit lightingToggled( m_View.isLightingEnabled ); update(); } void GLViewer::toggleLightTracking() { if( m_View.isLightTrackingEnabled ) m_View.lightRotationMatrix = viewRotationMatrix().inverted() * m_View.lightRotationMatrix; else m_View.lightRotationMatrix = viewRotationMatrix() * m_View.lightRotationMatrix; m_View.isLightTrackingEnabled = !m_View.isLightTrackingEnabled; emit lightTrackingToggled( m_View.isLightTrackingEnabled ); update(); }