1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-25 05:17:44 +00:00

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.
This commit is contained in:
Joshua Goins 2023-07-09 10:54:27 -04:00
parent baf1158e00
commit 47d612eb8f
18 changed files with 322 additions and 265 deletions

View file

@ -5,7 +5,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
find_package(Qt5 COMPONENTS Core Widgets CONFIG REQUIRED) find_package(Qt5 COMPONENTS Core Widgets Concurrent CONFIG REQUIRED)
include(FetchContent) include(FetchContent)

View file

@ -1,12 +1,14 @@
add_library(NovusCommon STATIC) add_library(NovusCommon STATIC)
target_sources(NovusCommon PRIVATE target_sources(NovusCommon PRIVATE
include/aboutwindow.h include/aboutwindow.h
src/aboutwindow.cpp) include/filecache.h
src/aboutwindow.cpp
src/filecache.cpp)
target_include_directories(NovusCommon PUBLIC target_include_directories(NovusCommon PUBLIC
include include
PRIVATE PRIVATE
${CMAKE_BINARY_DIR}) ${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 # meant for including the license text
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE LICENSE_TXT) file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE LICENSE_TXT)

View file

@ -0,0 +1,18 @@
#pragma once
#include <physis.hpp>
#include <QString>
#include <QMap>
struct GameData;
class FileCache {
public:
explicit FileCache(GameData& data);
physis_Buffer& lookupFile(const QString& path);
private:
QMap<QString, physis_Buffer> cachedBuffers;
GameData& data;
};

16
common/src/filecache.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "filecache.h"
#include <physis.hpp>
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];
}

View file

@ -23,6 +23,7 @@ target_link_libraries(mdlviewer PUBLIC
${LIBRARIES} ${LIBRARIES}
Qt5::Core Qt5::Core
Qt5::Widgets Qt5::Widgets
Qt5::Concurrent
renderer renderer
assimp::assimp assimp::assimp
magic_enum magic_enum

View file

@ -6,11 +6,12 @@
#include "gearview.h" #include "gearview.h"
struct GameData; struct GameData;
class FileCache;
class FullModelViewer : public QWidget { class FullModelViewer : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit FullModelViewer(GameData* data); explicit FullModelViewer(GameData* data, FileCache& cache);
Q_SIGNALS: Q_SIGNALS:
void gearChanged(); void gearChanged();

View file

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "gearview.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
#include <QFutureWatcher>
#include "gearview.h"
enum class TreeType { enum class TreeType {
Root, Root,
@ -37,6 +39,11 @@ public:
std::optional<GearInfo> getGearFromIndex(const QModelIndex& index); std::optional<GearInfo> getGearFromIndex(const QModelIndex& index);
private: private:
void exdFinished(int index);
void finished();
QFutureWatcher<physis_EXD>* exdFuture;
std::vector<GearInfo> gears; std::vector<GearInfo> gears;
QStringList slotNames; QStringList slotNames;

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "filecache.h"
#include "mdlpart.h" #include "mdlpart.h"
#include <QComboBox> #include <QComboBox>
#include <QWidget> #include <QWidget>
@ -31,7 +32,7 @@ struct GameData;
class GearView : public QWidget { class GearView : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit GearView(GameData* data); explicit GearView(GameData* data, FileCache& cache);
/// Returns an inclusive list of races supported by the current gearset. /// Returns an inclusive list of races supported by the current gearset.
std::vector<std::pair<Race, Subrace>> supportedRaces() const; std::vector<std::pair<Race, Subrace>> supportedRaces() const;
@ -92,4 +93,5 @@ private:
MDLPart* mdlPart = nullptr; MDLPart* mdlPart = nullptr;
GameData* data; GameData* data;
FileCache& cache;
}; };

View file

@ -11,6 +11,7 @@
#include "singlegearview.h" #include "singlegearview.h"
struct GameData; struct GameData;
class FileCache;
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
public: public:
@ -21,4 +22,5 @@ private:
FullModelViewer* fullModelViewer = nullptr; FullModelViewer* fullModelViewer = nullptr;
GameData& data; GameData& data;
FileCache cache;
}; };

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "filecache.h"
#include "gearview.h" #include "gearview.h"
#include <QPushButton> #include <QPushButton>
#include <QWidget> #include <QWidget>
@ -9,7 +10,7 @@ struct GameData;
class SingleGearView : public QWidget { class SingleGearView : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit SingleGearView(GameData* data); explicit SingleGearView(GameData* data, FileCache& cache);
Q_SIGNALS: Q_SIGNALS:
void gearChanged(); void gearChanged();

View file

@ -9,7 +9,7 @@
#include <QRadioButton> #include <QRadioButton>
#include <QVBoxLayout> #include <QVBoxLayout>
FullModelViewer::FullModelViewer(GameData* data) : data(data) { FullModelViewer::FullModelViewer(GameData* data, FileCache& cache) : data(data) {
setWindowTitle("Full Model Viewer"); setWindowTitle("Full Model Viewer");
setMinimumWidth(640); setMinimumWidth(640);
setMinimumHeight(480); 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")); cmp = physis_cmp_parse(physis_gamedata_extract_file(data, "chara/xls/charamake/human.cmp"));
gearView = new GearView(data); gearView = new GearView(data, cache);
updateCharacterParameters(); updateCharacterParameters();
connect(gearView, &GearView::modelReloaded, this, &FullModelViewer::updateCharacterParameters); connect(gearView, &GearView::modelReloaded, this, &FullModelViewer::updateCharacterParameters);

View file

@ -1,10 +1,10 @@
#include "gearlistmodel.h" #include "gearlistmodel.h"
#include <QDebug> #include <QDebug>
#include <QtConcurrent>
#include <magic_enum.hpp> #include <magic_enum.hpp>
GearListModel::GearListModel(GameData* data) : gameData(data), QAbstractItemModel() { GearListModel::GearListModel(GameData* data) : gameData(data), QAbstractItemModel() {
beginResetModel();
// smallclothes body // smallclothes body
{ {
GearInfo info = {}; GearInfo info = {};
@ -25,55 +25,27 @@ GearListModel::GearListModel(GameData* data) : gameData(data), QAbstractItemMode
auto exh = physis_gamedata_read_excel_sheet_header(data, "Item"); auto exh = physis_gamedata_read_excel_sheet_header(data, "Item");
for (int p = 0; p < exh->page_count; p++) { exdFuture = new QFutureWatcher<physis_EXD>(this);
auto exd = physis_gamedata_read_excel_sheet(data, "Item", exh, Language::English, p); connect(exdFuture, &QFutureWatcher<physis_EXD>::resultReadyAt, this, &GearListModel::exdFinished);
connect(exdFuture, &QFutureWatcher<physis_EXD>::finished, this, &GearListModel::finished);
for (int i = 0; i < exd.row_count; i++) { QVector<int> pages;
const auto row = exd.row_data[i]; for(int i = 0; i < exh->page_count; i++) {
auto primaryModel = row.column_data[47].u_int64._0; pages.push_back(i);
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);
}
} }
std::function<physis_EXD(int)> 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<Slot>()) { for (auto slotName : magic_enum::enum_names<Slot>()) {
slotNames.push_back(slotName.data()); slotNames.push_back(slotName.data());
} }
endResetModel();
rootItem = new TreeInformation(); rootItem = new TreeInformation();
rootItem->type = TreeType::Root; rootItem->type = TreeType::Root;
int i = 0;
for (auto slot : magic_enum::enum_values<Slot>()) {
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 { int GearListModel::rowCount(const QModelIndex& parent) const {
@ -161,4 +133,51 @@ std::optional<GearInfo> GearListModel::getGearFromIndex(const QModelIndex& index
return {}; 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<Slot>()) {
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" #include "moc_gearlistmodel.cpp"

View file

@ -1,11 +1,13 @@
#include "gearview.h" #include "gearview.h"
#include "magic_enum.hpp"
#include <QDebug> #include <QDebug>
#include <QVBoxLayout> #include <QVBoxLayout>
GearView::GearView(GameData* data) : data(data) { #include "magic_enum.hpp"
mdlPart = new MDLPart(data); #include "filecache.h"
GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) {
mdlPart = new MDLPart(data, cache);
reloadRaceDeforms(); reloadRaceDeforms();
@ -201,9 +203,7 @@ void GearView::reloadModel() {
maxLod = 0; maxLod = 0;
for (const auto& gear : gears) { for (const auto& gear : gears) {
auto mdl_data = physis_gamedata_extract_file( auto mdl_data = cache.lookupFile(physis_build_equipment_path(
data,
physis_build_equipment_path(
gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot)); gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot));
// attempt to load the next best race // attempt to load the next best race
@ -211,9 +211,7 @@ void GearView::reloadModel() {
Race fallbackRace = currentRace; Race fallbackRace = currentRace;
Subrace fallbackSubrace = currentSubrace; Subrace fallbackSubrace = currentSubrace;
if (mdl_data.size == 0) { if (mdl_data.size == 0) {
mdl_data = physis_gamedata_extract_file( mdl_data = cache.lookupFile(physis_build_equipment_path(
data,
physis_build_equipment_path(
gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot)); gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot));
fallbackRace = Race::Hyur; fallbackRace = Race::Hyur;
fallbackSubrace = Subrace::Midlander; fallbackSubrace = Subrace::Midlander;
@ -246,12 +244,12 @@ void GearView::reloadModel() {
fmt::arg("bodyCode", bodyCode)); fmt::arg("bodyCode", bodyCode));
if (physis_gamedata_exists(data, mtrl_path.c_str())) { 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); materials.push_back(mat);
} }
if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { 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); materials.push_back(mat);
} }
} }
@ -263,9 +261,7 @@ void GearView::reloadModel() {
} }
if (face) { if (face) {
auto mdl_data = physis_gamedata_extract_file( auto mdl_data = cache.lookupFile(physis_build_character_path(
data,
physis_build_character_path(
CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender)); CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) { if (mdl_data.size > 0) {
@ -285,7 +281,7 @@ void GearView::reloadModel() {
fmt::print("oops: {}", skinmtrl_path); fmt::print("oops: {}", skinmtrl_path);
if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { 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); materials.push_back(mat);
} }
} }
@ -295,9 +291,7 @@ void GearView::reloadModel() {
} }
if (hair) { if (hair) {
auto mdl_data = physis_gamedata_extract_file( auto mdl_data = cache.lookupFile(physis_build_character_path(
data,
physis_build_character_path(
CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender)); CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) { if (mdl_data.size > 0) {
@ -317,7 +311,7 @@ void GearView::reloadModel() {
fmt::print("oops: {}", skinmtrl_path); fmt::print("oops: {}", skinmtrl_path);
if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { 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); materials.push_back(mat);
} }
} }
@ -327,9 +321,7 @@ void GearView::reloadModel() {
} }
if (ear) { if (ear) {
auto mdl_data = physis_gamedata_extract_file( auto mdl_data = cache.lookupFile(physis_build_character_path(
data,
physis_build_character_path(
CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender)); CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) { if (mdl_data.size > 0) {
@ -349,7 +341,7 @@ void GearView::reloadModel() {
fmt::print("oops: {}", skinmtrl_path); fmt::print("oops: {}", skinmtrl_path);
if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { 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); materials.push_back(mat);
} }
} }
@ -359,9 +351,7 @@ void GearView::reloadModel() {
} }
if (tail) { if (tail) {
auto mdl_data = physis_gamedata_extract_file( auto mdl_data = cache.lookupFile(physis_build_character_path(
data,
physis_build_character_path(
CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender)); CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender));
if (mdl_data.size > 0) { if (mdl_data.size > 0) {
@ -377,7 +367,7 @@ void GearView::reloadModel() {
fmt::arg("bodyCode", *tail)); fmt::arg("bodyCode", *tail));
if (physis_gamedata_exists(data, skinmtrl_path.c_str())) { 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); mdlPart->addModel(mdl, {mat}, currentLod);
} }
} }

View file

@ -20,8 +20,9 @@
#include "aboutwindow.h" #include "aboutwindow.h"
#include "cmpeditor.h" #include "cmpeditor.h"
#include "gearlistwidget.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"); setWindowTitle("mdlviewer");
setMinimumSize(QSize(800, 600)); setMinimumSize(QSize(800, 600));
@ -84,12 +85,12 @@ MainWindow::MainWindow(GameData* in_data) : data(*in_data) {
}); });
layout->addWidget(gearListWidget); layout->addWidget(gearListWidget);
gearView = new SingleGearView(&data); gearView = new SingleGearView(&data, cache);
connect(gearView, &SingleGearView::addToFullModelViewer, this, [=](GearInfo& info) { connect(gearView, &SingleGearView::addToFullModelViewer, this, [=](GearInfo& info) {
fullModelViewer->addGear(info); fullModelViewer->addGear(info);
}); });
layout->addWidget(gearView); layout->addWidget(gearView);
fullModelViewer = new FullModelViewer(&data); fullModelViewer = new FullModelViewer(&data, cache);
fullModelViewer->show(); fullModelViewer->show();
} }

View file

@ -4,10 +4,11 @@
#include <QPushButton> #include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "filecache.h"
#include "magic_enum.hpp" #include "magic_enum.hpp"
SingleGearView::SingleGearView(GameData* data) : data(data) { SingleGearView::SingleGearView(GameData* data, FileCache& cache) : data(data) {
gearView = new GearView(data); gearView = new GearView(data, cache);
auto layout = new QVBoxLayout(); auto layout = new QVBoxLayout();
layout->addWidget(gearView); layout->addWidget(gearView);

View file

@ -1,3 +1,3 @@
add_library(mdlpart STATIC mdlpart.cpp) 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}) target_include_directories(mdlpart PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View file

@ -17,100 +17,100 @@
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.inl> #include <glm/gtc/type_ptr.inl>
#include "filecache.h"
#ifndef USE_STANDALONE_WINDOW #ifndef USE_STANDALONE_WINDOW
class VulkanWindow : public QWindow { class VulkanWindow : public QWindow {
public: public:
VulkanWindow(MDLPart *part, Renderer *renderer, QVulkanInstance *instance) VulkanWindow(MDLPart* part, Renderer* renderer, QVulkanInstance* instance)
: part(part), m_renderer(renderer), m_instance(instance) { : part(part), m_renderer(renderer), m_instance(instance) {
setSurfaceType(VulkanSurface); setSurfaceType(VulkanSurface);
setVulkanInstance(instance); 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();
}
}
} }
bool event(QEvent *e) { void exposeEvent(QExposeEvent*) {
switch(e->type()) { if (isExposed()) {
case QEvent::UpdateRequest: if (!m_initialized) {
render(); m_initialized = true;
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<QMouseEvent*>(e);
if (mouseEvent->button() == Qt::MouseButton::LeftButton) { auto surface = m_instance->surfaceForWindow(this);
part->lastX = mouseEvent->x(); if (!m_renderer->initSwapchain(surface, width(), height()))
part->lastY = mouseEvent->y(); m_initialized = false;
part->cameraMode = MDLPart::CameraMode::Orbit; else
render();
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); bool event(QEvent* e) {
setMouseGrabEnabled(false); switch (e->type()) {
} break; case QEvent::UpdateRequest:
case QEvent::MouseMove: { render();
auto mouseEvent = dynamic_cast<QMouseEvent*>(e); break;
if (part->cameraMode != MDLPart::CameraMode::None) { case QEvent::Resize: {
const int deltaX = mouseEvent->x() - part->lastX; QResizeEvent* resizeEvent = (QResizeEvent*)e;
const int deltaY = mouseEvent->y() - part->lastY; auto surface = m_instance->surfaceForWindow(this);
m_renderer->resize(surface, resizeEvent->size().width(), resizeEvent->size().height());
} break;
case QEvent::MouseButtonPress: {
auto mouseEvent = dynamic_cast<QMouseEvent*>(e);
if (part->cameraMode == MDLPart::CameraMode::Orbit) { if (mouseEvent->button() == Qt::MouseButton::LeftButton) {
part->yaw += deltaX * 0.01f; // TODO: remove these magic numbers part->lastX = mouseEvent->x();
part->pitch += deltaY * 0.01f; part->lastY = mouseEvent->y();
} else { part->cameraMode = MDLPart::CameraMode::Orbit;
glm::vec3 position(
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<QMouseEvent*>(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 * sin(part->yaw),
part->cameraDistance * part->pitch, part->cameraDistance * part->pitch,
part->cameraDistance * cos(part->yaw)); 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; glm::vec3 up, right;
up = rot * glm::vec3{0, 1, 0}; up = rot * glm::vec3{0, 1, 0};
right = rot * glm::vec3{1, 0, 0}; right = rot * glm::vec3{1, 0, 0};
part->position += up * (float)deltaY * 0.01f; part->position += up * (float)deltaY * 0.01f;
part->position += right * (float)deltaX * 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<QWheelEvent*>(e);
part->lastX = mouseEvent->x(); part->cameraDistance -= scrollEvent->angleDelta().y() / 120.0f; // FIXME: why 120?
part->lastY = mouseEvent->y(); } break;
}
} break;
case QEvent::Wheel:
{
auto scrollEvent = dynamic_cast<QWheelEvent*>(e);
part->cameraDistance -= scrollEvent->angleDelta().y() / 120.0f; // FIXME: why 120?
}
break;
} }
return QWindow::event(e); return QWindow::event(e);
@ -118,9 +118,9 @@ public:
void render() { void render() {
glm::vec3 position( glm::vec3 position(
part->cameraDistance * sin(part->yaw), part->cameraDistance * sin(part->yaw),
part->cameraDistance * part->pitch, part->cameraDistance * part->pitch,
part->cameraDistance * cos(part->yaw)); part->cameraDistance * cos(part->yaw));
m_renderer->view = glm::lookAt(part->position + position, part->position, glm::vec3(0, -1, 0)); m_renderer->view = glm::lookAt(part->position + position, part->position, glm::vec3(0, -1, 0));
@ -138,12 +138,12 @@ private:
MDLPart* part; MDLPart* part;
}; };
#else #else
#include "standalonewindow.h" #include "equipment.h"
#include "equipment.h" #include "standalonewindow.h"
#endif #endif
MDLPart::MDLPart(GameData *data) : data(data) { MDLPart::MDLPart(GameData* data, FileCache& cache) : data(data), cache(cache) {
auto viewportLayout = new QVBoxLayout(); auto viewportLayout = new QVBoxLayout();
setLayout(viewportLayout); setLayout(viewportLayout);
@ -176,7 +176,7 @@ MDLPart::MDLPart(GameData *data) : data(data) {
connect(this, &MDLPart::skeletonChanged, this, &MDLPart::reloadBoneData); connect(this, &MDLPart::skeletonChanged, this, &MDLPart::reloadBoneData);
} }
void MDLPart::exportModel(const QString &fileName) { void MDLPart::exportModel(const QString& fileName) {
Assimp::Exporter exporter; Assimp::Exporter exporter;
aiScene scene; aiScene scene;
@ -198,15 +198,15 @@ void MDLPart::exportModel(const QString &fileName) {
std::vector<aiNode*> skeletonNodes; std::vector<aiNode*> 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(); auto& node = skeletonNodes.emplace_back();
node = new aiNode(); node = new aiNode();
node->mName = models[0].model.affected_bone_names[i]; node->mName = models[0].model.affected_bone_names[i];
int real_bone_id = 0; int real_bone_id = 0;
for(int k = 0; k < skeleton->num_bones; k++) { for (int k = 0; k < skeleton->num_bones; k++) {
if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { if (strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) {
real_bone_id = k; real_bone_id = k;
} }
} }
@ -217,18 +217,18 @@ void MDLPart::exportModel(const QString &fileName) {
} }
// setup parenting // 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; int real_bone_id = 0;
for(int k = 0; k < skeleton->num_bones; k++) { for (int k = 0; k < skeleton->num_bones; k++) {
if(strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) { if (strcmp(skeleton->bones[k].name, models[0].model.affected_bone_names[i]) == 0) {
real_bone_id = k; real_bone_id = k;
} }
} }
auto& real_bone = skeleton->bones[real_bone_id]; auto& real_bone = skeleton->bones[real_bone_id];
if(real_bone.parent_bone != nullptr) { if (real_bone.parent_bone != nullptr) {
for(int k = 0; k < models[0].model.num_affected_bones; k++) { 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 (strcmp(models[0].model.affected_bone_names[k], real_bone.parent_bone->name) == 0) {
skeletonNodes[i]->mParent = skeletonNodes[k]; skeletonNodes[i]->mParent = skeletonNodes[k];
skeletonNodes[k]->mChildren[skeletonNodes[k]->mNumChildren++] = skeletonNodes[i]; 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]->mName = "root";
skeleton_node->mChildren[0]->mChildren = new aiNode*[models[0].model.num_affected_bones]; skeleton_node->mChildren[0]->mChildren = new aiNode*[models[0].model.num_affected_bones];
for(int i = 0; i < skeletonNodes.size(); i++) { for (int i = 0; i < skeletonNodes.size(); i++) {
if(skeletonNodes[i]->mParent == nullptr) { if (skeletonNodes[i]->mParent == nullptr) {
skeleton_node->mChildren[0]->mChildren[skeleton_node->mChildren[0]->mNumChildren++] = skeletonNodes[i]; 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] = new aiMesh();
scene.mMeshes[i]->mMaterialIndex = 0; scene.mMeshes[i]->mMaterialIndex = 0;
auto& node = scene.mRootNode->mChildren[i]; auto& node = scene.mRootNode->mChildren[i];
node = new aiNode(); node = new aiNode();
node->mNumMeshes = 1; node->mNumMeshes = 1;
node->mMeshes = new unsigned int [scene.mRootNode->mNumMeshes]; node->mMeshes = new unsigned int[scene.mRootNode->mNumMeshes];
node->mMeshes[0] = i; node->mMeshes[0] = i;
auto mesh = scene.mMeshes[i]; auto mesh = scene.mMeshes[i];
mesh->mNumVertices = models[0].model.lods[0].parts[i].num_vertices; mesh->mNumVertices = models[0].model.lods[0].parts[i].num_vertices;
mesh->mVertices = new aiVector3D [mesh->mNumVertices]; mesh->mVertices = new aiVector3D[mesh->mNumVertices];
mesh->mNormals = new aiVector3D [mesh->mNumVertices]; mesh->mNormals = new aiVector3D[mesh->mNumVertices];
mesh->mTextureCoords[0] = new aiVector3D [mesh->mNumVertices]; mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
mesh->mNumUVComponents[0] = 2; 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]; 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->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->mTextureCoords[0][j] = aiVector3D(vertex.uv[0], vertex.uv[1], 0.0f);
} }
mesh->mNumBones = models[0].model.num_affected_bones; mesh->mNumBones = models[0].model.num_affected_bones;
mesh->mBones = new aiBone*[mesh->mNumBones]; 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; int real_bone_id = j;
// TODO: is this still relevant?5 // TODO: is this still relevant?5
/*for(int k = 0; k < skeleton.bones.size(); k++) { /*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]->mWeights = new aiVertexWeight[mesh->mBones[j]->mNumWeights];
// mesh->mBones[j]->mNode = skeleton_node->mChildren[j]; // mesh->mBones[j]->mNode = skeleton_node->mChildren[j];
for(int k = 0; k < mesh->mNumVertices; k++) { for (int k = 0; k < mesh->mNumVertices; k++) {
for(int z = 0; z < 4; z++) { for (int z = 0; z < 4; z++) {
if (models[0].model.lods[0].parts[i].vertices[k].bone_id[z] == real_bone_id) { 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.mVertexId = k;
weight.mWeight = models[0].model.lods[0].parts[i].vertices[k].bone_weight[z]; 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]; mesh->mFaces = new aiFace[mesh->mNumFaces];
int lastFace = 0; 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++]; aiFace& face = mesh->mFaces[lastFace++];
face.mNumIndices = 3; face.mNumIndices = 3;
@ -332,9 +332,10 @@ void MDLPart::addModel(physis_MDL mdl, std::vector<physis_Material> materials, i
auto model = renderer->addModel(mdl, lod); auto model = renderer->addModel(mdl, lod);
std::transform(materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material& mat) { std::transform(
return createMaterial(mat); materials.begin(), materials.end(), std::back_inserter(model.materials), [this](const physis_Material& mat) {
}); return createMaterial(mat);
});
models.push_back(model); models.push_back(model);
@ -350,8 +351,7 @@ void MDLPart::setSkeleton(physis_Skeleton newSkeleton) {
} }
void MDLPart::loadRaceDeformMatrices(physis_Buffer buffer) { void MDLPart::loadRaceDeformMatrices(physis_Buffer buffer) {
QJsonDocument document = QJsonDocument::fromJson( QJsonDocument document = QJsonDocument::fromJson(QByteArray((const char*)buffer.data, buffer.size));
QByteArray((const char *)buffer.data, buffer.size));
for (auto boneObj : document.object()["Data"].toArray()) { for (auto boneObj : document.object()["Data"].toArray()) {
QJsonArray matrix = boneObj.toObject()["Matrix"].toArray(); QJsonArray matrix = boneObj.toObject()["Matrix"].toArray();
QString boneName = boneObj.toObject()["Name"].toString(); 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++) { for (int i = 0; i < skeleton->num_bones; i++) {
if (std::string_view{skeleton->bones[i].name} == if (std::string_view{skeleton->bones[i].name} == boneName.toStdString()) {
boneName.toStdString()) { auto& data = boneData[i];
auto &data = boneData[i];
data.deformRaceMatrix = actualMatrix; data.deformRaceMatrix = actualMatrix;
} }
@ -394,7 +393,7 @@ void MDLPart::reloadRenderer() {
} }
void MDLPart::reloadBoneData() { void MDLPart::reloadBoneData() {
if(skeleton) { if (skeleton) {
if (!firstTimeSkeletonDataCalculated) { if (!firstTimeSkeletonDataCalculated) {
if (boneData.empty()) { if (boneData.empty()) {
boneData.resize(skeleton->num_bones); boneData.resize(skeleton->num_bones);
@ -402,7 +401,7 @@ void MDLPart::reloadBoneData() {
calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr); calculateBoneInversePose(*skeleton, *skeleton->root_bone, nullptr);
for (auto &bone : boneData) { for (auto& bone : boneData) {
bone.inversePose = glm::inverse(bone.inversePose); bone.inversePose = glm::inverse(bone.inversePose);
} }
firstTimeSkeletonDataCalculated = true; firstTimeSkeletonDataCalculated = true;
@ -411,12 +410,13 @@ void MDLPart::reloadBoneData() {
// update data // update data
calculateBone(*skeleton, *skeleton->root_bone, nullptr); 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 // we want to map the actual affected bones to bone ids
std::map<int, int> boneMapping; std::map<int, int> boneMapping;
for (int i = 0; i < model.model.num_affected_bones; i++) { for (int i = 0; i < model.model.num_affected_bones; i++) {
for (int k = 0; k < skeleton->num_bones; k++) { 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; 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; RenderMaterial newMaterial;
for (int i = 0; i < material.num_textures; i++) { 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]; char type = t[t.length() - 5];
switch(type) { switch (type) {
case 'm': { 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); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
newMaterial.multiTexture = new RenderTexture(tex); newMaterial.multiTexture = new RenderTexture(tex);
} }
case 'd': { 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); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
newMaterial.diffuseTexture = new RenderTexture(tex); newMaterial.diffuseTexture = new RenderTexture(tex);
} } break;
break;
case 'n': { 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); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
newMaterial.normalTexture = new RenderTexture(tex); newMaterial.normalTexture = new RenderTexture(tex);
} } break;
break;
case 's': { 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); auto tex = renderer->addTexture(texture.width, texture.height, texture.rgba, texture.rgba_size);
newMaterial.specularTexture = new RenderTexture(tex); newMaterial.specularTexture = new RenderTexture(tex);
} } break;
break;
default: default:
qDebug() << "unhandled type" << type; qDebug() << "unhandled type" << type;
break; break;
@ -488,8 +485,9 @@ void MDLPart::calculateBoneInversePose(physis_Skeleton& skeleton, physis_Bone& b
boneData[bone.index].inversePose = parentMatrix * local; boneData[bone.index].inversePose = parentMatrix * local;
for(int i = 0; i < skeleton.num_bones; i++) { 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}) { 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); 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) { void MDLPart::calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone) {
const glm::mat4 parent_matrix = const glm::mat4 parent_matrix =
parent_bone == nullptr ? glm::mat4(1.0f) parent_bone == nullptr ? glm::mat4(1.0f) : boneData[parent_bone->index].localTransform;
: boneData[parent_bone->index].localTransform;
glm::mat4 local = glm::mat4(1.0f); glm::mat4 local = glm::mat4(1.0f);
local = glm::translate( local = glm::translate(local, glm::vec3(bone.position[0], bone.position[1], bone.position[2]));
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::mat4_cast(glm::quat(bone.rotation[3], bone.rotation[0], local = glm::scale(local, glm::vec3(bone.scale[0], bone.scale[1], bone.scale[2]));
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].localTransform = parent_matrix * local;
boneData[bone.index].finalTransform = boneData[bone.index].finalTransform =
boneData[bone.index].localTransform * boneData[bone.index].localTransform * boneData[bone.index].deformRaceMatrix * boneData[bone.index].inversePose;
boneData[bone.index].deformRaceMatrix *
boneData[bone.index].inversePose;
for (int i = 0; i < skeleton.num_bones; i++) { for (int i = 0; i < skeleton.num_bones; i++) {
if (skeleton.bones[i].parent_bone != nullptr && if (skeleton.bones[i].parent_bone != nullptr &&
std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{skeleton.bones[i].parent_bone->name} == std::string_view{bone.name}) {
std::string_view{bone.name}) {
calculateBone(skeleton, skeleton.bones[i], &bone); calculateBone(skeleton, skeleton.bones[i], &bone);
} }
} }

View file

@ -10,66 +10,71 @@ struct GameData;
class VulkanWindow; class VulkanWindow;
class StandaloneWindow; class StandaloneWindow;
class FileCache;
class MDLPart : public QWidget { class MDLPart : public QWidget {
Q_OBJECT Q_OBJECT
public: 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 lastX = -1;
int lastY = -1; int lastY = -1;
enum class CameraMode { None, Orbit, Move }; enum class CameraMode {
None,
Orbit,
Move
};
CameraMode cameraMode = CameraMode::None; CameraMode cameraMode = CameraMode::None;
float pitch = 0.0f; float pitch = 0.0f;
float yaw = 0.0f; float yaw = 0.0f;
float cameraDistance = 5.0f; float cameraDistance = 5.0f;
glm::vec3 position{0, 0, 0}; glm::vec3 position{0, 0, 0};
std::unique_ptr<physis_Skeleton> skeleton; std::unique_ptr<physis_Skeleton> skeleton;
struct BoneData { struct BoneData {
glm::mat4 localTransform, finalTransform, inversePose; glm::mat4 localTransform, finalTransform, inversePose;
glm::mat4 deformRaceMatrix{1.0f}; glm::mat4 deformRaceMatrix{1.0f};
}; };
std::vector<BoneData> boneData; std::vector<BoneData> boneData;
Q_SIGNALS: Q_SIGNALS:
void modelChanged(); void modelChanged();
void skeletonChanged(); void skeletonChanged();
public Q_SLOTS: public Q_SLOTS:
/// Clears all stored MDLs. /// Clears all stored MDLs.
void clear(); void clear();
/// Adds a new MDL with a list of materials used. /// Adds a new MDL with a list of materials used.
void addModel(physis_MDL mdl, std::vector<physis_Material> materials, void addModel(physis_MDL mdl, std::vector<physis_Material> materials, int lod);
int lod);
/// Sets the skeleton any skinned MDLs should bind to. /// Sets the skeleton any skinned MDLs should bind to.
void setSkeleton(physis_Skeleton skeleton); void setSkeleton(physis_Skeleton skeleton);
/// Sets the race deform matrices /// Sets the race deform matrices
void loadRaceDeformMatrices(physis_Buffer buffer); void loadRaceDeformMatrices(physis_Buffer buffer);
/// Clears the current skeleton. /// Clears the current skeleton.
void clearSkeleton(); void clearSkeleton();
void reloadBoneData(); void reloadBoneData();
void reloadRenderer(); void reloadRenderer();
private: 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); void calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const physis_Bone* parent_bone);
GameData* data = nullptr; GameData* data = nullptr;
FileCache& cache;
std::vector<RenderModel> models; std::vector<RenderModel> models;