1
Fork 0
mirror of https://github.com/redstrate/Novus.git synced 2025-04-24 21:07:46 +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)
find_package(Qt5 COMPONENTS Core Widgets CONFIG REQUIRED)
find_package(Qt5 COMPONENTS Core Widgets Concurrent CONFIG REQUIRED)
include(FetchContent)

View file

@ -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)

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}
Qt5::Core
Qt5::Widgets
Qt5::Concurrent
renderer
assimp::assimp
magic_enum

View file

@ -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();

View file

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

View file

@ -1,5 +1,6 @@
#pragma once
#include "filecache.h"
#include "mdlpart.h"
#include <QComboBox>
#include <QWidget>
@ -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<std::pair<Race, Subrace>> supportedRaces() const;
@ -92,4 +93,5 @@ private:
MDLPart* mdlPart = nullptr;
GameData* data;
FileCache& cache;
};

View file

@ -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;
};

View file

@ -1,5 +1,6 @@
#pragma once
#include "filecache.h"
#include "gearview.h"
#include <QPushButton>
#include <QWidget>
@ -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();

View file

@ -9,7 +9,7 @@
#include <QRadioButton>
#include <QVBoxLayout>
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);

View file

@ -1,10 +1,10 @@
#include "gearlistmodel.h"
#include <QDebug>
#include <QtConcurrent>
#include <magic_enum.hpp>
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<physis_EXD>(this);
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++) {
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<int> pages;
for(int i = 0; i < exh->page_count; i++) {
pages.push_back(i);
}
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>()) {
slotNames.push_back(slotName.data());
}
endResetModel();
rootItem = new TreeInformation();
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 {
@ -161,4 +133,51 @@ std::optional<GearInfo> 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<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"

View file

@ -1,11 +1,13 @@
#include "gearview.h"
#include "magic_enum.hpp"
#include <QDebug>
#include <QVBoxLayout>
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);
}
}

View file

@ -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();
}

View file

@ -4,10 +4,11 @@
#include <QPushButton>
#include <QVBoxLayout>
#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);

View file

@ -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})

View file

@ -17,100 +17,100 @@
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/type_ptr.inl>
#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<QMouseEvent*>(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<QMouseEvent*>(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<QMouseEvent*>(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<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 * 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<QWheelEvent*>(e);
part->lastX = mouseEvent->x();
part->lastY = mouseEvent->y();
}
} break;
case QEvent::Wheel:
{
auto scrollEvent = dynamic_cast<QWheelEvent*>(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<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();
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<physis_Material> 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<int, int> 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);
}
}

View file

@ -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<physis_Skeleton> skeleton;
std::unique_ptr<physis_Skeleton> 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> boneData;
std::vector<BoneData> 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<physis_Material> materials,
int lod);
/// Adds a new MDL with a list of materials used.
void addModel(physis_MDL mdl, std::vector<physis_Material> 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<RenderModel> models;