/* * (c) LSIIT, UMR CNRS/UdS * Authors: O. Génevaux, F. Larue. * * See licence.txt for additional information. */ #include "GLViewer.h" #include "UIMainWindow.h" #include #include #include #include "InfoBarManager.h" #include #include #include #include QVector3D GLViewer::BgTopColor; QVector3D GLViewer::BgBottomColor; QVector3D GLViewer::CoolColor( 0.5f, 0.0f, 1.0f ); QVector3D GLViewer::WarmColor( 1.0f, 0.8f, 0.0f ); QVector3D GLViewer::LightColor( 1.0f, 1.0f, 1.0f ); QVector3D GLViewer::AmbientColor; QVector3D GLViewer::SpecularColor; float GLViewer::Ambient = 0.1f; float GLViewer::Specular = 0.5f; float GLViewer::Shininess = 80.0f; QString GLViewer::s_MetricGrid2DSuffix; QString GLViewer::s_MetricGrid3DSuffix; QImage GLViewer::s_Logos[4]; 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() { removeAllDisplayables(); clearNavigationControlStack(); 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 ); m_View.metricGrid2D.setSuffix( &s_MetricGrid2DSuffix ); m_View.metricGrid3D.setSuffix( &s_MetricGrid3DSuffix ); } void GLViewer::init( DisplayDoF dof ) { m_BelowCursor = NULL; m_MustUpdateFocal = true; m_MustUpdateProjectionMatrix = true; m_MustUpdateViewMatrix = true; setSpeedFactor( 1.0f, 4.0f, 0.25f ); 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.isOrthographic = false; 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 ); } if( m_NavigationCtrl.empty() ) { m_NavigationCtrl.push_front( new NavigationControl(this) ); } else { while( m_NavigationCtrl.size() > 1 ) popNavigationControl(); } // Initialize selection tools. m_IsSelectionAllowed = true; 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; m_PerformanceCounterEnabled = 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::pushNavigationControl( NavigationControl* ctrl ) { assert( ctrl != NULL ); m_NavigationCtrl.push_front(ctrl); m_MustUpdateFocal = true; m_MustUpdateViewMatrix = true; update(); } void GLViewer::popNavigationControl() { if( !m_NavigationCtrl.empty() ) { delete m_NavigationCtrl.front(); m_NavigationCtrl.pop_front(); m_MustUpdateFocal = true; m_MustUpdateViewMatrix = true; update(); } } void GLViewer::clearNavigationControlStack() { while( !m_NavigationCtrl.empty() ) { delete m_NavigationCtrl.front(); m_NavigationCtrl.pop_front(); } } 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; } void GLViewer::populateDisplayerFactories( QList &plugins ) { foreach( DisplayableFactoryInterface *plugin, plugins ) populateDisplayerFactories( plugin ); } DisplayableInterface* GLViewer::addDisplayable( GenericUIData *m ) { // If a displayable already exists for this manageable, remove it before creating a new one. //removeDisplayable( m ); DisplayableMap::iterator foundDisp = m_Displayables.find( m ); if( foundDisp != m_Displayables.end() ) return foundDisp->second.displayable; // Recovers the displayable factory for the corresponding datatype. FactoryMap::iterator factory = m_Factories.find( m->GetTypeString() ); if( factory == m_Factories.end() ) return NULL; // 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(), 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( m->GetDisplayOptions()->AreAllHidden() ) m->GetDisplayOptions()->hide(); } if( isSelectionEnabled() ) { for( auto mngr : dispInfo->selectionMgr ) if( mngr->selectableEntity() == m_SelectionCurrentTool->entityToSelect() ) { dispInfo->currentSelectionMgr = mngr; mngr->onRecompilingSelectionShader( m_SelectionCurrentTool ); break; } } update(); } if( dof() == DISPLAY_DOF_2D && displayable ) { m_AddingOrder.push_back( dispInfo ); repackDisplayables(); } return displayable; } //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, bool resetSelectionManagers ) { 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. if( resetSelectionManagers ) { 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; } } } 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( QList &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(); 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() ) frameBox( sceneBox() ); } Box3f GLViewer::sceneBox() const { Box3f box; for( auto &dispInfo : m_Displayables ) { const Box3f &objectBox = dispInfo.second.boundingBox; if( !objectBox.IsNull() ) for( auto &c : objectBox.Corners() ) box.Add( dispInfo.second.GetTransform().map(c) ); } return 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() ); item->GetDisplayOptions()->setParent( NULL ); } 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 ); update(); } } void GLViewer::removeAllDisplayables() { makeCurrent(); DisplayableMap::iterator disp = m_Displayables.begin(); while( disp != m_Displayables.end() ) disp = removeDisplayable_Internal( disp ); m_AddingOrder.clear(); update(); } void GLViewer::initializeGL() { makeCurrent(); glewInit(); setFocusPolicy( Qt::ClickFocus ); resizeGL( width(), height() ); update(); } void GLViewer::pixelRay( int px, int py, QVector3D &rayOrig, QVector3D &rayDir ) const { if( isProjectionOrthographic() ) { QVector4D clipCoord( 2.0f*px/width() - 1.0f, 2.0f*py/height() - 1.0f, -1.0f, 1.0f ); rayOrig = viewMatrixInverse().map( projectionMatrixInverse().map( clipCoord ) ).toVector3D(); rayDir = frontAxis(); } else { QVector4D clipCoord( 2.0f*px/width() - 1.0f, 2.0f*py/height() - 1.0f, 0.0f, 1.0f ); rayOrig = viewpointLocation(); rayDir = viewRotationMatrixInverse().map( projectionMatrixInverse().map( clipCoord ) ).toVector3D(); } } 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.0; 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; } // Accurate measurement of the time elapsed since the last animation step. TimeMeasurement now = std::chrono::high_resolution_clock::now(); double dt = 0.000001 * std::chrono::duration_cast< std::chrono::microseconds >( now - m_AnimationFrameTime ).count(); m_AnimationFrameTime = now; m_AnimationFrameRate = float( 1.0 / dt ); // Make all active animated objects to execute a new animation step. if( animationsRunning ) { 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::setDemoModeEnabled( bool enabled ) { if( m_IsDemoModeEnabled = enabled ) { updateDemoModeLogos(); update(); } } void GLViewer::updateDemoModeLogos() { if( m_IsDemoModeEnabled ) { if( s_Logos[0].isNull() ) { s_Logos[0] = QImage( ":/images/resources/unistra-logo.png" ); s_Logos[1] = QImage( ":/images/resources/igg-logo.png" ); s_Logos[2] = QImage( ":/images/resources/icube-logo.png" ); } int h = int(0.075f * height()); if( h != m_CurrentLogos[0].height() ) for( int i=0; i<3; ++i ) { h = int(((i == 1)? 0.09f : 0.075f) * height()); int w = int(float(h)*s_Logos[i].width()/s_Logos[i].height()); m_CurrentLogos[i] = s_Logos[i].scaled( QSize(w,h), Qt::KeepAspectRatio, Qt::SmoothTransformation ); } } } void GLViewer::displayDemoModeLogos() { if( m_IsDemoModeEnabled ) { QPainter painter; painter.begin( this ); painter.drawImage( 25, height()-m_CurrentLogos[0].height()-10, m_CurrentLogos[0] ); painter.drawImage( (width()-m_CurrentLogos[1].width())/2, height()-m_CurrentLogos[1].height()-10, m_CurrentLogos[1] ); painter.drawImage( width()-m_CurrentLogos[2].width()-25, height()-m_CurrentLogos[2].height()-10, m_CurrentLogos[2] ); painter.end(); } } 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 ); glColor3fv( &GLViewer::BgBottomColor[0] ); glVertex2i( -1, -1 ); glVertex2i( 1, -1 ); glColor3fv( &GLViewer::BgTopColor[0] ); 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 ) { navigationControl()->updateProjectionMatrix( m_ProjectionMatrix ); m_ProjectionMatrixInverse = m_ProjectionMatrix.inverted(); m_MustUpdateProjectionMatrix = false; mustUpdateViewProjectionMatrix = true; } if( m_MustUpdateViewMatrix ) { navigationControl()->updateViewMatrix( m_ViewMatrix ); 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_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 = QApplication::font(); f.setPointSize( 10 ); f.setWeight( QFont::Bold ); Qt::Alignment align = static_cast( Qt::AlignHCenter | Qt::AlignBottom ); 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_IsCursorInside ) { m_SelectionCurrentTool->displaySelectionState(); m_SelectionCurrentTool->display(); } if( m_PerformanceCounterEnabled ) { QString label = QString::number( m_AnimationFrameRate, 'f', 2 ) + " FPS"; glColor3ub( 0, 0, 0 ); renderText( 10, 10, label, QFont( font().family(), 20, QFont::Bold ), static_cast( Qt::AlignLeft | Qt::AlignTop ) ); update(); } // Display the information bar. m_InfoBarManager->display(); displayDemoModeLogos(); } 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(); #if QT_VERSION < QT_VERSION_CHECK(5,11,0) imgNameWidth = fm.width( imgName ); #else imgNameWidth = fm.horizontalAdvance( imgName ); #endif if( imgNameWidth > topRight.x()-bottomLeft.x()-8 ) { int imgNameLength = imgName.length(); do { -- imgNameLength; #if QT_VERSION < QT_VERSION_CHECK(5,11,0) imgNameWidth -= fm.width( imgName.at(imgNameLength) ); #else imgNameWidth -= fm.horizontalAdvance( imgName.at(imgNameLength) ); #endif } 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::AlignBottom ); renderText( textX-1, textY+1, imgName, f, Qt::AlignBottom ); renderText( textX+1, textY-1, imgName, f, Qt::AlignBottom ); renderText( textX+1, textY+1, imgName, f, Qt::AlignBottom ); glColor3ub( 64, 64, 64 ); renderText( textX, textY, imgName, f, Qt::AlignBottom ); } } } 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(); updateDemoModeLogos(); } 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, QList > processorByType; for( BaseSelectionProcessor* p : procList ) { QStringList acceptedTypes; p->acceptedDataType( acceptedTypes ); for( QString& type : acceptedTypes ) processorByType[type].push_back( p ); } for( auto& d : m_Displayables ) { auto processorFound = processorByType.find( d.first->GetTypeString() ); if( processorFound != processorByType.end() ) for( auto p : processorFound.value() ) processSelection( d.second, *p ); } } 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.isDisplayableUpdateRequired() ) { updateDisplayable( m, proc.isSelectionResetRequired() ); updateSelectionZBuffer(); } else if( proc.isSelectionResetRequired() ) mgr->clearSelection(); } } QVector2D GLViewer::screenCoords( float x, float y, float z ) const { QVector2D screen( 0.0f, 0.0f ); // Identify x and y locations to render text within widget QMatrix4x4 mvp = viewProjectionMatrix(); QVector4D out = mvp.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 ); screen.setX( (1 + out.x()) * 0.5 * width () ); screen.setY( (1 + out.y()) * 0.5 * height() ); } screen.setY( height() - screen.y() ); // y is inverted return screen; } void GLViewer::renderText( float x, float 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 blend, depthTest, multisample; GLint blendSrcRGB, blendSrcAlpha; GLint blendDstRGB, blendDstAlpha; GLint alignment; GLfloat glColor[4]; glGetIntegerv( GL_FRAMEBUFFER_BINDING, &buffer ); glGetBooleanv( GL_DEPTH_TEST, &depthTest ); glGetBooleanv( GL_BLEND, &blend ); glGetBooleanv( GL_MULTISAMPLE, &multisample ); glGetIntegerv( GL_BLEND_SRC_RGB , &blendSrcRGB ); glGetIntegerv( GL_BLEND_SRC_ALPHA, &blendSrcAlpha ); glGetIntegerv( GL_BLEND_DST_RGB , &blendDstRGB ); glGetIntegerv( GL_BLEND_DST_ALPHA, &blendDstAlpha ); glGetIntegerv( GL_UNPACK_ALIGNMENT, &alignment ); glGetFloatv( GL_CURRENT_COLOR, glColor ); // Retrieve last OpenGL color to use as a font color. QColor fontColor = QColor( 255.0f*glColor[0], 255.0f*glColor[1], 255.0f*glColor[2], 255.0f*glColor[3] ); // Paint text. if( align & Qt::AlignBottom ) y -= (textLines.size()-1) * fm.lineSpacing() + fm.descent(); else if( align & Qt::AlignVCenter ) y -= ((textLines.size()) * fm.lineSpacing())/2 - fm.ascent(); else if( align & Qt::AlignTop ) y += fm.ascent(); glPixelStorei( GL_UNPACK_ALIGNMENT, 4 ); // Mandatory to avoid font corruption while using painter.drawText(...)! QPainter painter; painter.begin( this ); painter.setPen( fontColor ); painter.setFont( font ); //painter.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing ); for( auto &l : textLines ) { if( align & Qt::AlignRight ) #if QT_VERSION < QT_VERSION_CHECK(5,11,0) painter.drawText( x - fm.width(l), y, l ); #else painter.drawText( x - fm.horizontalAdvance(l), y, l ); #endif else if( align & Qt::AlignHCenter ) #if QT_VERSION < QT_VERSION_CHECK(5,11,0) painter.drawText( x - fm.width(l)/2, y, l ); #else painter.drawText( x - fm.horizontalAdvance(l)/2, y, l ); #endif else painter.drawText( x, y, l ); y += fm.lineSpacing(); } painter.end(); // Restore OpenGL states. glPixelStorei( GL_UNPACK_ALIGNMENT, alignment ); glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, buffer ); blend? glEnable( GL_BLEND ) : glDisable( GL_BLEND ); depthTest? glEnable( GL_DEPTH_TEST ) : glDisable( GL_DEPTH_TEST ); multisample? glEnable( GL_MULTISAMPLE ) : glDisable( GL_MULTISAMPLE ); glBlendFuncSeparateEXT( blendSrcRGB, blendDstRGB, blendSrcAlpha, blendDstAlpha ); glColor4fv( glColor ); } 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( viewMatrixInverse().data() ); glMultMatrixf( modelMatrixInverse(d).data() ); QMatrix4x4 projBackup = m_ProjectionMatrix; glGetFloatv( GL_PROJECTION_MATRIX, m_ProjectionMatrix.data() ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadMatrixf( 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; } navigationControl()->mouseMoveEvent( dx, dy, evt ); } else if( evt->modifiers() & Qt::ShiftModifier )//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 || m_ChangeFarPlaneMode ) { float &planeFactor = m_ChangeNearPlaneMode? m_View.nearPlaneFactor : m_View.farPlaneFactor; float &plane = m_ChangeNearPlaneMode? m_View.zNear : m_View.zFar; double delta = 0.0005 * planeFactor * evt->delta(); if( planeFactor + delta > 0.0 ) { planeFactor += delta; plane = planeFactor * m_View.distToFocusPoint; m_MustUpdateProjectionMatrix = true; update(); } } else { double deltaD = evt->delta(); if( evt->modifiers() & Qt::ShiftModifier ) deltaD *= m_ShiftSpeedFactor; else if( evt->modifiers() & Qt::ControlModifier ) deltaD *= m_CtrlSpeedFactor; else deltaD *= m_SpeedFactor; navigationControl()->wheelEvent( deltaD, evt ); } } 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 ) navigationControl()->mouseDoubleClickEvent( evt ); } bool GLViewer::event( QEvent *evt ) { if( evt->type() == QEvent::KeyPress ) { QKeyEvent *kevt = static_cast( evt ); if( kevt->key() == Qt::Key_Tab ) { if( !m_IsSelectionAllowed ) return true; 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 ) { if( navigationControl()->keyPressEvent(evt) ) return; bool defaultBehaviour = false; if( dof() == DISPLAY_DOF_2D ) { switch( evt->key() ) { 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 // if( dof() == DISPLAY_DOF_3D ) { 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_Plus: { increasePointSize(); break; } case Qt::Key_Minus: { decreasePointSize(); 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: { 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_F12: { m_PerformanceCounterEnabled = !m_PerformanceCounterEnabled; update(); break; } case Qt::Key_PageDown: m_ChangeNearPlaneMode = true; break; case Qt::Key_PageUp: 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_PageDown ) m_ChangeNearPlaneMode = false; if( evt->key() == Qt::Key_PageUp ) 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, frustumR, frustumB, frustumT, frustumN, frustumF; if( isProjectionOrthographic() ) { frustumN = (projMat(2,3) + 1.0) / projMat(2,2); frustumF = (projMat(2,3) - 1.0) / projMat(2,2); frustumL = -(1.0 + projMat(0,3)) / projMat(0,0); frustumR = (1.0 - projMat(0,3)) / projMat(0,0); frustumB = -(1.0 + projMat(1,3)) / projMat(1,1); frustumT = (1.0 - projMat(1,3)) / projMat(1,1); } else { frustumN = projMat(2,3) / (projMat(2,2) - 1.0); frustumF = projMat(2,3) / (projMat(2,2) + 1.0); frustumL = frustumN * (projMat(0,2) - 1.0) / projMat(0,0); frustumR = frustumN * (projMat(0,2) + 1.0) / projMat(0,0); frustumB = frustumN * (projMat(1,2) - 1.0) / projMat(1,1); frustumT = frustumN * (projMat(1,2) + 1.0) / 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 ); if( isProjectionOrthographic() ) { projMat(0,0) = 2.0f / (newFrustumR - newFrustumL); projMat(0,3) = -(newFrustumR + newFrustumL) / (newFrustumR - newFrustumL); projMat(1,1) = 2.0f / (newFrustumT - newFrustumB); projMat(1,3) = -(newFrustumT + newFrustumB) / (newFrustumT - newFrustumB); projMat(2,2) = -2.0f / (frustumF - frustumN); projMat(2,3) = -(frustumF + frustumN) / (frustumF - frustumN); projMat(3,3) = 1.0f; } else { projMat(0,0) = 2.0f * frustumN / (newFrustumR - newFrustumL); projMat(0,2) = (newFrustumR + newFrustumL) / (newFrustumR - newFrustumL); projMat(1,1) = 2.0f * frustumN / (newFrustumT - newFrustumB); projMat(1,2) = (newFrustumT + newFrustumB) / (newFrustumT - newFrustumB); projMat(2,2) = (frustumN + frustumF) / (frustumN - frustumF); projMat(2,3) = 2.0f * frustumN * frustumF / (frustumN - frustumF); 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, rayDir; pixelRay( x, y, rayOrig, rayDir ); rayDir.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.0f, 0.0f, 0.0f) ); } 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; emit pointSizeChanged( m_View.pointSize ); update(); } void GLViewer::decreasePointSize() { if( m_View.pointSize > 1 ) { -- m_View.pointSize; emit pointSizeChanged( m_View.pointSize ); update(); } } void GLViewer::setPointSize( unsigned int size ) { if( size >= 1 && size != m_View.pointSize ) { m_View.pointSize = size; emit pointSizeChanged( 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::setOrthographic( bool ortho ) { if( ortho != m_View.isOrthographic ) { m_View.isOrthographic = ortho; m_MustUpdateFocal = true; m_MustUpdateProjectionMatrix = true; update(); } } void GLViewer::setSelectionAllowed( bool allowed ) { m_IsSelectionAllowed = allowed; if( !allowed && isSelectionEnabled() ) toggleSelection(); } 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(); } void GLViewer::updateSelection( QList< GenericUIData*> selectedItems ) { for( auto d : m_Displayables ) d.second.displayable->setSelected( false ); for( auto item : selectedItems ) { DisplayableInterface *d = getDisplayable( item ); if( d ) d->setSelected( true ); } update(); } bool GLViewer::isDisplayableValid( DisplayableInterface *d ) const { for( auto dinfo : m_Displayables ) if( dinfo.second.displayable == d ) return true; return false; }