diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index d6b982b4..852528d8 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -444,7 +444,7 @@ void GameData::loadModelFile(const std::string& name) { } } -void GameData::loadModel(ModelID model) { +bool GameData::loadModel(ModelID model) { auto info = modelinfo[model].get(); /// @todo replace openFile with API for loading from CDIMAGE archives auto name = info->name; @@ -482,13 +482,13 @@ void GameData::loadModel(ModelID model) { if (!file) { logger->error("Data", "Failed to load model for " + std::to_string(model) + " [" + name + "]"); - return; + return false; } auto m = dffLoader.loadFromMemory(file); if (!m) { logger->error("Data", "Error loading model file for " + std::to_string(model)); - return; + return false; } /// @todo handle timeinfo models correctly. auto isSimple = info->type() == ModelDataType::SimpleInfo; @@ -509,6 +509,8 @@ void GameData::loadModel(ModelID model) { clump->setModel(m); /// @todo how is LOD handled for clump objects? } + + return true; } void GameData::loadIFP(const std::string& name) { diff --git a/rwengine/src/engine/GameData.hpp b/rwengine/src/engine/GameData.hpp index 2bd11daf..664655bd 100644 --- a/rwengine/src/engine/GameData.hpp +++ b/rwengine/src/engine/GameData.hpp @@ -150,7 +150,7 @@ public: /** * Loads and associates a model's data */ - void loadModel(ModelID model); + bool loadModel(ModelID model); /** * Loads an IFP file containing animations diff --git a/rwviewer/CMakeLists.txt b/rwviewer/CMakeLists.txt index 6c5feacd..26c08c3b 100644 --- a/rwviewer/CMakeLists.txt +++ b/rwviewer/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(rwviewer WIN32 views/ObjectViewer.cpp views/ModelViewer.cpp views/WorldViewer.cpp + views/ViewerInterface.cpp ViewerWidget.cpp ItemListModel.cpp diff --git a/rwviewer/ViewerWidget.cpp b/rwviewer/ViewerWidget.cpp index f4f26f6b..1b0168cc 100644 --- a/rwviewer/ViewerWidget.cpp +++ b/rwviewer/ViewerWidget.cpp @@ -1,35 +1,44 @@ #include "ViewerWidget.hpp" #include #include -#include -#include #include #include #include -#include #include #include #include #include -#include -ViewerWidget::ViewerWidget(QGLFormat g, QWidget* parent, - const QGLWidget* shareWidget, Qt::WindowFlags f) - : QGLWidget(g, parent, shareWidget, f) - , renderer(nullptr) - , gworld(nullptr) - , activeModel(nullptr) +constexpr float kViewFov = glm::radians(90.0f); + +namespace { +ViewCamera OrbitCamera (const glm::vec2& viewPort, const glm::vec2& viewAngles, + float viewDistance, glm::mat4& view, glm::mat4& proj) +{ + ViewCamera vc; + glm::vec3 eye(sin(viewAngles.x) * cos(viewAngles.y), + cos(viewAngles.x) * cos(viewAngles.y), sin(viewAngles.y)); + + vc.position = eye * viewDistance; + vc.frustum.aspectRatio = viewPort.x / viewPort.y; + proj = vc.frustum.projection(); + view = glm::lookAt(vc.position, {0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}); + vc.rotation = -glm::quat_cast(view); + vc.frustum.update(proj * view); + return vc; +} +} + +ViewerWidget::ViewerWidget(QOpenGLContext* context, QWindow* parent) + : QWindow(parent) + , context(context) , selectedFrame(nullptr) - , dummyObject(nullptr) - , currentObjectID(0) - , _lastModel(nullptr) - , canimation(nullptr) , viewDistance(1.f) , dragging(false) , moveFast(false) , _frameWidgetDraw(nullptr) , _frameWidgetGeom(nullptr) { - setFocusPolicy(Qt::StrongFocus); + setSurfaceType(OpenGLSurface); } struct WidgetVertex { @@ -43,12 +52,7 @@ std::vector widgetVerts = {{-.5f, 0.f, 0.f}, {.5f, 0.f, 0.f}, {0.f, -.5f, 0.f}, {0.f, .5f, 0.f}, {0.f, 0.f, -.5f}, {0.f, 0.f, .5f}}; -void ViewerWidget::initializeGL() { - QGLWidget::initializeGL(); - timer.setInterval(25); - connect(&timer, SIGNAL(timeout()), SLOT(updateGL())); - timer.start(); - +void ViewerWidget::initGL() { _frameWidgetDraw = new DrawBuffer; _frameWidgetDraw->setFaceType(GL_LINES); _frameWidgetGeom = new GeometryBuffer; @@ -64,88 +68,93 @@ void ViewerWidget::initializeGL() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } -void ViewerWidget::resizeGL(int w, int h) { - QGLWidget::resizeGL(w, h); - glViewport(0, 0, w, h); -} - -void ViewerWidget::paintGL() { - glClearColor(0.3f, 0.3f, 0.3f, 1.f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glViewport(0, 0, width(), height()); - - if (world() == nullptr) return; - - RW_CHECK(renderer != nullptr, "GameRenderer is null"); - auto& r = *renderer; - - r.setViewport(width(), height()); - - if (dummyObject && dummyObject->animator) { - dummyObject->animator->tick(1.f / 60.f); - } - - r.getRenderer()->invalidate(); - - glEnable(GL_DEPTH_TEST); - - glm::mat4 m(1.f); +void ViewerWidget::drawModel(GameRenderer& r, ClumpPtr& model) { + glm::mat4 view, proj; + const auto& vc = OrbitCamera({width(), height()}, + viewAngles, + viewDistance, + view, proj); r.getRenderer()->useProgram(r.worldProg.get()); + r.getRenderer()->setSceneParameters( + {proj, view, glm::vec4(0.15f), glm::vec4(0.7f), glm::vec4(1.f), + glm::vec4(0.f), 90.f, vc.frustum.far}); + model->getFrame()->updateHierarchyTransform(); - ViewCamera vc; + ObjectRenderer _renderer(world(), vc, 1.f, 0); + RenderList renders; + _renderer.renderClump(model.get(), glm::mat4(), nullptr, renders); + r.getRenderer()->drawBatched(renders); - float viewFov = glm::radians(45.f); + drawFrameWidget(model->getFrame().get()); + r.renderPostProcess(); +} - vc.frustum.far = 500.f; - vc.frustum.near = 0.1f; - vc.frustum.fov = viewFov; - vc.frustum.aspectRatio = width() / (height() * 1.f); +void ViewerWidget::drawObject(GameRenderer &r, GameObject *object) { + glm::mat4 view, proj; + const auto& vc = OrbitCamera({width(), height()}, + viewAngles, + viewDistance, + view, proj); - ClumpPtr model = activeModel; - if (model != _lastModel) { - _lastModel = model; - emit modelChanged(_lastModel); - } - - glm::vec3 eye(sin(viewAngles.x) * cos(viewAngles.y), - cos(viewAngles.x) * cos(viewAngles.y), sin(viewAngles.y)); - - if (model) { - model->getFrame()->updateHierarchyTransform(); - - // Ensure camera is still accurate - vc.position = eye * viewDistance; - glm::mat4 proj = vc.frustum.projection(); - glm::mat4 view = glm::lookAt(vc.position, glm::vec3(0.f, 0.f, 0.f), - glm::vec3(0.f, 0.f, 1.f)); - vc.rotation = -glm::quat_cast(view); - vc.frustum.update(proj * view); - - r.getRenderer()->setSceneParameters( + r.getRenderer()->useProgram(r.worldProg.get()); + r.getRenderer()->setSceneParameters( {proj, view, glm::vec4(0.15f), glm::vec4(0.7f), glm::vec4(1.f), glm::vec4(0.f), 90.f, vc.frustum.far}); - r.getRenderer()->invalidate(); + ObjectRenderer objectRenderer(world(), vc, 1.f, 0); + RenderList renders; + objectRenderer.buildRenderList(object, renders); + std::sort(renders.begin(), renders.end(), + [](const Renderer::RenderInstruction& a, + const Renderer::RenderInstruction& b) { + return a.sortKey < b.sortKey; + }); + r.getRenderer()->drawBatched(renders); + r.renderPostProcess(); +} - r.setupRender(); +void ViewerWidget::drawWorld(GameRenderer& r) { + ViewCamera vc; + vc.frustum.fov = kViewFov; + vc.frustum.far = 1000.f; + vc.frustum.near = 0.1f; + vc.position = viewPosition; + vc.rotation = glm::angleAxis(glm::half_pi() + viewAngles.x, + glm::vec3(0.f, 0.f, 1.f)) * + glm::angleAxis(viewAngles.y, glm::vec3(0.f, 1.f, 0.f)); + vc.frustum.aspectRatio = width() / (height() * 1.f); + r.renderWorld(world(), vc, 0.f); +} - ObjectRenderer renderer(world(), vc, 1.f, 0); - RenderList renders; - renderer.renderClump(model.get(), glm::mat4(), nullptr, renders); - r.getRenderer()->drawBatched(renders); +void ViewerWidget::paintGL() { + glViewport(0, 0, width(), height()); + glClearColor(0.3f, 0.3f, 0.3f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - drawFrameWidget(model->getFrame().get()); - r.renderPostProcess(); - } else if (world()->allObjects.size() > 0) { - vc.frustum.fov = glm::radians(90.f); - vc.frustum.far = 1000.f; - vc.position = viewPosition; - vc.rotation = glm::angleAxis(glm::half_pi() + viewAngles.x, - glm::vec3(0.f, 0.f, 1.f)) * - glm::angleAxis(viewAngles.y, glm::vec3(0.f, 1.f, 0.f)); - r.renderWorld(world(), vc, 0.f); + if (world() == nullptr) return; + + RW_CHECK(_renderer != nullptr, "GameRenderer is null"); + auto& r = *_renderer; + r.getRenderer()->invalidate(); + r.setViewport(width(), height()); + + glEnable(GL_DEPTH_TEST); + + + r.getRenderer()->invalidate(); + r.setupRender(); + + switch (_viewMode) { + case Mode::Model: + if (_model) drawModel(r, _model); + break; + case Mode::Object: + if (_object) drawObject(r, _object); + break; + case Mode::World: + drawWorld(r); + break; } } @@ -166,9 +175,9 @@ void ViewerWidget::drawFrameWidget(ModelFrame* f, const glm::mat4& m) { } dp.textures = {whiteTex}; - RW_CHECK(renderer != nullptr, "GameRenderer is null"); - if(renderer != nullptr) { - renderer->getRenderer()->drawArrays(thisM, _frameWidgetDraw, dp); + RW_CHECK(_renderer != nullptr, "GameRenderer is null"); + if(_renderer != nullptr) { + _renderer->getRenderer()->drawArrays(thisM, _frameWidgetDraw, dp); } for (auto c : f->getChildren()) { @@ -177,40 +186,51 @@ void ViewerWidget::drawFrameWidget(ModelFrame* f, const glm::mat4& m) { } GameWorld* ViewerWidget::world() { - return gworld; + return _world; } -void ViewerWidget::showObject(qint16 item) { - currentObjectID = item; +void ViewerWidget::showObject(quint16 item) { + RW_ASSERT(world()); + _viewMode = Mode::Object; + _objectID = item; - if (dummyObject) gworld->destroyObject(dummyObject); + if (_object) _world->destroyObject(_object); + _object = nullptr; auto def = world()->data->modelinfo[item].get(); + if (!def) { + return; + } - if (def) { - switch (def->type()) { - default: - dummyObject = gworld->createInstance(item, {}); - break; - case ModelDataType::PedInfo: - dummyObject = gworld->createPedestrian(item, {}); - break; - case ModelDataType::VehicleInfo: - dummyObject = gworld->createVehicle(item, {}); - break; - } + if (!world()->data->loadModel(item)) { + return; + } - RW_CHECK(dummyObject != nullptr, "Dummy Object is null"); - if (dummyObject != nullptr) { - activeModel = dummyObject->getModel(); - } + switch (def->type()) { + default: + _object = _world->createInstance(item, {}); + break; + case ModelDataType::PedInfo: + _object = _world->createPedestrian(item, {}); + break; + case ModelDataType::VehicleInfo: + _object = _world->createVehicle(item, {}); + break; + } + + RW_CHECK(_object != nullptr, "Dummy Object is null"); + + if (_object->getModel()) { + auto objectRadius = _object->getModel()->getBoundingRadius(); + viewDistance = objectRadius * 2; + viewAngles.x = glm::radians(-45.f); + viewAngles.y = glm::radians(22.5f); } } void ViewerWidget::showModel(ClumpPtr model) { - if (dummyObject) gworld->destroyObject(dummyObject); - dummyObject = nullptr; - activeModel = model; + _viewMode = Mode::Model; + _model = model; } void ViewerWidget::selectFrame(ModelFrame* frame) { @@ -218,12 +238,12 @@ void ViewerWidget::selectFrame(ModelFrame* frame) { } void ViewerWidget::exportModel() { +#if 0 QString toSv = QFileDialog::getSaveFileName( this, "Export Model", QDir::homePath(), "Model (*.DFF)"); if (toSv.size() == 0) return; -#if 0 auto it = world()->objectTypes.find(currentObjectID); if( it != world()->objectTypes.end() ) { for( auto& archive : world()->data.archives ) { @@ -240,14 +260,6 @@ void ViewerWidget::exportModel() { #endif } -void ViewerWidget::dataLoaded(GameWorld* world) { - gworld = world; -} - -void ViewerWidget::setRenderer(GameRenderer* render) { - renderer = render; -} - void ViewerWidget::keyPressEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Shift) moveFast = true; @@ -270,11 +282,11 @@ void ViewerWidget::keyReleaseEvent(QKeyEvent* e) { } ClumpPtr ViewerWidget::currentModel() const { - return activeModel; + return _model; } GameObject* ViewerWidget::currentObject() const { - return dummyObject; + return _object; } void ViewerWidget::mousePressEvent(QMouseEvent* e) { @@ -297,3 +309,42 @@ void ViewerWidget::mouseMoveEvent(QMouseEvent* e) { void ViewerWidget::wheelEvent(QWheelEvent* e) { viewDistance = qMax(viewDistance - e->angleDelta().y() / 240.f, 0.5f); } + +void ViewerWidget::gameLoaded(GameWorld *world, GameRenderer *renderer) { + _world = world; + _renderer = renderer; +} + +void ViewerWidget::renderNow() { + if (!isExposed()) { + return; + } + + context->makeCurrent(this); + + if (!initialised) { + initGL(); + initialised = true; + } + + paintGL(); + context->swapBuffers(this); + + requestUpdate(); +} + +bool ViewerWidget::event(QEvent *e) { + switch(e->type()) { + case QEvent::UpdateRequest: + renderNow(); + return true; + default: return QWindow::event(e); + } +} + +void ViewerWidget::exposeEvent(QExposeEvent *) { + if (isExposed()) { + requestUpdate(); + } +} + diff --git a/rwviewer/ViewerWidget.hpp b/rwviewer/ViewerWidget.hpp index f4cd9713..b4a581b2 100644 --- a/rwviewer/ViewerWidget.hpp +++ b/rwviewer/ViewerWidget.hpp @@ -1,6 +1,5 @@ -#pragma once -#ifndef _VIEWERWIDGET_HPP_ -#define _VIEWERWIDGET_HPP_ +#ifndef _RWVIEWER_VIEWERWIDGET_HPP_ +#define _RWVIEWER_VIEWERWIDGET_HPP_ #include #include #include @@ -13,27 +12,77 @@ // Prevent Qt from conflicting with glLoadGen #define GL_ARB_debug_output #define GL_KHR_debug -#include +#include class GameRenderer; class Clump; -class ViewerWidget : public QGLWidget { +class ViewerWidget : public QWindow { Q_OBJECT +public: + enum class Mode { + //! View an Object, \see showObject + Object, + //! View a DFF model, \see showModel + Model, + //! View loaded instances, \see showWorld(); + World, + }; - GameRenderer* renderer; + ViewerWidget(QOpenGLContext* context, QWindow* parent); - QString currentFile; + void initGL(); + void paintGL(); - QTimer timer; - GameWorld* gworld; + void renderNow(); + bool event(QEvent*) override; - ClumpPtr activeModel; - ModelFrame* selectedFrame; - GameObject* dummyObject; - quint16 currentObjectID; + void exposeEvent(QExposeEvent*) override; + + ClumpPtr currentModel() const; + GameObject* currentObject() const; + + GameWorld* world(); + + void setMode(Mode m) { + _viewMode = m; + } + + Mode currentMode() const { + return _viewMode; + } + +public slots: + void showObject(quint16 item); + void showModel(ClumpPtr model); + void selectFrame(ModelFrame* frame); + void exportModel(); + + void gameLoaded(GameWorld* world, GameRenderer* renderer); + +signals: + void fileOpened(const QString& file); + + void modelChanged(ClumpPtr model); + +protected: + void keyPressEvent(QKeyEvent*) override; + void keyReleaseEvent(QKeyEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void mouseReleaseEvent(QMouseEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + void wheelEvent(QWheelEvent*) override; + + Mode _viewMode = Mode::World; + + QOpenGLContext* context; + GameWorld* _world = nullptr; + GameRenderer* _renderer = nullptr; + + ClumpPtr _model; + ModelFrame* selectedFrame = nullptr; + GameObject* _object = nullptr; + quint16 _objectID = 0; - ClumpPtr _lastModel; - Animation* canimation; float viewDistance; glm::vec2 viewAngles; @@ -49,47 +98,12 @@ class ViewerWidget : public QGLWidget { GLuint whiteTex; void drawFrameWidget(ModelFrame* f, const glm::mat4& = glm::mat4(1.f)); + bool initialised = false; -public: - ViewerWidget(QGLFormat g, QWidget* parent = 0, - const QGLWidget* shareWidget = 0, Qt::WindowFlags f = 0); + void drawModel(GameRenderer& r, ClumpPtr& model); + void drawObject(GameRenderer& r, GameObject* object); + void drawWorld(GameRenderer& r); - virtual void initializeGL(); - - virtual void resizeGL(int w, int h); - - virtual void paintGL(); - - ClumpPtr currentModel() const; - GameObject* currentObject() const; - - GameWorld* world(); - -public slots: - - void showObject(qint16 item); - void showModel(ClumpPtr model); - void selectFrame(ModelFrame* frame); - - void exportModel(); - - void dataLoaded(GameWorld* world); - - void setRenderer(GameRenderer* renderer); - -signals: - - void fileOpened(const QString& file); - - void modelChanged(ClumpPtr model); - -protected: - void keyPressEvent(QKeyEvent*) override; - void keyReleaseEvent(QKeyEvent*) override; - void mousePressEvent(QMouseEvent*) override; - void mouseReleaseEvent(QMouseEvent*) override; - void mouseMoveEvent(QMouseEvent*) override; - void wheelEvent(QWheelEvent*) override; }; #endif diff --git a/rwviewer/ViewerWindow.cpp b/rwviewer/ViewerWindow.cpp index 9b198c9b..687d3822 100644 --- a/rwviewer/ViewerWindow.cpp +++ b/rwviewer/ViewerWindow.cpp @@ -9,14 +9,12 @@ #include #include -#include #include #include -#include #include #include -#include -#include +#include +#include static int MaxRecentGames = 5; @@ -25,8 +23,16 @@ ViewerWindow::ViewerWindow(QWidget* parent, Qt::WindowFlags flags) , gameData(nullptr) , gameWorld(nullptr) , renderer(nullptr) { + show(); setMinimumSize(640, 480); + createMenus(); + if (!setupEngine()) { + return; + } + createDefaultViews(); +} +void ViewerWindow::createMenus() { QMenuBar* mb = this->menuBar(); QMenu* file = mb->addMenu("&File"); @@ -45,84 +51,52 @@ ViewerWindow::ViewerWindow(QWidget* parent, Qt::WindowFlags flags) connect(ex, SIGNAL(triggered()), QApplication::instance(), SLOT(closeAllWindows())); - //----------------------- View Mode setup - - QGLFormat glFormat; - glFormat.setVersion(3, 3); - glFormat.setProfile(QGLFormat::CoreProfile); - - viewerWidget = new ViewerWidget(glFormat); - viewerWidget->context()->makeCurrent(); - connect(this, SIGNAL(loadedData(GameWorld*)), viewerWidget, - SLOT(dataLoaded(GameWorld*))); - - //------------- Object Viewer - m_views[ViewMode::Object] = new ObjectViewer(viewerWidget); - m_viewNames[ViewMode::Object] = "Objects"; - - //------------- Model Viewer - m_views[ViewMode::Model] = new ModelViewer(viewerWidget); - m_viewNames[ViewMode::Model] = "Model"; - - //------------- World Viewer - m_views[ViewMode::World] = new WorldViewer(viewerWidget); - m_viewNames[ViewMode::World] = "World"; - - //------------- display mode switching - viewSwitcher = new QStackedWidget; - auto signalMapper = new QSignalMapper(this); - auto switchPanel = new QVBoxLayout(); - int i = 0; - for (auto viewer : m_views) { - viewSwitcher->addWidget(viewer); - connect(this, SIGNAL(loadedData(GameWorld*)), viewer, - SLOT(showData(GameWorld*))); - - auto viewerButton = new QPushButton(m_viewNames[i].c_str()); - signalMapper->setMapping(m_views[i], i); - signalMapper->setMapping(viewerButton, i); - connect(viewerButton, SIGNAL(clicked()), signalMapper, SLOT(map())); - switchPanel->addWidget(viewerButton); - i++; - } - // Map world viewer loading placements to switch to the world viewer - connect(m_views[ViewMode::World], SIGNAL(placementsLoaded(QString)), - signalMapper, SLOT(map())); - - switchView(ViewMode::Object); - - connect(m_views[ViewMode::Object], SIGNAL(showObjectModel(uint16_t)), this, - SLOT(showObjectModel(uint16_t))); - connect(m_views[ViewMode::Object], SIGNAL(showObjectModel(uint16_t)), - m_views[ViewMode::Model], SLOT(showObject(uint16_t))); - connect(this, SIGNAL(loadAnimations(QString)), m_views[ViewMode::Model], - SLOT(loadAnimations(QString))); - - connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(switchView(int))); - connect(signalMapper, SIGNAL(mapped(int)), viewSwitcher, - SLOT(setCurrentIndex(int))); - - switchPanel->addStretch(); - auto mainlayout = new QHBoxLayout(); - mainlayout->addLayout(switchPanel); - mainlayout->addWidget(viewSwitcher); - auto mainwidget = new QWidget(); - mainwidget->setLayout(mainlayout); - mb->addMenu("&Data"); - QMenu* anim = mb->addMenu("&Animation"); - anim->addAction("Load &Animations", this, SLOT(openAnimations())); - - QMenu* map = mb->addMenu("&Map"); - map->addAction("Load IPL", m_views[ViewMode::World], - SLOT(loadPlacements())); - - this->setCentralWidget(mainwidget); - updateRecentGames(); } +bool ViewerWindow::setupEngine() { + QSurfaceFormat format = windowHandle()->format(); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setVersion(3,3); + context_ = new QOpenGLContext(this); + context_->setShareContext(QOpenGLContext::globalShareContext()); + context_->setFormat(format); + + hiddenSurface = new QOffscreenSurface(windowHandle()->screen()); + hiddenSurface->setFormat(format); + hiddenSurface->create(); + + if (!context_->create()) { + QMessageBox::critical(this, "OpenGL Failure", + "Failed to create OpenGL context"); + QApplication::exit(1); + return false; + } + + return true; +} + +void ViewerWindow::createDefaultViews() { + views = new QTabWidget(this); + + auto objectView = new ObjectViewer(this); + views->addTab(objectView, "Objects"); + connect(this, &ViewerWindow::gameLoaded, objectView, &ObjectViewer::showData); + + auto modelView = new ModelViewer(this); + views->addTab(modelView, "Model"); + connect(this, &ViewerWindow::gameLoaded, modelView, &ModelViewer::showData); + connect(objectView, &ObjectViewer::showObjectModel, modelView, &ModelViewer::showObject); + + auto worldView = new WorldViewer(this); + views->addTab(worldView, "World"); + connect(this, &ViewerWindow::gameLoaded, worldView, &WorldViewer::showData); + + setCentralWidget(views); +} + void ViewerWindow::showEvent(QShowEvent*) { static bool first = true; if (first) { @@ -140,14 +114,6 @@ void ViewerWindow::closeEvent(QCloseEvent* event) { QMainWindow::closeEvent(event); } -void ViewerWindow::openAnimations() { - QFileDialog dialog(this, "Open Animations", QDir::homePath(), - "IFP Animations (*.ifp)"); - if (dialog.exec()) { - loadAnimations(dialog.selectedFiles()[0]); - } -} - void ViewerWindow::loadGame() { QString dir = QFileDialog::getExistingDirectory( this, tr("Open Directory"), QDir::homePath(), @@ -158,19 +124,26 @@ void ViewerWindow::loadGame() { void ViewerWindow::loadGame(const QString& path) { QDir gameDir(path); - - if (gameDir.exists() && path.size() > 0) { - gameData = - new GameData(&engineLog, gameDir.absolutePath().toStdString()); - gameWorld = new GameWorld(&engineLog, gameData); - renderer = new GameRenderer(&engineLog, gameData); - gameWorld->state = new GameState; - viewerWidget->setRenderer(renderer); - - gameWorld->data->load(); - - loadedData(gameWorld); + if (path.isEmpty()) { + return; } + if (!gameDir.exists()) { + QMessageBox::critical(this, "Error", "The requested path doesn't exist"); + return; + } + + if (!makeCurrent()) { + return; + } + + gameData = std::make_unique(&engineLog, gameDir.absolutePath().toStdString()); + gameWorld = std::make_unique(&engineLog, gameData.get()); + renderer = std::make_unique(&engineLog, gameData.get()); + gameWorld->state = new GameState; + + gameWorld->data->load(); + + gameLoaded(gameWorld.get(), renderer.get()); QSettings settings("OpenRW", "rwviewer"); QStringList recent = settings.value("recentGames").toStringList(); @@ -189,19 +162,8 @@ void ViewerWindow::openRecent() { } } -void ViewerWindow::switchView(int mode) { - if (mode < int(m_views.size())) { - m_views[mode]->setViewerWidget(viewerWidget); - } else { - RW_ERROR("Unhandled view mode" << mode); - } -} - void ViewerWindow::showObjectModel(uint16_t) { - // Switch to the model viewer - switchView(ViewMode::Model); - viewSwitcher->setCurrentIndex( - viewSwitcher->indexOf(m_views[ViewMode::Model])); +#warning implement me } void ViewerWindow::updateRecentGames() { @@ -221,3 +183,22 @@ void ViewerWindow::updateRecentGames() { recentSep->setVisible(recent.size() > 0); } + +ViewerWindow::~ViewerWindow() { + +} + +bool ViewerWindow::makeCurrent() { + if (!context_->makeCurrent(hiddenSurface)) { + QMessageBox::critical(this, "OpenGL", "makeCurrent failed"); + QApplication::exit(1); + return false; + } + return true; +} + +ViewerWidget *ViewerWindow::createViewer() { + auto view = new ViewerWidget(context_, windowHandle()); + connect(this, &ViewerWindow::gameLoaded, view, &ViewerWidget::gameLoaded); + return view; +} diff --git a/rwviewer/ViewerWindow.hpp b/rwviewer/ViewerWindow.hpp index fb81e962..bb9d61f3 100644 --- a/rwviewer/ViewerWindow.hpp +++ b/rwviewer/ViewerWindow.hpp @@ -1,4 +1,3 @@ -#pragma once #ifndef _VIEWERWINDOW_HPP_ #define _VIEWERWINDOW_HPP_ #include @@ -6,76 +5,60 @@ #include #include -#include +#include #include +#include +#include -#include +#include class ViewerWidget; class ViewerInterface; class GameRenderer; -class QGLContext; class ViewerWindow : public QMainWindow { Q_OBJECT - - enum ViewMode { Object = 0, Model = 1, World = 2, _Count }; + QOpenGLContext* context_; + QOffscreenSurface* hiddenSurface; + QTabWidget* views; Logger engineLog; - GameData* gameData; - GameWorld* gameWorld; - GameRenderer* renderer; - GameState* state; - - /** Contains the OGL context */ - ViewerWidget* viewerWidget; - - std::array m_views; - std::array m_viewNames; - - QStackedWidget* viewSwitcher; - - QGLContext* context; + std::unique_ptr gameData; + std::unique_ptr gameWorld; + std::unique_ptr renderer; public: ViewerWindow(QWidget* parent = 0, Qt::WindowFlags flags = 0); - - /** - * @brief openGame Loads a game's dat file. - * @param datFile - */ - void openGame(const QString& datFile); + ~ViewerWindow(); virtual void showEvent(QShowEvent*); - virtual void closeEvent(QCloseEvent*); + ViewerWidget* createViewer(); + public slots: - - void openAnimations(); - void loadGame(); void loadGame(const QString& path); signals: - - void loadedData(GameWorld* world); - void loadAnimations(const QString& file); + void gameLoaded(GameWorld*, GameRenderer*); private slots: - void openRecent(); - - void switchView(int mode); - void showObjectModel(uint16_t object); private: QList recentGames; QAction* recentSep; void updateRecentGames(); + + void createMenus(); + bool setupEngine(); + void createDefaultViews(); + + bool makeCurrent(); }; #endif diff --git a/rwviewer/main.cpp b/rwviewer/main.cpp index 21417320..e29a23da 100644 --- a/rwviewer/main.cpp +++ b/rwviewer/main.cpp @@ -1,12 +1,11 @@ #include -#include #include "ViewerWindow.hpp" int main(int argc, char *argv[]) { + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); ViewerWindow viewer; - viewer.show(); return app.exec(); } diff --git a/rwviewer/views/ModelViewer.cpp b/rwviewer/views/ModelViewer.cpp index a5d1b682..7b8ecfb7 100644 --- a/rwviewer/views/ModelViewer.cpp +++ b/rwviewer/views/ModelViewer.cpp @@ -6,37 +6,22 @@ #include #include "ViewerWidget.hpp" -ModelViewer::ModelViewer(ViewerWidget* viewer, QWidget* parent, - Qt::WindowFlags f) +ModelViewer::ModelViewer(QWidget* parent, Qt::WindowFlags f) : ViewerInterface(parent, f), viewing(nullptr) { mainSplit = new QSplitter; mainLayout = new QVBoxLayout; - viewerWidget = viewer; - viewerWidget->setMinimumSize(250, 250); - - animationWidget = new AnimationListWidget; - connect(animationWidget, SIGNAL(selectedAnimationChanged(Animation*)), - SLOT(playAnimation(Animation*))); - frames = new ModelFramesWidget; frames->setMaximumWidth(300); + viewerWidget = createViewer(); + viewerWidget->setMode(ViewerWidget::Mode::Model); + mainSplit->addWidget(frames); - mainSplit->addWidget(animationWidget); + mainSplit->addWidget(QWidget::createWindowContainer(viewerWidget)); mainLayout->addWidget(mainSplit); - this->setLayout(mainLayout); - - connect(frames, SIGNAL(selectedFrameChanged(ModelFrame*)), viewerWidget, - SLOT(selectFrame(ModelFrame*))); - setViewerWidget(viewerWidget); -} - -void ModelViewer::setViewerWidget(ViewerWidget* widget) { - viewerWidget = widget; - mainSplit->addWidget(viewerWidget); - showModel(viewing); + setLayout(mainLayout); } void ModelViewer::showModel(ClumpPtr model) { @@ -46,35 +31,27 @@ void ModelViewer::showModel(ClumpPtr model) { } void ModelViewer::showObject(uint16_t object) { - viewerWidget->showObject(object); - viewing = viewerWidget->currentModel(); - frames->setModel(viewing); -} - -void ModelViewer::loadAnimations(const QString& file) { - std::ifstream dfile(file.toStdString().c_str(), std::ios_base::binary); - AnimationList anims; - - if (dfile.is_open()) { - dfile.seekg(0, std::ios_base::end); - size_t length = dfile.tellg(); - dfile.seekg(0); - char* file = new char[length]; - dfile.read(file, length); - - LoaderIFP loader; - if (loader.loadFromMemory(file)) { - for (auto& f : loader.animations) { - anims.push_back(f); - } - } - - delete[] file; + auto def = world()->data->modelinfo[object].get(); + if (!def) { + return; } - animationWidget->setAnimations(anims); + auto modelName = def->name + ".dff"; + auto textureName = def->name + ".txd"; + auto textures = world()->data->loadTextureArchive(textureName); + + LoaderDFF dffLoader; + dffLoader.setTextureLookupCallback( + [&](const std::string& texture, const std::string&) { + return textures.at(texture); + }); + + auto file = world()->data->index.openFile(modelName); + if (!file) { + RW_ERROR("Couldn't load " << modelName); + return; + } + showModel(dffLoader.loadFromMemory(file)); } -void ModelViewer::playAnimation(AnimationPtr anim) { - viewerWidget->currentObject()->animator->playAnimation(0, anim, 1.f, true); -} + diff --git a/rwviewer/views/ModelViewer.hpp b/rwviewer/views/ModelViewer.hpp index 83dbe65e..02acc42c 100644 --- a/rwviewer/views/ModelViewer.hpp +++ b/rwviewer/views/ModelViewer.hpp @@ -3,7 +3,6 @@ #define _MODELVIEWER_HPP_ #include #include -#include "AnimationListWidget.hpp" #include "ViewerInterface.hpp" @@ -16,7 +15,6 @@ class ViewerWidget; class Clump; class ModelFramesWidget; -class Animation; class ModelViewer : public ViewerInterface { Q_OBJECT @@ -29,14 +27,8 @@ class ModelViewer : public ViewerInterface { ModelFramesWidget* frames; - AnimationList loadedAnimations; - AnimationListWidget* animationWidget; - public: - ModelViewer(ViewerWidget* viewer = 0, QWidget* parent = 0, - Qt::WindowFlags f = 0); - - void setViewerWidget(ViewerWidget* widget) override; + ModelViewer(QWidget* parent = 0, Qt::WindowFlags f = 0); public slots: @@ -49,9 +41,6 @@ public slots: * Display a game object's model */ void showObject(uint16_t object); - - void loadAnimations(const QString& file); - void playAnimation(AnimationPtr anim); }; #endif diff --git a/rwviewer/views/ObjectViewer.cpp b/rwviewer/views/ObjectViewer.cpp index 0e81dae3..e639bf0b 100644 --- a/rwviewer/views/ObjectViewer.cpp +++ b/rwviewer/views/ObjectViewer.cpp @@ -1,16 +1,112 @@ #include "ObjectViewer.hpp" -#include -#include #include -#include "ViewerWidget.hpp" +#include -ObjectViewer::ObjectViewer(ViewerWidget* viewer, QWidget* parent, - Qt::WindowFlags f) +#include +#include +#include + + +class ObjectSearchModel : public QSortFilterProxyModel { +public: + ObjectSearchModel(QObject* parent) + : QSortFilterProxyModel(parent) { } + + void showCARS(bool cars) { + _showCars = cars; + invalidateFilter(); + } + + void showOBJS(bool objs) { + _showMisc = objs; + invalidateFilter(); + } + + void showPEDS(bool peds) { + _showPeds = peds; + invalidateFilter(); + } + + void filterName(const QString& name) { + _name = name.toStdString(); + invalidateFilter(); + } + + bool filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const override + { + auto index0 = sourceModel()->index(sourceRow, 0, sourceParent); + const auto& model = _data->modelinfo.at(index0.internalId()); + + switch (model->type()) { + case ModelDataType::VehicleInfo: + if (!_showCars) + return false; + break; + case ModelDataType::PedInfo: + if (!_showPeds) + return false; + break; + default: + if (!_showMisc) + return false; + break; + } + + return !(!_name.empty() && model->name.find(_name) == std::string::npos); + } + + void setModel(ObjectListModel* model) { + _data = model->gameData(); + QSortFilterProxyModel::setSourceModel(model); + } + + GameData* _data = nullptr; +private: + bool _showPeds = true; + bool _showMisc = true; + bool _showCars = true; + + std::string _name; +}; + +namespace { +QLayout* searchControls(ObjectSearchModel* search) { + auto bar = new QHBoxLayout; + + auto searchBox = new QLineEdit; + searchBox->setPlaceholderText("Search"); + QObject::connect(searchBox, &QLineEdit::textChanged, search, &ObjectSearchModel::filterName); + + auto cars = new QCheckBox; + cars->setText("CARS"); + cars->setChecked(true); + QObject::connect(cars, &QCheckBox::clicked, search, &ObjectSearchModel::showCARS ); + auto peds = new QCheckBox; + peds->setText("PEDS"); + peds->setChecked(true); + QObject::connect(peds, &QCheckBox::clicked, search, &ObjectSearchModel::showPEDS ); + auto misc = new QCheckBox; + misc->setText("Misc"); + misc->setChecked(true); + QObject::connect(misc, &QCheckBox::clicked, search, &ObjectSearchModel::showOBJS ); + + bar->addWidget(searchBox, 1); + bar->addWidget(cars); + bar->addWidget(peds); + bar->addWidget(misc); + + return bar; +} +} + +ObjectViewer::ObjectViewer(QWidget* parent, Qt::WindowFlags f) : ViewerInterface(parent, f) { - mainLayout = new QHBoxLayout; + mainLayout = new QHBoxLayout(this); + + auto leftLayout = new QVBoxLayout; objectList = new QTableView; - objectMenu = new QMenu(objectList); objectList->setContextMenuPolicy(Qt::CustomContextMenu); auto viewModelAction = new QAction("View Model", objectMenu); @@ -18,11 +114,22 @@ ObjectViewer::ObjectViewer(ViewerWidget* viewer, QWidget* parent, connect(viewModelAction, SIGNAL(triggered()), this, SLOT(menuViewModel())); connect(objectList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenu(QPoint))); + filterModel = new ObjectSearchModel(this); + objectList->setModel(filterModel); + objectList->setColumnWidth(0, 50); + objectList->setColumnWidth(1, 150); + objectList->setColumnWidth(2, 200); + objectList->setSortingEnabled(true); + connect(objectList->selectionModel(), + SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, + SLOT(showItem(QModelIndex))); - mainLayout->addWidget(objectList); + leftLayout->addLayout(searchControls(filterModel)); + leftLayout->addWidget(objectList); + mainLayout->addLayout(leftLayout, 6); - previewWidget = viewer; - previewWidget->setMinimumSize(250, 250); + previewWidget = createViewer(); + previewWidget->setMode(ViewerWidget::Mode::Object); infoLayout = new QGridLayout; @@ -35,29 +142,23 @@ ObjectViewer::ObjectViewer(ViewerWidget* viewer, QWidget* parent, infoLayout->addWidget(previewClass, 2, 1); infoLayout->addWidget(new QLabel("Model"), 3, 0); infoLayout->addWidget(previewModel, 3, 1); + infoLayout->addWidget(QWidget::createWindowContainer(previewWidget), 0, 0, 1, 2); + infoLayout->setRowStretch(0, 1); - mainLayout->addLayout(infoLayout); - - this->setLayout(mainLayout); - - setViewerWidget(previewWidget); -} - -void ObjectViewer::setViewerWidget(ViewerWidget* widget) { - // widgetLayout->removeWidget(previewWidget); - previewWidget = widget; - infoLayout->addWidget(previewWidget, 0, 0, 1, 2); + mainLayout->addLayout(infoLayout, 4); + setLayout(mainLayout); } void ObjectViewer::worldChanged() { - if (objectList->model()) { - delete objectList->model(); + if (filterModel->sourceModel()) { + delete filterModel->sourceModel(); } - objectList->setModel(new ObjectListModel(world()->data, objectList)); - connect(objectList->selectionModel(), - SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, - SLOT(showItem(QModelIndex))); + auto newModel = new ObjectListModel(world()->data, this); + filterModel->setModel(newModel); + objectList->sortByColumn(0, Qt::AscendingOrder); + + objectList->resizeColumnsToContents(); } void ObjectViewer::showItem(qint16 item) { @@ -73,7 +174,8 @@ void ObjectViewer::showItem(qint16 item) { } void ObjectViewer::showItem(QModelIndex model) { - showItem(model.internalId()); + auto source = filterModel->mapToSource(model); + showItem(source.internalId()); } void ObjectViewer::onCustomContextMenu(const QPoint& p) { @@ -85,7 +187,7 @@ void ObjectViewer::onCustomContextMenu(const QPoint& p) { void ObjectViewer::menuViewModel() { if (contextMenuIndex.isValid()) { - auto id = contextMenuIndex.internalId(); - showObjectModel(id); + auto source = filterModel->mapToSource(contextMenuIndex); + showObjectModel(source.internalId()); } } diff --git a/rwviewer/views/ObjectViewer.hpp b/rwviewer/views/ObjectViewer.hpp index f7418cf4..94bb2b53 100644 --- a/rwviewer/views/ObjectViewer.hpp +++ b/rwviewer/views/ObjectViewer.hpp @@ -1,4 +1,3 @@ -#pragma once #ifndef _OBJECTVIEWER_HPP_ #define _OBJECTVIEWER_HPP_ #include @@ -10,10 +9,13 @@ #include #include #include +#include class ViewerWidget; class Clump; +class ObjectSearchModel; + class ObjectViewer : public ViewerInterface { Q_OBJECT @@ -29,23 +31,18 @@ class ObjectViewer : public ViewerInterface { QMenu* objectMenu; QModelIndex contextMenuIndex; -public: - ObjectViewer(ViewerWidget* viewer = 0, QWidget* parent = 0, - Qt::WindowFlags f = 0); + ObjectSearchModel* filterModel; - void setViewerWidget(ViewerWidget* widget); +public: + ObjectViewer(QWidget* parent = 0, Qt::WindowFlags f = 0); protected: void worldChanged() override; signals: - - void modelChanged(Clump* model); - void showObjectModel(uint16_t object); public slots: - void showItem(qint16 item); private slots: diff --git a/rwviewer/views/ViewerInterface.cpp b/rwviewer/views/ViewerInterface.cpp new file mode 100644 index 00000000..87b7f51c --- /dev/null +++ b/rwviewer/views/ViewerInterface.cpp @@ -0,0 +1,7 @@ +#include "ViewerInterface.hpp" + +#include "ViewerWindow.hpp" + +ViewerWidget* ViewerInterface::createViewer() { + return static_cast(window())->createViewer(); +} \ No newline at end of file diff --git a/rwviewer/views/ViewerInterface.hpp b/rwviewer/views/ViewerInterface.hpp index e96a9cb1..dc49e3b3 100644 --- a/rwviewer/views/ViewerInterface.hpp +++ b/rwviewer/views/ViewerInterface.hpp @@ -13,19 +13,22 @@ public: : QWidget(parent, f), m_world(nullptr) { } - virtual void setViewerWidget(ViewerWidget* widget) = 0; - GameWorld* world() { return m_world; } protected: - virtual void worldChanged() { - } + virtual void worldChanged() {} + + ViewerWidget* createViewer(); + +signals: + void gameLoaded(GameWorld*, GameRenderer*); public slots: - void showData(GameWorld* world) { + void showData(GameWorld* world, GameRenderer* renderer) { m_world = world; + gameLoaded(world, renderer); worldChanged(); } diff --git a/rwviewer/views/WorldViewer.cpp b/rwviewer/views/WorldViewer.cpp index e27e1ce2..35d61ecd 100644 --- a/rwviewer/views/WorldViewer.cpp +++ b/rwviewer/views/WorldViewer.cpp @@ -3,22 +3,13 @@ #include -WorldViewer::WorldViewer(ViewerWidget* viewer, QWidget* parent, - Qt::WindowFlags f) +WorldViewer::WorldViewer(QWidget* parent, Qt::WindowFlags f) : ViewerInterface(parent, f) { mainLayout = new QVBoxLayout; - viewerWidget = viewer; - viewerWidget->setMinimumSize(250, 250); + mainLayout->addWidget(QWidget::createWindowContainer(createViewer())); - this->setLayout(mainLayout); -} - -void WorldViewer::setViewerWidget(ViewerWidget* widget) { - viewerWidget = widget; - // Clear the active model - widget->showModel(nullptr); - mainLayout->addWidget(viewerWidget); + setLayout(mainLayout); } void WorldViewer::loadPlacements(const QString& file) { diff --git a/rwviewer/views/WorldViewer.hpp b/rwviewer/views/WorldViewer.hpp index fdbe8f93..c988d4f1 100644 --- a/rwviewer/views/WorldViewer.hpp +++ b/rwviewer/views/WorldViewer.hpp @@ -18,10 +18,7 @@ class WorldViewer : public ViewerInterface { ViewerWidget* viewerWidget; public: - WorldViewer(ViewerWidget* viewer = 0, QWidget* parent = 0, - Qt::WindowFlags f = 0); - - void setViewerWidget(ViewerWidget* widget) override; + WorldViewer(QWidget* parent = 0, Qt::WindowFlags f = 0); signals: void placementsLoaded(const QString& file);