From 47d612eb8fd8850f193bdb606789920ec1462500 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 9 Jul 2023 10:54:27 -0400 Subject: [PATCH] Add file cache and concurrent item loading to speed up mdlviewer Instead of extracting item sheets one at a time, it's now done on multiple threads. Loading gear is now faster since reused files are cached, switching between races is still wasteful, but it's a good enough improvement for now. --- CMakeLists.txt | 2 +- common/CMakeLists.txt | 6 +- common/include/filecache.h | 18 ++ common/src/filecache.cpp | 16 ++ mdlviewer/CMakeLists.txt | 1 + mdlviewer/include/fullmodelviewer.h | 3 +- mdlviewer/include/gearlistmodel.h | 9 +- mdlviewer/include/gearview.h | 4 +- mdlviewer/include/mainwindow.h | 2 + mdlviewer/include/singlegearview.h | 3 +- mdlviewer/src/fullmodelviewer.cpp | 4 +- mdlviewer/src/gearlistmodel.cpp | 101 ++++++---- mdlviewer/src/gearview.cpp | 44 ++--- mdlviewer/src/mainwindow.cpp | 7 +- mdlviewer/src/singlegearview.cpp | 5 +- parts/mdl/CMakeLists.txt | 2 +- parts/mdl/mdlpart.cpp | 287 ++++++++++++++-------------- parts/mdl/mdlpart.h | 73 +++---- 18 files changed, 322 insertions(+), 265 deletions(-) create mode 100644 common/include/filecache.h create mode 100644 common/src/filecache.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0107a25..0f4477d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) -find_package(Qt5 COMPONENTS Core Widgets CONFIG REQUIRED) +find_package(Qt5 COMPONENTS Core Widgets Concurrent CONFIG REQUIRED) include(FetchContent) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 572e21f..7a645d3 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,12 +1,14 @@ add_library(NovusCommon STATIC) target_sources(NovusCommon PRIVATE include/aboutwindow.h - src/aboutwindow.cpp) + include/filecache.h + src/aboutwindow.cpp + src/filecache.cpp) target_include_directories(NovusCommon PUBLIC include PRIVATE ${CMAKE_BINARY_DIR}) -target_link_libraries(NovusCommon PUBLIC Qt5::Core Qt5::Widgets) +target_link_libraries(NovusCommon PUBLIC Qt5::Core Qt5::Widgets physis) # meant for including the license text file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE LICENSE_TXT) diff --git a/common/include/filecache.h b/common/include/filecache.h new file mode 100644 index 0000000..90a5d30 --- /dev/null +++ b/common/include/filecache.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +struct GameData; + +class FileCache { +public: + explicit FileCache(GameData& data); + + physis_Buffer& lookupFile(const QString& path); + +private: + QMap cachedBuffers; + GameData& data; +}; \ No newline at end of file diff --git a/common/src/filecache.cpp b/common/src/filecache.cpp new file mode 100644 index 0000000..60563fa --- /dev/null +++ b/common/src/filecache.cpp @@ -0,0 +1,16 @@ +#include "filecache.h" + +#include + +FileCache::FileCache(GameData& data) : data(data) { + +} + +physis_Buffer& FileCache::lookupFile(const QString& path) { + if (!cachedBuffers.contains(path)) { + cachedBuffers[path] = physis_gamedata_extract_file(&data, path.toStdString().c_str()); + } + + return cachedBuffers[path]; +} + diff --git a/mdlviewer/CMakeLists.txt b/mdlviewer/CMakeLists.txt index c6c5436..168c8a5 100644 --- a/mdlviewer/CMakeLists.txt +++ b/mdlviewer/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(mdlviewer PUBLIC ${LIBRARIES} Qt5::Core Qt5::Widgets + Qt5::Concurrent renderer assimp::assimp magic_enum diff --git a/mdlviewer/include/fullmodelviewer.h b/mdlviewer/include/fullmodelviewer.h index 6d667b6..dd68293 100644 --- a/mdlviewer/include/fullmodelviewer.h +++ b/mdlviewer/include/fullmodelviewer.h @@ -6,11 +6,12 @@ #include "gearview.h" struct GameData; +class FileCache; class FullModelViewer : public QWidget { Q_OBJECT public: - explicit FullModelViewer(GameData* data); + explicit FullModelViewer(GameData* data, FileCache& cache); Q_SIGNALS: void gearChanged(); diff --git a/mdlviewer/include/gearlistmodel.h b/mdlviewer/include/gearlistmodel.h index 0b768e7..cfd7833 100644 --- a/mdlviewer/include/gearlistmodel.h +++ b/mdlviewer/include/gearlistmodel.h @@ -1,7 +1,9 @@ #pragma once -#include "gearview.h" #include +#include + +#include "gearview.h" enum class TreeType { Root, @@ -37,6 +39,11 @@ public: std::optional getGearFromIndex(const QModelIndex& index); private: + void exdFinished(int index); + void finished(); + + QFutureWatcher* exdFuture; + std::vector gears; QStringList slotNames; diff --git a/mdlviewer/include/gearview.h b/mdlviewer/include/gearview.h index 473bc74..3e4bb09 100644 --- a/mdlviewer/include/gearview.h +++ b/mdlviewer/include/gearview.h @@ -1,5 +1,6 @@ #pragma once +#include "filecache.h" #include "mdlpart.h" #include #include @@ -31,7 +32,7 @@ struct GameData; class GearView : public QWidget { Q_OBJECT public: - explicit GearView(GameData* data); + explicit GearView(GameData* data, FileCache& cache); /// Returns an inclusive list of races supported by the current gearset. std::vector> supportedRaces() const; @@ -92,4 +93,5 @@ private: MDLPart* mdlPart = nullptr; GameData* data; + FileCache& cache; }; \ No newline at end of file diff --git a/mdlviewer/include/mainwindow.h b/mdlviewer/include/mainwindow.h index dba2ed0..cd63cb5 100644 --- a/mdlviewer/include/mainwindow.h +++ b/mdlviewer/include/mainwindow.h @@ -11,6 +11,7 @@ #include "singlegearview.h" struct GameData; +class FileCache; class MainWindow : public QMainWindow { public: @@ -21,4 +22,5 @@ private: FullModelViewer* fullModelViewer = nullptr; GameData& data; + FileCache cache; }; \ No newline at end of file diff --git a/mdlviewer/include/singlegearview.h b/mdlviewer/include/singlegearview.h index 78d630c..db7b8a6 100644 --- a/mdlviewer/include/singlegearview.h +++ b/mdlviewer/include/singlegearview.h @@ -1,5 +1,6 @@ #pragma once +#include "filecache.h" #include "gearview.h" #include #include @@ -9,7 +10,7 @@ struct GameData; class SingleGearView : public QWidget { Q_OBJECT public: - explicit SingleGearView(GameData* data); + explicit SingleGearView(GameData* data, FileCache& cache); Q_SIGNALS: void gearChanged(); diff --git a/mdlviewer/src/fullmodelviewer.cpp b/mdlviewer/src/fullmodelviewer.cpp index 35d8a28..9885f84 100644 --- a/mdlviewer/src/fullmodelviewer.cpp +++ b/mdlviewer/src/fullmodelviewer.cpp @@ -9,7 +9,7 @@ #include #include -FullModelViewer::FullModelViewer(GameData* data) : data(data) { +FullModelViewer::FullModelViewer(GameData* data, FileCache& cache) : data(data) { setWindowTitle("Full Model Viewer"); setMinimumWidth(640); setMinimumHeight(480); @@ -44,7 +44,7 @@ FullModelViewer::FullModelViewer(GameData* data) : data(data) { cmp = physis_cmp_parse(physis_gamedata_extract_file(data, "chara/xls/charamake/human.cmp")); - gearView = new GearView(data); + gearView = new GearView(data, cache); updateCharacterParameters(); connect(gearView, &GearView::modelReloaded, this, &FullModelViewer::updateCharacterParameters); diff --git a/mdlviewer/src/gearlistmodel.cpp b/mdlviewer/src/gearlistmodel.cpp index 4ac372a..717ebfe 100644 --- a/mdlviewer/src/gearlistmodel.cpp +++ b/mdlviewer/src/gearlistmodel.cpp @@ -1,10 +1,10 @@ #include "gearlistmodel.h" #include +#include #include GearListModel::GearListModel(GameData* data) : gameData(data), QAbstractItemModel() { - beginResetModel(); // smallclothes body { GearInfo info = {}; @@ -25,55 +25,27 @@ GearListModel::GearListModel(GameData* data) : gameData(data), QAbstractItemMode auto exh = physis_gamedata_read_excel_sheet_header(data, "Item"); - for (int p = 0; p < exh->page_count; p++) { - auto exd = physis_gamedata_read_excel_sheet(data, "Item", exh, Language::English, p); + exdFuture = new QFutureWatcher(this); + connect(exdFuture, &QFutureWatcher::resultReadyAt, this, &GearListModel::exdFinished); + connect(exdFuture, &QFutureWatcher::finished, this, &GearListModel::finished); - for (int i = 0; i < exd.row_count; i++) { - const auto row = exd.row_data[i]; - auto primaryModel = row.column_data[47].u_int64._0; - auto secondaryModel = row.column_data[48].u_int64._0; - - int16_t parts[4]; - memcpy(parts, &primaryModel, sizeof(int16_t) * 4); - - GearInfo info = {}; - info.name = row.column_data[9].string._0; - info.slot = physis_slot_from_id(row.column_data[17].u_int8._0); - info.modelInfo.primaryID = parts[0]; - - gears.push_back(info); - } + QVector pages; + for(int i = 0; i < exh->page_count; i++) { + pages.push_back(i); } + std::function loadEXD = [data, exh](const int page) -> physis_EXD { + return physis_gamedata_read_excel_sheet(data, "Item", exh, Language::English, page); + }; + + exdFuture->setFuture(QtConcurrent::mapped(pages, loadEXD)); + for (auto slotName : magic_enum::enum_names()) { slotNames.push_back(slotName.data()); } - endResetModel(); rootItem = new TreeInformation(); rootItem->type = TreeType::Root; - - int i = 0; - for (auto slot : magic_enum::enum_values()) { - TreeInformation* categoryItem = new TreeInformation(); - categoryItem->type = TreeType::Category; - categoryItem->slotType = slot; - categoryItem->parent = rootItem; - categoryItem->row = i++; - rootItem->children.push_back(categoryItem); - - int j = 0; - for (auto gear : gears) { - if (gear.slot == slot) { - TreeInformation* item = new TreeInformation(); - item->type = TreeType::Item; - item->gear = gear; - item->parent = categoryItem; - item->row = j++; - categoryItem->children.push_back(item); - } - } - } } int GearListModel::rowCount(const QModelIndex& parent) const { @@ -161,4 +133,51 @@ std::optional GearListModel::getGearFromIndex(const QModelIndex& index return {}; } +void GearListModel::exdFinished(int index) { + auto exd = exdFuture->resultAt(index); + + for (int i = 0; i < exd.row_count; i++) { + const auto row = exd.row_data[i]; + auto primaryModel = row.column_data[47].u_int64._0; + auto secondaryModel = row.column_data[48].u_int64._0; + + int16_t parts[4]; + memcpy(parts, &primaryModel, sizeof(int16_t) * 4); + + GearInfo info = {}; + info.name = row.column_data[9].string._0; + info.slot = physis_slot_from_id(row.column_data[17].u_int8._0); + info.modelInfo.primaryID = parts[0]; + + gears.push_back(info); + } +} + +void GearListModel::finished() { + beginResetModel(); + + int i = 0; + for (auto slot : magic_enum::enum_values()) { + TreeInformation* categoryItem = new TreeInformation(); + categoryItem->type = TreeType::Category; + categoryItem->slotType = slot; + categoryItem->parent = rootItem; + categoryItem->row = i++; + rootItem->children.push_back(categoryItem); + + int j = 0; + for (auto gear : gears) { + if (gear.slot == slot) { + TreeInformation* item = new TreeInformation(); + item->type = TreeType::Item; + item->gear = gear; + item->parent = categoryItem; + item->row = j++; + categoryItem->children.push_back(item); + } + } + } + endResetModel(); +} + #include "moc_gearlistmodel.cpp" \ No newline at end of file diff --git a/mdlviewer/src/gearview.cpp b/mdlviewer/src/gearview.cpp index b9ba082..2ae5497 100644 --- a/mdlviewer/src/gearview.cpp +++ b/mdlviewer/src/gearview.cpp @@ -1,11 +1,13 @@ #include "gearview.h" -#include "magic_enum.hpp" #include #include -GearView::GearView(GameData* data) : data(data) { - mdlPart = new MDLPart(data); +#include "magic_enum.hpp" +#include "filecache.h" + +GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) { + mdlPart = new MDLPart(data, cache); reloadRaceDeforms(); @@ -201,9 +203,7 @@ void GearView::reloadModel() { maxLod = 0; for (const auto& gear : gears) { - auto mdl_data = physis_gamedata_extract_file( - data, - physis_build_equipment_path( + auto mdl_data = cache.lookupFile(physis_build_equipment_path( gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot)); // attempt to load the next best race @@ -211,9 +211,7 @@ void GearView::reloadModel() { Race fallbackRace = currentRace; Subrace fallbackSubrace = currentSubrace; if (mdl_data.size == 0) { - mdl_data = physis_gamedata_extract_file( - data, - physis_build_equipment_path( + mdl_data = cache.lookupFile(physis_build_equipment_path( gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot)); fallbackRace = Race::Hyur; fallbackSubrace = Subrace::Midlander; @@ -246,12 +244,12 @@ void GearView::reloadModel() { fmt::arg("bodyCode", bodyCode)); if (physis_gamedata_exists(data, mtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, mtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(mtrl_path.c_str())); materials.push_back(mat); } if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, skinmtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); materials.push_back(mat); } } @@ -263,9 +261,7 @@ void GearView::reloadModel() { } if (face) { - auto mdl_data = physis_gamedata_extract_file( - data, - physis_build_character_path( + auto mdl_data = cache.lookupFile(physis_build_character_path( CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { @@ -285,7 +281,7 @@ void GearView::reloadModel() { fmt::print("oops: {}", skinmtrl_path); if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, skinmtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); materials.push_back(mat); } } @@ -295,9 +291,7 @@ void GearView::reloadModel() { } if (hair) { - auto mdl_data = physis_gamedata_extract_file( - data, - physis_build_character_path( + auto mdl_data = cache.lookupFile(physis_build_character_path( CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { @@ -317,7 +311,7 @@ void GearView::reloadModel() { fmt::print("oops: {}", skinmtrl_path); if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, skinmtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); materials.push_back(mat); } } @@ -327,9 +321,7 @@ void GearView::reloadModel() { } if (ear) { - auto mdl_data = physis_gamedata_extract_file( - data, - physis_build_character_path( + auto mdl_data = cache.lookupFile(physis_build_character_path( CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { @@ -349,7 +341,7 @@ void GearView::reloadModel() { fmt::print("oops: {}", skinmtrl_path); if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, skinmtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); materials.push_back(mat); } } @@ -359,9 +351,7 @@ void GearView::reloadModel() { } if (tail) { - auto mdl_data = physis_gamedata_extract_file( - data, - physis_build_character_path( + auto mdl_data = cache.lookupFile(physis_build_character_path( CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { @@ -377,7 +367,7 @@ void GearView::reloadModel() { fmt::arg("bodyCode", *tail)); if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { - auto mat = physis_material_parse(physis_gamedata_extract_file(data, skinmtrl_path.c_str())); + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); mdlPart->addModel(mdl, {mat}, currentLod); } } diff --git a/mdlviewer/src/mainwindow.cpp b/mdlviewer/src/mainwindow.cpp index 79346a0..85dd0b6 100644 --- a/mdlviewer/src/mainwindow.cpp +++ b/mdlviewer/src/mainwindow.cpp @@ -20,8 +20,9 @@ #include "aboutwindow.h" #include "cmpeditor.h" #include "gearlistwidget.h" +#include "filecache.h" -MainWindow::MainWindow(GameData* in_data) : data(*in_data) { +MainWindow::MainWindow(GameData* in_data) : data(*in_data), cache(FileCache{*in_data}) { setWindowTitle("mdlviewer"); setMinimumSize(QSize(800, 600)); @@ -84,12 +85,12 @@ MainWindow::MainWindow(GameData* in_data) : data(*in_data) { }); layout->addWidget(gearListWidget); - gearView = new SingleGearView(&data); + gearView = new SingleGearView(&data, cache); connect(gearView, &SingleGearView::addToFullModelViewer, this, [=](GearInfo& info) { fullModelViewer->addGear(info); }); layout->addWidget(gearView); - fullModelViewer = new FullModelViewer(&data); + fullModelViewer = new FullModelViewer(&data, cache); fullModelViewer->show(); } \ No newline at end of file diff --git a/mdlviewer/src/singlegearview.cpp b/mdlviewer/src/singlegearview.cpp index 18ed8da..387e594 100644 --- a/mdlviewer/src/singlegearview.cpp +++ b/mdlviewer/src/singlegearview.cpp @@ -4,10 +4,11 @@ #include #include +#include "filecache.h" #include "magic_enum.hpp" -SingleGearView::SingleGearView(GameData* data) : data(data) { - gearView = new GearView(data); +SingleGearView::SingleGearView(GameData* data, FileCache& cache) : data(data) { + gearView = new GearView(data, cache); auto layout = new QVBoxLayout(); layout->addWidget(gearView); diff --git a/parts/mdl/CMakeLists.txt b/parts/mdl/CMakeLists.txt index ca74645..beee462 100644 --- a/parts/mdl/CMakeLists.txt +++ b/parts/mdl/CMakeLists.txt @@ -1,3 +1,3 @@ add_library(mdlpart STATIC mdlpart.cpp) -target_link_libraries(mdlpart PUBLIC physis z ${LIBRARIES} Qt5::Core Qt5::Widgets renderer) +target_link_libraries(mdlpart PUBLIC physis z ${LIBRARIES} Qt5::Core Qt5::Widgets renderer NovusCommon) target_include_directories(mdlpart PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index b0b3e3c..84cc7fb 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -17,100 +17,100 @@ #include #include +#include "filecache.h" + #ifndef USE_STANDALONE_WINDOW class VulkanWindow : public QWindow { public: - VulkanWindow(MDLPart *part, Renderer *renderer, QVulkanInstance *instance) - : part(part), m_renderer(renderer), m_instance(instance) { - setSurfaceType(VulkanSurface); - setVulkanInstance(instance); - } - - void exposeEvent(QExposeEvent *) { - if (isExposed()) { - if (!m_initialized) { - m_initialized = true; - - auto surface = m_instance->surfaceForWindow(this); - if (!m_renderer->initSwapchain(surface, width(), height())) - m_initialized = false; - else - render(); - } - } + VulkanWindow(MDLPart* part, Renderer* renderer, QVulkanInstance* instance) + : part(part), m_renderer(renderer), m_instance(instance) { + setSurfaceType(VulkanSurface); + setVulkanInstance(instance); } - bool event(QEvent *e) { - switch(e->type()) { - case QEvent::UpdateRequest: - render(); - break; - case QEvent::Resize: { - QResizeEvent* resizeEvent = (QResizeEvent*)e; - auto surface = m_instance->surfaceForWindow(this); - m_renderer->resize(surface, resizeEvent->size().width(), resizeEvent->size().height()); - } break; - case QEvent::MouseButtonPress: { - auto mouseEvent = dynamic_cast(e); + void exposeEvent(QExposeEvent*) { + if (isExposed()) { + if (!m_initialized) { + m_initialized = true; - if (mouseEvent->button() == Qt::MouseButton::LeftButton) { - part->lastX = mouseEvent->x(); - part->lastY = mouseEvent->y(); - part->cameraMode = MDLPart::CameraMode::Orbit; - - setKeyboardGrabEnabled(true); - setMouseGrabEnabled(true); - } else if (mouseEvent->button() == Qt::MouseButton::RightButton) { - part->lastX = mouseEvent->x(); - part->lastY = mouseEvent->y(); - part->cameraMode = MDLPart::CameraMode::Move; - - setKeyboardGrabEnabled(true); - setMouseGrabEnabled(true); + auto surface = m_instance->surfaceForWindow(this); + if (!m_renderer->initSwapchain(surface, width(), height())) + m_initialized = false; + else + render(); } - } break; - case QEvent::MouseButtonRelease: { - part->cameraMode = MDLPart::CameraMode::None; + } + } - setKeyboardGrabEnabled(false); - setMouseGrabEnabled(false); - } break; - case QEvent::MouseMove: { - auto mouseEvent = dynamic_cast(e); - if (part->cameraMode != MDLPart::CameraMode::None) { - const int deltaX = mouseEvent->x() - part->lastX; - const int deltaY = mouseEvent->y() - part->lastY; + bool event(QEvent* e) { + switch (e->type()) { + case QEvent::UpdateRequest: + render(); + break; + case QEvent::Resize: { + QResizeEvent* resizeEvent = (QResizeEvent*)e; + auto surface = m_instance->surfaceForWindow(this); + m_renderer->resize(surface, resizeEvent->size().width(), resizeEvent->size().height()); + } break; + case QEvent::MouseButtonPress: { + auto mouseEvent = dynamic_cast(e); - if (part->cameraMode == MDLPart::CameraMode::Orbit) { - part->yaw += deltaX * 0.01f; // TODO: remove these magic numbers - part->pitch += deltaY * 0.01f; - } else { - glm::vec3 position( + if (mouseEvent->button() == Qt::MouseButton::LeftButton) { + part->lastX = mouseEvent->x(); + part->lastY = mouseEvent->y(); + part->cameraMode = MDLPart::CameraMode::Orbit; + + setKeyboardGrabEnabled(true); + setMouseGrabEnabled(true); + } else if (mouseEvent->button() == Qt::MouseButton::RightButton) { + part->lastX = mouseEvent->x(); + part->lastY = mouseEvent->y(); + part->cameraMode = MDLPart::CameraMode::Move; + + setKeyboardGrabEnabled(true); + setMouseGrabEnabled(true); + } + } break; + case QEvent::MouseButtonRelease: { + part->cameraMode = MDLPart::CameraMode::None; + + setKeyboardGrabEnabled(false); + setMouseGrabEnabled(false); + } break; + case QEvent::MouseMove: { + auto mouseEvent = dynamic_cast(e); + if (part->cameraMode != MDLPart::CameraMode::None) { + const int deltaX = mouseEvent->x() - part->lastX; + const int deltaY = mouseEvent->y() - part->lastY; + + if (part->cameraMode == MDLPart::CameraMode::Orbit) { + part->yaw += deltaX * 0.01f; // TODO: remove these magic numbers + part->pitch += deltaY * 0.01f; + } else { + glm::vec3 position( part->cameraDistance * sin(part->yaw), part->cameraDistance * part->pitch, part->cameraDistance * cos(part->yaw)); - glm::quat rot = glm::quatLookAt((part->position + position) - part->position, {0, 1, 0}); + glm::quat rot = glm::quatLookAt((part->position + position) - part->position, {0, 1, 0}); - glm::vec3 up, right; - up = rot * glm::vec3{0, 1, 0}; - right = rot * glm::vec3{1, 0, 0}; + glm::vec3 up, right; + up = rot * glm::vec3{0, 1, 0}; + right = rot * glm::vec3{1, 0, 0}; - part->position += up * (float)deltaY * 0.01f; - part->position += right * (float)deltaX * 0.01f; + part->position += up * (float)deltaY * 0.01f; + part->position += right * (float)deltaX * 0.01f; + } + + part->lastX = mouseEvent->x(); + part->lastY = mouseEvent->y(); } + } break; + case QEvent::Wheel: { + auto scrollEvent = dynamic_cast(e); - part->lastX = mouseEvent->x(); - part->lastY = mouseEvent->y(); - } - } break; - case QEvent::Wheel: - { - auto scrollEvent = dynamic_cast(e); - - part->cameraDistance -= scrollEvent->angleDelta().y() / 120.0f; // FIXME: why 120? - } - break; + part->cameraDistance -= scrollEvent->angleDelta().y() / 120.0f; // FIXME: why 120? + } break; } return QWindow::event(e); @@ -118,9 +118,9 @@ public: void render() { glm::vec3 position( - part->cameraDistance * sin(part->yaw), - part->cameraDistance * part->pitch, - part->cameraDistance * cos(part->yaw)); + part->cameraDistance * sin(part->yaw), + part->cameraDistance * part->pitch, + part->cameraDistance * cos(part->yaw)); m_renderer->view = glm::lookAt(part->position + position, part->position, glm::vec3(0, -1, 0)); @@ -138,12 +138,12 @@ private: MDLPart* part; }; #else -#include "standalonewindow.h" -#include "equipment.h" + #include "equipment.h" + #include "standalonewindow.h" #endif -MDLPart::MDLPart(GameData *data) : data(data) { +MDLPart::MDLPart(GameData* data, FileCache& cache) : data(data), cache(cache) { auto viewportLayout = new QVBoxLayout(); setLayout(viewportLayout); @@ -176,7 +176,7 @@ MDLPart::MDLPart(GameData *data) : data(data) { connect(this, &MDLPart::skeletonChanged, this, &MDLPart::reloadBoneData); } -void MDLPart::exportModel(const QString &fileName) { +void MDLPart::exportModel(const QString& fileName) { Assimp::Exporter exporter; aiScene scene; @@ -198,15 +198,15 @@ void MDLPart::exportModel(const QString &fileName) { std::vector skeletonNodes; - for(int i = 0; i < models[0].model.num_affected_bones; i++) { + for (int i = 0; i < models[0].model.num_affected_bones; i++) { auto& node = skeletonNodes.emplace_back(); node = new aiNode(); node->mName = models[0].model.affected_bone_names[i]; int real_bone_id = 0; - for(int k = 0; k < skeleton->num_bones; k++) { - if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { - real_bone_id = k; + for (int k = 0; k < skeleton->num_bones; k++) { + if (strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { + real_bone_id = k; } } @@ -217,18 +217,18 @@ void MDLPart::exportModel(const QString &fileName) { } // setup parenting - for(int i = 0; i < models[0].model.num_affected_bones; i++) { + for (int i = 0; i < models[0].model.num_affected_bones; i++) { int real_bone_id = 0; - for(int k = 0; k < skeleton->num_bones; k++) { - if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { - real_bone_id = k; + for (int k = 0; k < skeleton->num_bones; k++) { + if (strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { + real_bone_id = k; } } auto& real_bone = skeleton->bones[real_bone_id]; - if(real_bone.parent_bone != nullptr) { - for(int k = 0; k < models[0].model.num_affected_bones; k++) { - if(strcmp(models[0].model.affected_bone_names[k], real_bone.parent_bone->name) == 0) { + if (real_bone.parent_bone != nullptr) { + for (int k = 0; k < models[0].model.num_affected_bones; k++) { + if (strcmp(models[0].model.affected_bone_names[k], real_bone.parent_bone->name) == 0) { skeletonNodes[i]->mParent = skeletonNodes[k]; skeletonNodes[k]->mChildren[skeletonNodes[k]->mNumChildren++] = skeletonNodes[i]; } @@ -240,39 +240,39 @@ void MDLPart::exportModel(const QString &fileName) { skeleton_node->mChildren[0]->mName = "root"; skeleton_node->mChildren[0]->mChildren = new aiNode*[models[0].model.num_affected_bones]; - for(int i = 0; i < skeletonNodes.size(); i++) { - if(skeletonNodes[i]->mParent == nullptr) { + for (int i = 0; i < skeletonNodes.size(); i++) { + if (skeletonNodes[i]->mParent == nullptr) { skeleton_node->mChildren[0]->mChildren[skeleton_node->mChildren[0]->mNumChildren++] = skeletonNodes[i]; } } - for(int i = 0; i < models[0].model.lods[0].num_parts; i++) { + for (int i = 0; i < models[0].model.lods[0].num_parts; i++) { scene.mMeshes[i] = new aiMesh(); scene.mMeshes[i]->mMaterialIndex = 0; auto& node = scene.mRootNode->mChildren[i]; node = new aiNode(); node->mNumMeshes = 1; - node->mMeshes = new unsigned int [scene.mRootNode->mNumMeshes]; + node->mMeshes = new unsigned int[scene.mRootNode->mNumMeshes]; node->mMeshes[0] = i; auto mesh = scene.mMeshes[i]; mesh->mNumVertices = models[0].model.lods[0].parts[i].num_vertices; - mesh->mVertices = new aiVector3D [mesh->mNumVertices]; - mesh->mNormals = new aiVector3D [mesh->mNumVertices]; - mesh->mTextureCoords[0] = new aiVector3D [mesh->mNumVertices]; + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; mesh->mNumUVComponents[0] = 2; - for(int j = 0; j < mesh->mNumVertices; j++) { + for (int j = 0; j < mesh->mNumVertices; j++) { auto vertex = models[0].model.lods[0].parts[i].vertices[j]; mesh->mVertices[j] = aiVector3D(vertex.position[0], vertex.position[1], vertex.position[2]); - mesh->mNormals[j] = aiVector3D (vertex.normal[0], vertex.normal[1], vertex.normal[2]); + mesh->mNormals[j] = aiVector3D(vertex.normal[0], vertex.normal[1], vertex.normal[2]); mesh->mTextureCoords[0][j] = aiVector3D(vertex.uv[0], vertex.uv[1], 0.0f); } mesh->mNumBones = models[0].model.num_affected_bones; mesh->mBones = new aiBone*[mesh->mNumBones]; - for(int j = 0; j < mesh->mNumBones; j++) { + for (int j = 0; j < mesh->mNumBones; j++) { int real_bone_id = j; // TODO: is this still relevant?5 /*for(int k = 0; k < skeleton.bones.size(); k++) { @@ -287,10 +287,10 @@ void MDLPart::exportModel(const QString &fileName) { mesh->mBones[j]->mWeights = new aiVertexWeight[mesh->mBones[j]->mNumWeights]; // mesh->mBones[j]->mNode = skeleton_node->mChildren[j]; - for(int k = 0; k < mesh->mNumVertices; k++) { - for(int z = 0; z < 4; z++) { + for (int k = 0; k < mesh->mNumVertices; k++) { + for (int z = 0; z < 4; z++) { if (models[0].model.lods[0].parts[i].vertices[k].bone_id[z] == real_bone_id) { - auto &weight = mesh->mBones[j]->mWeights[k * 4 + z]; + auto& weight = mesh->mBones[j]->mWeights[k * 4 + z]; weight.mVertexId = k; weight.mWeight = models[0].model.lods[0].parts[i].vertices[k].bone_weight[z]; } @@ -302,7 +302,7 @@ void MDLPart::exportModel(const QString &fileName) { mesh->mFaces = new aiFace[mesh->mNumFaces]; int lastFace = 0; - for(int j = 0; j < models[0].model.lods[0].parts[i].num_indices; j += 3) { + for (int j = 0; j < models[0].model.lods[0].parts[i].num_indices; j += 3) { aiFace& face = mesh->mFaces[lastFace++]; face.mNumIndices = 3; @@ -332,9 +332,10 @@ void MDLPart::addModel(physis_MDL mdl, std::vector materials, i auto model = renderer->addModel(mdl, lod); - std::transform(materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material& mat) { - return createMaterial(mat); - }); + std::transform( + materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material& mat) { + return createMaterial(mat); + }); models.push_back(model); @@ -350,8 +351,7 @@ void MDLPart::setSkeleton(physis_Skeleton newSkeleton) { } void MDLPart::loadRaceDeformMatrices(physis_Buffer buffer) { - QJsonDocument document = QJsonDocument::fromJson( - QByteArray((const char *)buffer.data, buffer.size)); + QJsonDocument document = QJsonDocument::fromJson(QByteArray((const char*)buffer.data, buffer.size)); for (auto boneObj : document.object()["Data"].toArray()) { QJsonArray matrix = boneObj.toObject()["Matrix"].toArray(); QString boneName = boneObj.toObject()["Name"].toString(); @@ -363,9 +363,8 @@ void MDLPart::loadRaceDeformMatrices(physis_Buffer buffer) { } for (int i = 0; i < skeleton->num_bones; i++) { - if (std::string_view{skeleton->bones[i].name} == - boneName.toStdString()) { - auto &data = boneData[i]; + if (std::string_view{skeleton->bones[i].name} == boneName.toStdString()) { + auto& data = boneData[i]; data.deformRaceMatrix = actualMatrix; } @@ -394,7 +393,7 @@ void MDLPart::reloadRenderer() { } void MDLPart::reloadBoneData() { - if(skeleton) { + if (skeleton) { if (!firstTimeSkeletonDataCalculated) { if (boneData.empty()) { boneData.resize(skeleton->num_bones); @@ -402,7 +401,7 @@ void MDLPart::reloadBoneData() { calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr); - for (auto &bone : boneData) { + for (auto& bone : boneData) { bone.inversePose = glm::inverse(bone.inversePose); } firstTimeSkeletonDataCalculated = true; @@ -411,12 +410,13 @@ void MDLPart::reloadBoneData() { // update data calculateBone(*skeleton, *skeleton->root_bone, nullptr); - for(auto& model : models) { + for (auto& model : models) { // we want to map the actual affected bones to bone ids std::map boneMapping; for (int i = 0; i < model.model.num_affected_bones; i++) { for (int k = 0; k < skeleton->num_bones; k++) { - if (std::string_view{skeleton->bones[k].name} == std::string_view{model.model.affected_bone_names[i]}) { + if (std::string_view{skeleton->bones[k].name} == + std::string_view{model.model.affected_bone_names[i]}) { boneMapping[i] = k; } } @@ -429,7 +429,7 @@ void MDLPart::reloadBoneData() { } } -RenderMaterial MDLPart::createMaterial(const physis_Material &material) { +RenderMaterial MDLPart::createMaterial(const physis_Material& material) { RenderMaterial newMaterial; for (int i = 0; i < material.num_textures; i++) { @@ -441,34 +441,31 @@ RenderMaterial MDLPart::createMaterial(const physis_Material &material) { char type = t[t.length() - 5]; - switch(type) { + switch (type) { case 'm': { - auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i])); + auto texture = physis_texture_parse(cache.lookupFile(material.textures[i])); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size); newMaterial.multiTexture = new RenderTexture(tex); } case 'd': { - auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i])); + auto texture = physis_texture_parse(cache.lookupFile(material.textures[i])); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size); newMaterial.diffuseTexture = new RenderTexture(tex); - } - break; + } break; case 'n': { - auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i])); + auto texture = physis_texture_parse(cache.lookupFile(material.textures[i])); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size); newMaterial.normalTexture = new RenderTexture(tex); - } - break; + } break; case 's': { - auto texture = physis_texture_parse(physis_gamedata_extract_file(data, material.textures[i])); + auto texture = physis_texture_parse(cache.lookupFile(material.textures[i])); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size); newMaterial.specularTexture = new RenderTexture(tex); - } - break; + } break; default: qDebug() << "unhandled type" << type; break; @@ -488,8 +485,9 @@ void MDLPart::calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& b boneData[bone.index].inversePose = parentMatrix * local; - for(int i = 0; i < skeleton.num_bones; i++) { - if(skeleton.bones[i].parent_bone != nullptr && std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{bone.name}) { + for (int i = 0; i < skeleton.num_bones; i++) { + if (skeleton.bones[i].parent_bone != nullptr && + std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{bone.name}) { calculateBoneInversePose(skeleton, skeleton.bones[i], &bone); } } @@ -497,27 +495,20 @@ void MDLPart::calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& b void MDLPart::calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone) { const glm::mat4 parent_matrix = - parent_bone == nullptr ? glm::mat4(1.0f) - : boneData[parent_bone->index].localTransform; + parent_bone == nullptr ? glm::mat4(1.0f) : boneData[parent_bone->index].localTransform; glm::mat4 local = glm::mat4(1.0f); - local = glm::translate( - local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); - local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], - bone.rotation[1], bone.rotation[2])); - local = glm::scale(local, - glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); + local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2])); + local *= glm::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], bone.rotation[1], bone.rotation[2])); + local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2])); boneData[bone.index].localTransform = parent_matrix * local; boneData[bone.index].finalTransform = - boneData[bone.index].localTransform * - boneData[bone.index].deformRaceMatrix * - boneData[bone.index].inversePose; + boneData[bone.index].localTransform * boneData[bone.index].deformRaceMatrix * boneData[bone.index].inversePose; for (int i = 0; i < skeleton.num_bones; i++) { if (skeleton.bones[i].parent_bone != nullptr && - std::string_view{skeleton.bones[i].parent_bone->name} == - std::string_view{bone.name}) { + std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{bone.name}) { calculateBone(skeleton, skeleton.bones[i], &bone); } } diff --git a/parts/mdl/mdlpart.h b/parts/mdl/mdlpart.h index f13ba6b..315f0f9 100644 --- a/parts/mdl/mdlpart.h +++ b/parts/mdl/mdlpart.h @@ -10,66 +10,71 @@ struct GameData; class VulkanWindow; class StandaloneWindow; +class FileCache; class MDLPart : public QWidget { - Q_OBJECT + Q_OBJECT public: - explicit MDLPart(GameData *data); + explicit MDLPart(GameData* data, FileCache& cache); - void exportModel(const QString &fileName); + void exportModel(const QString& fileName); - int lastX = -1; - int lastY = -1; + int lastX = -1; + int lastY = -1; - enum class CameraMode { None, Orbit, Move }; + enum class CameraMode { + None, + Orbit, + Move + }; - CameraMode cameraMode = CameraMode::None; - float pitch = 0.0f; - float yaw = 0.0f; - float cameraDistance = 5.0f; - glm::vec3 position{0, 0, 0}; + CameraMode cameraMode = CameraMode::None; + float pitch = 0.0f; + float yaw = 0.0f; + float cameraDistance = 5.0f; + glm::vec3 position{0, 0, 0}; - std::unique_ptr skeleton; + std::unique_ptr skeleton; - struct BoneData { - glm::mat4 localTransform, finalTransform, inversePose; - glm::mat4 deformRaceMatrix{1.0f}; - }; + struct BoneData { + glm::mat4 localTransform, finalTransform, inversePose; + glm::mat4 deformRaceMatrix{1.0f}; + }; - std::vector boneData; + std::vector boneData; Q_SIGNALS: - void modelChanged(); - void skeletonChanged(); + void modelChanged(); + void skeletonChanged(); public Q_SLOTS: - /// Clears all stored MDLs. - void clear(); + /// Clears all stored MDLs. + void clear(); - /// Adds a new MDL with a list of materials used. - void addModel(physis_MDL mdl, std::vector materials, - int lod); + /// Adds a new MDL with a list of materials used. + void addModel(physis_MDL mdl, std::vector materials, int lod); - /// Sets the skeleton any skinned MDLs should bind to. - void setSkeleton(physis_Skeleton skeleton); + /// Sets the skeleton any skinned MDLs should bind to. + void setSkeleton(physis_Skeleton skeleton); - /// Sets the race deform matrices - void loadRaceDeformMatrices(physis_Buffer buffer); + /// Sets the race deform matrices + void loadRaceDeformMatrices(physis_Buffer buffer); - /// Clears the current skeleton. - void clearSkeleton(); + /// Clears the current skeleton. + void clearSkeleton(); - void reloadBoneData(); - void reloadRenderer(); + void reloadBoneData(); + void reloadRenderer(); private: - RenderMaterial createMaterial(const physis_Material &mat); + RenderMaterial createMaterial(const physis_Material& mat); - void calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& bone, physis_Bone* parent_bone); + void calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& bone, physis_Bone* parent_bone); void calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone); GameData* data = nullptr; + FileCache& cache; std::vector models;