1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-10-06 09:07:19 +02:00

rwviewer: view stored texts + fonts

This commit is contained in:
Anonymous Maarten 2017-09-07 19:03:18 +02:00
parent ee455bb157
commit 6d4b69b742
10 changed files with 497 additions and 17 deletions

View File

@ -68,9 +68,9 @@ RWGame::RWGame(Logger& log, int argc, char* argv[])
}
// Set up text renderer
renderer.text.setFontTexture(0, "pager");
renderer.text.setFontTexture(1, "font1");
renderer.text.setFontTexture(2, "font2");
renderer.text.setFontTexture(FONT_PAGER, "pager");
renderer.text.setFontTexture(FONT_PRICEDOWN, "font1");
renderer.text.setFontTexture(FONT_ARIAL, "font2");
debug.setDebugMode(btIDebugDraw::DBG_DrawWireframe |
btIDebugDraw::DBG_DrawConstraints |

View File

@ -6,23 +6,43 @@ find_package(Qt5Widgets REQUIRED)
add_executable(rwviewer WIN32
main.cpp
OpenGLCompat.h
ViewerWindow.hpp
ViewerWindow.cpp
models/ObjectListModel.hpp
models/ObjectListModel.cpp
models/DFFFramesTreeModel.hpp
models/DFFFramesTreeModel.cpp
models/TextModel.hpp
models/TextModel.cpp
views/ViewerInterface.hpp
views/ObjectViewer.hpp
views/ObjectViewer.cpp
views/ModelViewer.hpp
views/ModelViewer.cpp
views/TextViewer.hpp
views/TextViewer.cpp
views/WorldViewer.hpp
views/WorldViewer.cpp
views/ViewerInterface.hpp
views/ViewerInterface.cpp
ViewerWidget.cpp
ViewerWidget.hpp
ItemListModel.hpp
ItemListModel.cpp
ItemListWidget.hpp
ItemListWidget.cpp
IMGArchiveModel.hpp
IMGArchiveModel.cpp
widgets/ModelFramesWidget.hpp
widgets/ModelFramesWidget.cpp
AnimationListModel.hpp
AnimationListModel.cpp
AnimationListWidget.hpp
AnimationListWidget.cpp
)

View File

@ -2,12 +2,15 @@
#include <QFileDialog>
#include <QMouseEvent>
#include <engine/Animator.hpp>
#include <engine/GameData.hpp>
#include <engine/GameWorld.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <objects/CharacterObject.hpp>
#include <objects/InstanceObject.hpp>
#include <objects/VehicleObject.hpp>
#include <render/GameRenderer.hpp>
#include <render/ObjectRenderer.hpp>
#include <render/TextRenderer.hpp>
constexpr float kViewFov = glm::radians(90.0f);
@ -32,6 +35,7 @@ ViewCamera OrbitCamera (const glm::vec2& viewPort, const glm::vec2& viewAngles,
ViewerWidget::ViewerWidget(QOpenGLContext* context, QWindow* parent)
: QWindow(parent)
, context(context)
, textInfos()
, selectedFrame(nullptr)
, viewDistance(1.f)
, dragging(false)
@ -127,6 +131,13 @@ void ViewerWidget::drawWorld(GameRenderer& r) {
r.renderWorld(world(), vc, 0.f);
}
void ViewerWidget::drawText(GameRenderer& r) {
for(auto &textInfo : textInfos) {
_renderer->text.renderText(textInfo, false);
}
r.renderPostProcess();
}
void ViewerWidget::paintGL() {
glViewport(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio());
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
@ -141,7 +152,6 @@ void ViewerWidget::paintGL() {
glEnable(GL_DEPTH_TEST);
r.getRenderer()->invalidate();
r.setupRender();
@ -155,6 +165,9 @@ void ViewerWidget::paintGL() {
case Mode::World:
drawWorld(r);
break;
case Mode::Text:
drawText(r);
break;
}
}
@ -228,9 +241,18 @@ void ViewerWidget::showObject(quint16 item) {
}
}
void ViewerWidget::clearText() {
textInfos.clear();
}
void ViewerWidget::showText(const TextRenderer::TextInfo &ti) {
textInfos.push_back(ti);
}
void ViewerWidget::showModel(ClumpPtr model) {
_viewMode = Mode::Model;
_model = model;
textInfos.clear();
}
void ViewerWidget::selectFrame(ModelFrame* frame) {
@ -248,7 +270,7 @@ void ViewerWidget::exportModel() {
if( it != world()->objectTypes.end() ) {
for( auto& archive : world()->data.archives ) {
for(size_t i = 0; i < archive.second.getAssetCount(); ++i) {
auto& assetI = archive.second.getAssetInfoByIndex(i);
auto& assetI = archive.second.getAssetInfoByIndex(i);;
std::string q(assetI.name);
std::transform(q.begin(), q.end(), q.begin(), ::tolower);
if( q.find(it->second->modelName) != q.npos ) {

View File

@ -6,8 +6,9 @@
#include <engine/GameWorld.hpp>
#include <gl/DrawBuffer.hpp>
#include <gl/GeometryBuffer.hpp>
#include <glm/glm.hpp>
#include <loaders/LoaderIFP.hpp>
#include <render/TextRenderer.hpp>
#include <glm/glm.hpp>
// Prevent Qt from conflicting with glLoadGen on macOS
#include "OpenGLCompat.h"
@ -16,6 +17,7 @@
class GameRenderer;
class Clump;
class ViewerWidget : public QWindow {
Q_OBJECT
public:
@ -26,6 +28,8 @@ public:
Model,
//! View loaded instances, \see showWorld();
World,
//! View text strings, \see showText
Text,
};
ViewerWidget(QOpenGLContext* context, QWindow* parent);
@ -54,6 +58,8 @@ public:
public slots:
void showObject(quint16 item);
void showModel(ClumpPtr model);
void clearText();
void showText(const TextRenderer::TextInfo &ti);
void selectFrame(ModelFrame* frame);
void exportModel();
@ -78,6 +84,7 @@ protected:
GameWorld* _world = nullptr;
GameRenderer* _renderer = nullptr;
std::vector<TextRenderer::TextInfo> textInfos;
ClumpPtr _model;
ModelFrame* selectedFrame = nullptr;
GameObject* _object = nullptr;
@ -103,6 +110,7 @@ protected:
void drawModel(GameRenderer& r, ClumpPtr& model);
void drawObject(GameRenderer& r, GameObject* object);
void drawWorld(GameRenderer& r);
void drawText(GameRenderer& r);
};

View File

@ -3,6 +3,7 @@
#include "views/ModelViewer.hpp"
#include "views/ObjectViewer.hpp"
#include "views/WorldViewer.hpp"
#include "views/TextViewer.hpp"
#include <engine/GameState.hpp>
#include <engine/GameWorld.hpp>
@ -42,14 +43,17 @@ void ViewerWindow::createMenus() {
for (int i = 0; i < MaxRecentGames; ++i) {
QAction* r = file->addAction("");
recentGames.append(r);
connect(r, SIGNAL(triggered()), SLOT(openRecent()));
connect(r, &QAction::triggered, this, [r, this]() {
QString recentGame = r->data().toString();
loadGame(recentGame);
});
}
recentSep = file->addSeparator();
auto ex = file->addAction("E&xit");
ex->setShortcut(QKeySequence::Quit);
connect(ex, SIGNAL(triggered()), QApplication::instance(),
SLOT(closeAllWindows()));
connect(ex, &QAction::triggered,
QApplication::instance(), &QApplication::closeAllWindows);
mb->addMenu("&Data");
@ -94,6 +98,10 @@ void ViewerWindow::createDefaultViews() {
views->addTab(worldView, "World");
connect(this, &ViewerWindow::gameLoaded, worldView, &WorldViewer::showData);
auto textView = new TextViewer(this);
views->addTab(textView, "Texts");
connect(this, &ViewerWindow::gameLoaded, textView, &TextViewer::showData);
setCentralWidget(views);
}
@ -143,6 +151,10 @@ void ViewerWindow::loadGame(const QString& path) {
gameWorld->data->load();
renderer->text.setFontTexture(FONT_PAGER, "pager");
renderer->text.setFontTexture(FONT_PRICEDOWN, "font1");
renderer->text.setFontTexture(FONT_ARIAL, "font2");
gameLoaded(gameWorld.get(), renderer.get());
QSettings settings("OpenRW", "rwviewer");
@ -155,13 +167,6 @@ void ViewerWindow::loadGame(const QString& path) {
updateRecentGames();
}
void ViewerWindow::openRecent() {
QAction* r = qobject_cast<QAction*>(sender());
if (r) {
loadGame(r->data().toString());
}
}
void ViewerWindow::showObjectModel(uint16_t) {
#pragma message("implement me")
}

View File

@ -49,7 +49,6 @@ signals:
void gameLoaded(GameWorld*, GameRenderer*);
private slots:
void openRecent();
void showObjectModel(uint16_t object);
private:

View File

@ -0,0 +1,76 @@
#include "TextModel.hpp"
#include <iostream>
#include <QBrush>
TextModel::TextModel(QObject *parent)
: QAbstractTableModel(parent), m_font(FONT_PAGER) {
}
void TextModel::setData(const TextMapType &textMap) {
beginResetModel();
m_textMap = textMap;
endResetModel();
}
int TextModel::rowCount(const QModelIndex &) const {
return m_textMap.keys.size();
}
int TextModel::columnCount(const QModelIndex &) const {
return m_textMap.languages.size();
}
const GameString &TextModel::lookupIndex(const QModelIndex &index) const {
const auto &language = m_textMap.languages.at(index.column());
const auto &key = m_textMap.keys.at(index.row());
return m_textMap.map_lang_key_tran.at(language).at(key);
}
QVariant TextModel::data(const QModelIndex &index, int role) const {
switch (role) {
case Qt::DisplayRole:
try {
const auto &gameText = this->lookupIndex(index);
auto gameString = GameStringUtil::toString(gameText, m_font);
return QString::fromStdString(gameString);
} catch (const std::out_of_range &) {
return QVariant::Invalid;
} catch (...) {
throw;
}
case Qt::BackgroundRole:
try {
this->lookupIndex(index);
return QVariant::Invalid;
} catch (const std::out_of_range &) {
return QBrush(Qt::red);
}
break;
default:
return QVariant::Invalid;
}
}
QVariant TextModel::headerData(int section, Qt::Orientation orientation, int role) const {
switch (role) {
case Qt::DisplayRole:
switch (orientation) {
case Qt::Horizontal:
return QString::fromStdString(m_textMap.languages[section]);
case Qt::Vertical:
return QString::fromStdString(m_textMap.keys[section]);
default:
return QVariant::Invalid;
}
default:
return QVariant::Invalid;
}
}
void TextModel::fontChanged(font_t font) {
beginResetModel();
m_font = font;
endResetModel();
}

View File

@ -0,0 +1,35 @@
#ifndef _TEXTMODEL_HPP_
#define _TEXTMODEL_HPP_
#include <string>
#include <map>
#include <vector>
#include <QAbstractTableModel>
#include <fonts/GameTexts.hpp>
struct TextMapType {
std::vector<std::string> languages;
std::vector<std::string> keys;
std::map<std::string, std::map<std::string, GameString>> map_lang_key_tran;
};
class TextModel : public QAbstractTableModel {
Q_OBJECT
public:
TextModel(QObject *parent = nullptr);
void setData(const TextMapType &textMap);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
const GameString &lookupIndex(const QModelIndex &index) const;
public slots:
void fontChanged(font_t font);
private:
font_t m_font;
TextMapType m_textMap;
};
#endif

View File

@ -0,0 +1,251 @@
#include "TextViewer.hpp"
#include <render/TextRenderer.hpp>
#include <rw/filesystem.hpp>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iostream>
#include <fonts/GameTexts.hpp>
#include <loaders/LoaderGXT.hpp>
#include <models/TextModel.hpp>
#include <boost/filesystem.hpp>
#include <render/GameRenderer.hpp>
#include <QGroupBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QItemSelection>
#include <QLineEdit>
#include <QModelIndex>
#include <QPushButton>
#include <QRadioButton>
#include <QRegExp>
#include <QRegExpValidator>
#include <QSpinBox>
#include <QSplitter>
#include <QTableView>
#include <QTextEdit>
void TextTableView::selectionChanged(const QItemSelection &selected, const QItemSelection &) {
if (!selected.size())
return;
auto index = selected.indexes()[0];
if (!index.isValid())
return;
auto *textModel = dynamic_cast<TextModel *>(this->model());
if (!textModel) {
return;
}
try {
auto gameString = textModel->lookupIndex(index);
emit gameStringChanged(gameString);
} catch (std::out_of_range &) {
return;
}
}
TextViewer::TextViewer(QWidget* parent, Qt::WindowFlags f)
: ViewerInterface(parent, f) {
auto dataLayout = new QVBoxLayout;
auto splitter = new QSplitter;
splitter->setChildrenCollapsible(false);
splitter->setOrientation(Qt::Horizontal);
viewerWidget = createViewer();
textModel = new TextModel;
textTable = new TextTableView;
textTable->setModel(textModel);
textTable->setSelectionMode(QAbstractItemView::SingleSelection);
connect(textTable, &TextTableView::gameStringChanged, this, &TextViewer::onGameStringChange);
dataLayout->addWidget(textTable);
auto propLayout = new QHBoxLayout;
connect(this, &TextViewer::fontChanged, this, &TextViewer::onFontChange);
auto groupBox = new QGroupBox;
auto *groupBoxLayout = new QHBoxLayout;
auto *radioFont1 = new QRadioButton(tr("Pager"));
connect(radioFont1, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_PAGER);});
groupBoxLayout->addWidget(radioFont1);
auto *radioFont2 = new QRadioButton(tr("Pricedown"));
connect(radioFont2, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_PRICEDOWN);});
groupBoxLayout->addWidget(radioFont2);
auto *radioFont3 = new QRadioButton(tr("Arial"));
connect(radioFont3, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_ARIAL);});
groupBoxLayout->addWidget(radioFont3);
groupBox->setLayout(groupBoxLayout);
groupBox->setProperty("border", "2px solid gray");
propLayout->addWidget(groupBox);
radioFont1->click();
auto textSizeLayout = new QFormLayout;
auto textSizeSpinBox = new QSpinBox;
textSizeSpinBox->setMinimum(1);
textSizeSpinBox->setMaximum(50);
connect(textSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TextViewer::onFontSizeChange);
textSizeSpinBox->setValue(20);
textSizeLayout->addRow(tr("Font size"), textSizeSpinBox);
propLayout->addLayout(textSizeLayout);
propLayout->addStretch();
dataLayout->addLayout(propLayout);
hexLineEdit = new QLineEdit;
hexLineEdit->setReadOnly(true);
hexLineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9A-F]*")));
dataLayout->addWidget(hexLineEdit);
textEdit = new QTextEdit;
dataLayout->addWidget(textEdit);
auto dataWidget = new QWidget;
dataWidget->setLayout(dataLayout);
splitter->addWidget(dataWidget);
viewerWidget->setMode(ViewerWidget::Mode::Text);
splitter->addWidget(QWidget::createWindowContainer(viewerWidget));
auto mainLayout = new QHBoxLayout;
mainLayout->addWidget(splitter);
setLayout(mainLayout);
connect(textSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TextViewer::onFontSizeChange);
connect(textEdit, &QTextEdit::textChanged, this, &TextViewer::onStringChange);
}
void TextViewer::onFontChange(font_t font) {
currentFont = font;
emit textModel->fontChanged(font);
updateRender();
}
void TextViewer::onFontSizeChange(int size) {
currentFontSize = size;
updateRender();
}
void TextViewer::onStringChange() {
auto string = textEdit->toPlainText();
auto newGameString = GameStringUtil::fromString(string.toStdString(), currentFont);
onGameStringChange(newGameString);
}
void TextViewer::onGameStringChange(const GameString &gameString) {
if (!currentGameString.compare(gameString)) {
return;
}
currentGameString = gameString;
std::ostringstream oss;
oss << std::hex;
for (auto c : gameString) {
oss << std::setw(sizeof(gameString[0])) << std::setfill('0')
<< int(c) << " ";
}
auto newHexText = QString::fromStdString(oss.str());
if (hexLineEdit->text().compare(newHexText)) {
hexLineEdit->setText(newHexText);
}
auto newText = QString::fromStdString(GameStringUtil::toString(gameString, currentFont));
if (textEdit->toPlainText().compare(newText)) {
textEdit->setText(newText);
}
updateRender();
}
void TextViewer::updateRender() {
viewerWidget->clearText();
const int ROW_STRIDE = currentFontSize * 1.2;
const int COL_STRIDE = currentFontSize * 1.2;
{
TextRenderer::TextInfo ti;
ti.font = currentFont;
ti.size = currentFontSize;
ti.baseColour = glm::u8vec3(255);
ti.backgroundColour = glm::u8vec4(0, 0, 0, 0);
ti.align = TextRenderer::TextInfo::TextAlignment::Left;
ti.wrapX = 0;
ti.text = currentGameString;
ti.screenPosition = glm::vec2(10, 10);
viewerWidget->showText(ti);
}
{
TextRenderer::TextInfo ti;
ti.font = currentFont;
ti.size = currentFontSize;
ti.baseColour = glm::u8vec3(255);
ti.backgroundColour = glm::u8vec4(0, 0, 0, 0);
ti.align = TextRenderer::TextInfo::TextAlignment::Left;
ti.wrapX = 0;
for(GameStringChar c=0x20; c<0xb2; ++c) {
unsigned column = c % 0x10;
unsigned row = (c / 0x10) - 2 + 3; /* +3 to offset first line*/
ti.text = c;
ti.screenPosition = glm::vec2(10 + (column * COL_STRIDE), 10 + (row * ROW_STRIDE));
viewerWidget->showText(ti);
}
}
}
void TextViewer::worldChanged() {
auto textNames = getFontTextureNames();
TextMapType textMap;
LoaderGXT loader;
std::set<std::string> keys;
for(const auto &textName : textNames) {
GameTexts texts;
auto handle = world()->data->index.openFile(textName);
loader.load(texts, handle);
const auto &language = textName;
textMap.languages.push_back(language);
const auto &stringTable = texts.getStringTable();
for (const auto &tableItem : stringTable) {
keys.insert(tableItem.first);
textMap.map_lang_key_tran[language][tableItem.first] = tableItem.second;
}
}
textMap.keys.resize(keys.size());
std::move(keys.begin(), keys.end(), textMap.keys.begin());
textModel->setData(textMap);
}
std::vector<std::string> TextViewer::getFontTextureNames() {
const auto &gameDataPath = rwfs::path(world()->data->getDataPath());
rwfs::path textPath;
for (const rwfs::path &p : rwfs::directory_iterator(gameDataPath)) {
if (!rwfs::is_directory(p)) {
continue;
}
std::string filename = p.filename().string();
std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);
if (!filename.compare("text")) {
textPath = p;
break;
}
}
if (!textPath.string().length()) {
throw std::runtime_error("text directory not found in gamedata path");
}
std::vector<std::string> names;
for (const rwfs::path &p : rwfs::directory_iterator(textPath)) {
// auto langName = p.lexically_relative(gameDataPath).string();
auto langName = p.filename().string();
std::transform(langName.begin(), langName.end(), langName.begin(), ::tolower);
names.push_back(langName);
}
return names;
}

View File

@ -0,0 +1,64 @@
#ifndef _TEXTVIEWER_HPP_
#define _TEXTVIEWER_HPP_
#include "ViewerInterface.hpp"
#include <fonts/GameTexts.hpp>
#include <rw/filesystem.hpp>
#include <QTableView>
#include <vector>
class TextModel;
class ViewerWidget;
class QLayout;
class QItemSelection;
class QLineEdit;
class QTextEdit;
class QModelIndex;
class QString;
class QWidget;
class TextTableView : public QTableView {
Q_OBJECT
protected slots:
virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
signals:
void gameStringChanged(const GameString &string);
};
class TextViewer : public ViewerInterface {
Q_OBJECT
TextModel *textModel;
TextTableView *textTable;
ViewerWidget *viewerWidget;
QLineEdit *hexLineEdit;
QTextEdit *textEdit;
virtual void worldChanged() override;
GameString currentGameString;
font_t currentFont;
int currentFontSize;
void updateRender();
void setGameString(const GameString &gameString);
std::vector<std::string> getFontTextureNames();
public:
TextViewer(QWidget* parent = 0, Qt::WindowFlags f = 0);
signals:
void fontChanged(font_t font);
private slots:
void onGameStringChange(const GameString &gameString);
void onStringChange();
void onFontChange(size_t font);
void onFontSizeChange(int font);
};
#endif