Skip to content
Snippets Groups Projects
MainWindow.cpp 21.76 KiB
#include <Gui/MainWindow.hpp>
#include <RadiumNBR/Gui/RendererPanel.hpp>

#include <tuple>

#include <QColorDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QToolBar>

#include <Gui/SelectionManager/SelectionManager.hpp>
#include <Gui/Timeline/Timeline.hpp>
#include <Gui/Viewer/CameraManipulator.hpp>
#include <Gui/Viewer/Gizmo/GizmoManager.hpp>
#include <Gui/Viewer/Viewer.hpp>

#include <PluginBase/RadiumPluginInterface.hpp>

#include <Engine/Rendering/ForwardRenderer.hpp>
#include <Engine/Rendering/RenderObjectManager.hpp>
#include <Engine/Scene/Camera.hpp>
#include <Engine/Scene/EntityManager.hpp>
#include <Engine/Scene/SignalManager.hpp>
#include <Engine/Scene/SystemDisplay.hpp>

#include <Core/Asset/FileLoaderInterface.hpp>
#include <Core/Utils/StringUtils.hpp>

#include <RadiumPlayer.hpp>

#include <Engine/Scene/SkeletonBasedAnimationSystem.hpp>
#include <Gui/SkeletonBasedAnimation/SkeletonBasedAnimationUI.hpp>

using namespace Ra::Gui;
using namespace Ra::Engine;
using namespace Ra::Engine::Rendering;
using namespace Ra::Engine::Scene;
namespace Mara {
MainWindow::MainWindow( QWidget* parent ) : MainWindowInterface( parent ) {
    setupUi( this );

    // Initialize the minimum tools for a Radium-Guibased Application
    m_viewer = std::make_unique<Viewer>();
    m_viewer->setObjectName( QStringLiteral( "m_viewer" ) );

    // Initialize the scene interactive representation
    m_sceneModel       = new ItemModel( Ra::Engine::RadiumEngine::getInstance(), this );
    m_selectionManager = new SelectionManager( m_sceneModel, this );
    connect( m_selectionManager,
             &Ra::Gui::SelectionManager::selectionChanged,
             this,
             &MainWindow::onSelectionChanged );
    // initialize Gui for the application
    auto viewerwidget = QWidget::createWindowContainer( m_viewer.get() );
    viewerwidget->setAutoFillBackground( false );
    setCentralWidget( viewerwidget );
    setWindowTitle( QString( "Radium player" ) );

    // Application control window
    m_controlWindow = new ControlDialogWindow();
#ifdef SHOWTREEVIEW
    m_sceneTreeView = new QTreeView();
    m_sceneTreeView->setModel( m_sceneModel );
    m_sceneTreeView->setSelectionModel( m_selectionManager );
    m_controlWindow->addTab( m_sceneTreeView, "Scene" );
    addControl( "Scene", tr( "Ctrl+N" ) );
#endif
    m_rendererControler = new RendererControl( this );
    m_controlWindow->addTab( m_rendererControler, "Renderer" );
    addControl( "Renderer", tr( "Ctrl+R" ) );
    createConnections();
}

MainWindow::~MainWindow() = default;
// Child QObjects will automatically be deleted

void MainWindow::cleanup() {
    // Must ensure the viewer is deleted once.
    m_viewer.reset( nullptr );
}

void MainWindow::addTimelineToolBar() {
    auto timelineToolbar = new QToolBar( "Timeline", this );
    timelineToolbar->setAllowedAreas( Qt::BottomToolBarArea );
    timelineToolbar->setVisible( false );

    // Add and connect timeline
    m_timeline = new Ra::Gui::Timeline( this );
    m_timeline->onChangeEnd( Ra::Engine::RadiumEngine::getInstance()->getEndTime() );
    // Timeline setup
    connect( m_timeline, &Ra::Gui::Timeline::playClicked, this, &MainWindow::timelinePlay );
    connect( m_timeline, &Ra::Gui::Timeline::cursorChanged, this, &MainWindow::timelineGoTo );
    connect(
        m_timeline, &Ra::Gui::Timeline::startChanged, this, &MainWindow::timelineStartChanged );
    connect( m_timeline, &Ra::Gui::Timeline::endChanged, this, &MainWindow::timelineEndChanged );
    connect( m_timeline, &Ra::Gui::Timeline::setPingPong, this, &MainWindow::timelineSetPingPong );
    connect(
        m_timeline, &Ra::Gui::Timeline::keyFrameChanged, [=]( Scalar ) { emit frameUpdate(); } );
    connect( this, &MainWindow::selectedItem, m_timeline, &Ra::Gui::Timeline::selectionChanged );

    timelineToolbar->addWidget( m_timeline );

    this->addToolBar( Qt::BottomToolBarArea, timelineToolbar );

    auto showTimelineToolbar = new QAction( this );
    showTimelineToolbar->setObjectName( QString::fromUtf8( "showTimelineToolbar" ) );
    showTimelineToolbar->setCheckable( true );
    showTimelineToolbar->setChecked( false );
    showTimelineToolbar->setText(
        QCoreApplication::translate( "MainWindow", "Timeline toolbar", nullptr ) );
    showTimelineToolbar->setToolTip(
        QCoreApplication::translate( "MainWindow", "Show/hide the timeline toolbar", nullptr ) );

    connect( showTimelineToolbar, &QAction::triggered, timelineToolbar, &QToolBar::setVisible );
    connect( timelineToolbar, &QToolBar::visibilityChanged, [showTimelineToolbar]( bool b ) {
        showTimelineToolbar->setChecked( b );
    } );

    menuToolbars->addAction( showTimelineToolbar );
}

void MainWindow::addSelectionToolBar() {
    auto selectionToolBar = new QToolBar( "Selection toolbar", this );

    auto actionGizmoOff = new QAction( selectionToolBar );
    actionGizmoOff->setObjectName( QString::fromUtf8( "actionGizmoOff" ) );
    actionGizmoOff->setCheckable( false );
    actionGizmoOff->setChecked( false );
    QIcon icon3;
    icon3.addFile(
        QString::fromUtf8( ":/Resources/Icons/select.png" ), QSize(), QIcon::Normal, QIcon::On );
    actionGizmoOff->setIcon( icon3 );
    actionGizmoOff->setText( QCoreApplication::translate( "MainWindow", "No Gizmos", nullptr ) );
    actionGizmoOff->setToolTip(
        QCoreApplication::translate( "MainWindow", "Disable transform gizmo.", nullptr ) );

    auto actionGizmoTranslate = new QAction( selectionToolBar );
    actionGizmoTranslate->setObjectName( QString::fromUtf8( "actionGizmoTranslate" ) );
    actionGizmoTranslate->setCheckable( false );
    QIcon icon;
    icon.addFile(
        QString::fromUtf8( ":/Resources/Icons/translate.png" ), QSize(), QIcon::Normal, QIcon::On );
    actionGizmoTranslate->setIcon( icon );
    actionGizmoTranslate->setText(
        QCoreApplication::translate( "MainWindow", "Translate", nullptr ) );
    actionGizmoTranslate->setToolTip(
        QCoreApplication::translate( "MainWindow", "Translate selected object", nullptr ) );

    auto actionGizmoRotate = new QAction( selectionToolBar );
    actionGizmoRotate->setObjectName( QString::fromUtf8( "actionGizmoRotate" ) );
    actionGizmoRotate->setCheckable( false );
    QIcon icon1;
    icon1.addFile(
        QString::fromUtf8( ":/Resources/Icons/rotate.png" ), QSize(), QIcon::Normal, QIcon::On );
    actionGizmoRotate->setIcon( icon1 );
    actionGizmoRotate->setText( QCoreApplication::translate( "MainWindow", "Rotate", nullptr ) );
    actionGizmoRotate->setToolTip(
        QCoreApplication::translate( "MainWindow", "Rotate selected object", nullptr ) );

    auto actionToggle_Local_Global = new QAction( selectionToolBar );
    actionToggle_Local_Global->setObjectName( QString::fromUtf8( "actionToggle_Local_Global" ) );
    actionToggle_Local_Global->setCheckable( true );
    QIcon icon2;
    icon2.addFile(
        QString::fromUtf8( ":/Resources/Icons/gizmo.png" ), QSize(), QIcon::Normal, QIcon::On );
    actionToggle_Local_Global->setIcon( icon2 );
    actionToggle_Local_Global->setText(
        QCoreApplication::translate( "MainWindow", "Toggle Local/Global", nullptr ) );
    actionToggle_Local_Global->setToolTip( QCoreApplication::translate(
        "MainWindow", "Changes the transform edition mode", nullptr ) );

    auto actionGizmoScale = new QAction( selectionToolBar );
    actionGizmoScale->setObjectName( QString::fromUtf8( "actionGizmoScale" ) );
    actionGizmoScale->setCheckable( false );
    QIcon icon5;
    icon5.addFile(
        QString::fromUtf8( ":/Resources/Icons/scale.png" ), QSize(), QIcon::Normal, QIcon::Off );
    actionGizmoScale->setIcon( icon5 );
    actionGizmoScale->setText( QCoreApplication::translate( "MainWindow", "Scale", nullptr ) );
    actionGizmoScale->setToolTip(
        QCoreApplication::translate( "MainWindow", "Scale selected object", nullptr ) );

    selectionToolBar->addAction( actionToggle_Local_Global );
    selectionToolBar->addAction( actionGizmoTranslate );
    selectionToolBar->addAction( actionGizmoRotate );
    selectionToolBar->addAction( actionGizmoScale );
    selectionToolBar->addAction( actionGizmoOff );

    connect(
        this, &MainWindow::selectedItem, m_viewer->getGizmoManager(), &GizmoManager::setEditable );
    connect( actionGizmoOff, &QAction::triggered, this, &MainWindow::gizmoShowNone );
    connect( actionGizmoTranslate, &QAction::triggered, this, &MainWindow::gizmoShowTranslate );
    connect( actionGizmoRotate, &QAction::triggered, this, &MainWindow::gizmoShowRotate );
    connect( actionGizmoScale, &QAction::triggered, this, &MainWindow::gizmoShowScale );
    connect( actionToggle_Local_Global,
             &QAction::toggled,
             m_viewer->getGizmoManager(),
             &GizmoManager::setLocal );
    connect( actionToggle_Local_Global, &QAction::toggled, this, &MainWindow::frameUpdate );

    this->addToolBar( selectionToolBar );

    auto showSelectionToolbar = new QAction( this );
    showSelectionToolbar->setObjectName( QString::fromUtf8( "showSelectionToolbar" ) );
    showSelectionToolbar->setCheckable( true );
    showSelectionToolbar->setChecked( true );
    showSelectionToolbar->setText(
        QCoreApplication::translate( "MainWindow", "Selection toolbar", nullptr ) );
    showSelectionToolbar->setToolTip(
        QCoreApplication::translate( "MainWindow", "Show/hide the selection toolbar", nullptr ) );

    connect( showSelectionToolbar, &QAction::triggered, selectionToolBar, &QToolBar::setVisible );
    connect( selectionToolBar, &QToolBar::visibilityChanged, [showSelectionToolbar]( bool b ) {
        showSelectionToolbar->setChecked( b );
    } );

    menuToolbars->addAction( showSelectionToolbar );
}

void MainWindow::postOpenGLInitializations() {
    addSelectionToolBar();
    addTimelineToolBar();
    addRadiumSystemsUI();
}

void MainWindow::createConnections() {
    // Radium engine and Gui initialization process
    connect( m_viewer.get(), &Viewer::glInitialized, this, &MainWindow::postOpenGLInitializations );

    // Define and connect Engine callbacks
    auto add = std::bind( &MainWindow::ItemAdded, this, std::placeholders::_1 );
    auto del = std::bind( &MainWindow::ItemRemoved, this, std::placeholders::_1 );
    // Connect engine signals to the appropriate callbacks
    auto theEngine = Ra::Engine::RadiumEngine::getInstance();
    theEngine->getSignalManager()->m_entityCreatedCallbacks.push_back( add );
    theEngine->getSignalManager()->m_entityDestroyedCallbacks.push_back( del );
    theEngine->getSignalManager()->m_componentAddedCallbacks.push_back( add );
    theEngine->getSignalManager()->m_componentRemovedCallbacks.push_back( del );
    theEngine->getSignalManager()->m_roAddedCallbacks.push_back( add );
    theEngine->getSignalManager()->m_roRemovedCallbacks.push_back( del );

    // Qt Gui callbacks
    connect( action_Load, &QAction::triggered, this, &MainWindow::loadFile );
    connect( actionInfo, &QAction::triggered, this, &MainWindow::displayFileInfo );
    connect( action_Reload_shaders, &QAction::triggered, m_viewer.get(), &Viewer::reloadShaders );
    connect( m_rendererControler,
             &RendererControl::rendererStateChanged,
             this,
             &MainWindow::frameUpdate );
    connect( action_Config, &QAction::triggered, this, &MainWindow::configure );
    connect( actionAdd_PluginsDir, &QAction::triggered, this, &MainWindow::addPluginsDir );
    connect( actionClear_PluginsDir, &QAction::triggered, this, &MainWindow::clearPluginsDir );

#ifdef SHOWTREEVIEW
    connect( actionTree_view, &QAction::triggered, this, &MainWindow::displayTreeView );
#endif

    // TODO : connect callbacks roAdded and roRemoved to the renderer and add observable on
    // RenderObject to manage more efficiently update for rendering
    connect( m_sceneModel, &ItemModel::visibilityROChanged, this, &MainWindow::setROVisible );

    connect( m_rendererControler, &RendererControl::fitCamera, this, &MainWindow::fitCamera );
    connect( m_rendererControler, &RendererControl::resetCamera, this, &MainWindow::resetCamera );
}

void MainWindow::addRenderer( const std::string& name, std::shared_ptr<Renderer> e ) {
    addRenderer( name, e, nullptr );
}

void MainWindow::addRenderer( const std::string& name,
                              std::shared_ptr<Renderer> e,
                              RadiumNBR::Gui::RendererPanel* controlPanel ) {
    auto id               = m_viewer->addRenderer( e );
    auto rendererCallback = [this, id]() { return this->m_viewer->changeRenderer( id ); };
    m_rendererControler->addRenderer( name, e.get(), rendererCallback, controlPanel );
}

Viewer* MainWindow::getViewer() {
    return m_viewer.get();
}

void MainWindow::onFrameComplete() {
    // update timeline only if time changed, to allow manipulation of keyframed objects
    auto engine = Ra::Engine::RadiumEngine::getInstance();
    if ( !Ra::Core::Math::areApproxEqual( m_timeline->getTime(), engine->getTime() ) )
    {
        m_lockTimeSystem = true;
        m_timeline->onChangeCursor( engine->getTime() );
        m_lockTimeSystem = false;
    }
}

void MainWindow::activateCamera( const std::string& sceneName ) {
    // If a camera is in the given scene, use it, else, use default
    std::string loadedEntityName = Ra::Core::Utils::getBaseName( sceneName, false );
    auto rootEntity =
        Ra::Engine::RadiumEngine::getInstance()->getEntityManager()->getEntity( loadedEntityName );
    if ( rootEntity != nullptr )
    {
        auto fc = std::find_if(
            rootEntity->getComponents().begin(),
            rootEntity->getComponents().end(),
            []( const auto& c ) { return ( c->getName().compare( 0, 7, "CAMERA_" ) == 0 ); } );
        if ( fc != rootEntity->getComponents().end() )
        {
            LOG( Ra::Core::Utils::logINFO ) << "Activating camera " << ( *fc )->getName();

            const auto systemEntity = Ra::Engine::Scene::SystemEntity::getInstance();
            systemEntity->removeComponent( "CAMERA_DEFAULT" );

            auto camera = static_cast<Ra::Engine::Scene::Camera*>( ( *fc ).get() );
            m_viewer->getCameraManipulator()->setCamera(
                camera->duplicate( systemEntity, "CAMERA_DEFAULT" ) );
        }
    }
}

void MainWindow::prepareDisplay() {
    if ( m_viewer->prepareDisplay() ) { emit frameUpdate(); }
}

void MainWindow::addControl( const QString& tabName, const QKeySequence& shortcut ) {
    auto name = tabName.toStdString();
    menuControl->addAction(
        tabName, [this, name]() { this->m_controlWindow->showTab( name ); }, shortcut );
}

void MainWindow::updateUi( Ra::Plugins::RadiumPluginInterface* plugin ) {
    // Add widget
    QString tabName;
    if ( plugin->doAddWidget( tabName ) )
    {
        QString shortcut = "Ctrl+" + tabName[0];
        addControl( tabName, tr( shortcut.toStdString().c_str() ) );
        m_controlWindow->addTab( plugin->getWidget(), tabName.toStdString() );
    }
}

void MainWindow::loadFile() {
    QString filter;
    QString allexts;
    for ( const auto& loader : Ra::Engine::RadiumEngine::getInstance()->getFileLoaders() )
    {
        QString exts;
        for ( const auto& e : loader->getFileExtensions() )
        {
            exts.append( QString::fromStdString( e ) + tr( " " ) );
        }
        allexts.append( exts + tr( " " ) );
        filter.append( QString::fromStdString( loader->name() ) + tr( " (" ) + exts + tr( ");;" ) );
    }
    // add a filter concetenatting all the supported extensions
    filter.prepend( tr( "Supported files (" ) + allexts + tr( ");;" ) );

    // remove the last ";;" of the string
    filter.remove( filter.size() - 2, 2 );

    QSettings settings;
    auto path     = settings.value( "files/load", QDir::homePath() ).toString();
    auto pathList = QFileDialog::getOpenFileNames( this, "Open Files", path, filter );

    if ( !pathList.empty() )
    {
        /* Remove existing scene before loading the new one */
        m_viewer->getCameraManipulator()->resetToDefaultCamera();
        m_selectionManager->clear();

        Ra::Engine::RadiumEngine::getInstance()->getEntityManager()->deleteEntities();
        fitCamera();
        // load the new scene
        settings.setValue( "files/load", pathList.front() );
        for ( const auto& file : pathList )
        {
            emit fileLoading( file );
        }

        auto romgr       = Ra::Engine::RadiumEngine::getInstance()->getRenderObjectManager();
        auto polyCount   = romgr->getNumFaces();
        auto vertexCount = romgr->getNumVertices();
        auto objectCount = romgr->getRenderObjectsCount();
        auto sceneStats  = std::make_tuple( objectCount, polyCount, vertexCount );
        m_controlWindow->setFileInfo( pathList.first().toStdString(), sceneStats );
        activateCamera( pathList.first().toStdString() );

        std::string loadedEntityName =
            Ra::Core::Utils::getBaseName( pathList.first().toStdString(), false );
        auto rootEntity = Ra::Engine::RadiumEngine::getInstance()->getEntityManager()->getEntity(
            loadedEntityName );
        if ( rootEntity != nullptr ) { emit entityAdded( rootEntity ); }
    }
}

void MainWindow::setROVisible( Ra::Core::Utils::Index roIndex, bool visible ) {
    emit roChanged( roIndex, visible );
}
void MainWindow::displayFileInfo() {
    m_controlWindow->showTab( "Information" );
}

void MainWindow::fitCamera() {
    auto aabb = Ra::Engine::RadiumEngine::getInstance()->computeSceneAabb();
    if ( aabb.isEmpty() ) { m_viewer->getCameraManipulator()->resetCamera(); }
    else
    { m_viewer->fitCameraToScene( aabb ); }
    emit frameUpdate();
}

void MainWindow::resetCamera() {
    if ( m_initialCamera )
    {
        const auto systemEntity = SystemEntity::getInstance();
        systemEntity->removeComponent( "CAMERA_DEFAULT" );

        auto c = m_initialCamera->duplicate( systemEntity, "CAMERA_DEFAULT" );
        c->resize( m_viewer->width(), m_viewer->height() );
        m_viewer->getCameraManipulator()->setCamera( c );
        emit frameUpdate();
    }
    else
    { fitCamera(); }
}

Ra::Gui::SelectionManager* MainWindow::getSelectionManager() {
    return m_selectionManager;
}

Ra::Gui::Timeline* MainWindow::getTimeline() {
    // mus return not null ?
    return m_timeline;
}

void MainWindow::ItemAdded( const ItemEntry& ent ) {
    m_sceneModel->addItem( ent );
}

void MainWindow::ItemRemoved( const ItemEntry& ent ) {
    m_sceneModel->removeItem( ent );
}

void MainWindow::configure() {
    QMessageBox::about(
        this, "Radium rendering application", "Configuration panel not yet implemented !" );
}

#ifdef SHOWTREEVIEW
void MainWindow::displayTreeView() {
    m_controlWindow->showTab( "Scene" );
}
#endif

void MainWindow::on_actionPlay_triggered( bool checked ) {
    Ra::Engine::RadiumEngine::getInstance()->play( checked );
    emit timeFlow( checked );
}

void MainWindow::on_actionStop_triggered() {
    on_actionPlay_triggered( false );
    Ra::Engine::RadiumEngine::getInstance()->resetTime();
    actionPlay->setChecked( false );
    emit frameUpdate();
}

void MainWindow::on_actionStep_triggered() {
    Ra::Engine::RadiumEngine::getInstance()->step();
    emit frameUpdate();
}

void MainWindow::gizmoShowNone() {
    m_viewer->getGizmoManager()->changeGizmoType( GizmoManager::NONE );
    emit frameUpdate();
}

void MainWindow::gizmoShowTranslate() {
    m_viewer->getGizmoManager()->changeGizmoType( GizmoManager::TRANSLATION );
    emit frameUpdate();
}

void MainWindow::gizmoShowRotate() {
    m_viewer->getGizmoManager()->changeGizmoType( GizmoManager::ROTATION );
    emit frameUpdate();
}

void MainWindow::gizmoShowScale() {
    m_viewer->getGizmoManager()->changeGizmoType( GizmoManager::SCALE );
    emit frameUpdate();
}

void MainWindow::onSelectionChanged( const QItemSelection& /*selected*/,
                                     const QItemSelection& /*deselected*/ ) {
    if ( m_selectionManager->hasSelection() )
    {
        const ItemEntry& ent = m_selectionManager->currentItem();
        emit selectedItem( ent );
        /*
         * @todo : complete this method to handle how events due to election changes
         * cf RadiumSandBox/Mainwindow
         */
    }
    else
    { emit selectedItem( ItemEntry() ); }
}

void MainWindow::timelinePlay( bool play ) {
    actionPlay->setChecked( play );
    if ( !m_lockTimeSystem )
    {
        on_actionPlay_triggered( play );
    }
}

void MainWindow::timelineGoTo( double t ) {
    if ( !m_lockTimeSystem )
    {
        Ra::Engine::RadiumEngine::getInstance()->setTime( Scalar( t ) );
        emit frameUpdate();
    }
}

void MainWindow::timelineStartChanged( double t ) {
    if ( !m_lockTimeSystem )
    {
        Ra::Engine::RadiumEngine::getInstance()->setStartTime( Scalar( t ) );
        emit frameUpdate();
    }
}

void MainWindow::timelineEndChanged( double t ) {
    if ( !m_lockTimeSystem )
    {
        Ra::Engine::RadiumEngine::getInstance()->setEndTime( Scalar( t ) );
        emit frameUpdate();
    }
}

void MainWindow::timelineSetPingPong( bool status ) {
    if ( !m_lockTimeSystem )
    {
        Ra::Engine::RadiumEngine::getInstance()->setForwardBackward( status );
        emit frameUpdate();
    }
}

void MainWindow::addRadiumSystemsUI() {
    // Register the Skeleton-based animation UI
    auto animSystem = static_cast<Ra::Engine::Scene::SkeletonBasedAnimationSystem*>(
        Ra::Engine::RadiumEngine::getInstance()->getSystem( "SkeletonBasedAnimationSystem" ) );
    auto skelAnimUI = new Ra::Gui::SkeletonBasedAnimationUI( animSystem, m_timeline );
    m_controlWindow->addTab( skelAnimUI, "Skeleton Animation" );
    addControl( "Skeleton Animation", tr( "Ctrl+A" ) );
    connect( skelAnimUI, &Ra::Gui::SkeletonBasedAnimationUI::askForUpdate, [this]() {
        emit frameUpdate();
    } );
    connect( this,
             &MainWindow::entityAdded,
             skelAnimUI,
             &Ra::Gui::SkeletonBasedAnimationUI::postLoadFile );
    connect( this,
             &MainWindow::selectedItem,
             skelAnimUI,
             &Ra::Gui::SkeletonBasedAnimationUI::selectionChanged );
}
} // namespace Mara