diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b4c10c..6e84bb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ if(NOT PRERELEASE_STRING) set(PRERELEASE_STRING "") endif() -project("Deling" VERSION 2.0.0 LANGUAGES CXX +project("Deling" VERSION 1.0.0 LANGUAGES CXX DESCRIPTION "Final Fantasy VIII field archive editor" HOMEPAGE_URL "https://github.com/myst6re/deling" ) @@ -170,6 +170,26 @@ set(PROJECT_SOURCES "src/FsPreviewWidget.h" "src/FsWidget.cpp" "src/FsWidget.h" + "src/game/worldmap/Map.cpp" + "src/game/worldmap/Map.h" + "src/game/worldmap/MapBlock.cpp" + "src/game/worldmap/MapBlock.h" + "src/game/worldmap/MapPoly.cpp" + "src/game/worldmap/MapPoly.h" + "src/game/worldmap/MapSegment.cpp" + "src/game/worldmap/MapSegment.h" + "src/game/worldmap/ObjFile.cpp" + "src/game/worldmap/ObjFile.h" + "src/game/worldmap/TexlFile.cpp" + "src/game/worldmap/TexlFile.h" + "src/game/worldmap/WmArchive.cpp" + "src/game/worldmap/WmArchive.h" + "src/game/worldmap/WmEncounter.cpp" + "src/game/worldmap/WmEncounter.h" + "src/game/worldmap/WmsetFile.cpp" + "src/game/worldmap/WmsetFile.h" + "src/game/worldmap/WmxFile.cpp" + "src/game/worldmap/WmxFile.h" "src/GZIP.cpp" "src/GZIP.h" "src/HexLineEdit.cpp" @@ -201,6 +221,8 @@ set(PROJECT_SOURCES "src/OrientationWidget.h" "src/PlainTextEdit.cpp" "src/PlainTextEdit.h" + "src/Poly.cpp" + "src/Poly.h" "src/PreviewWidget.cpp" "src/PreviewWidget.h" "src/ProgressWidget.cpp" @@ -263,6 +285,10 @@ set(PROJECT_SOURCES "src/widgets/TdwWidget2.h" "src/widgets/WalkmeshWidget.cpp" "src/widgets/WalkmeshWidget.h" + "src/widgets/WorldmapGLWidget.cpp" + "src/widgets/WorldmapGLWidget.h" + "src/widgets/WorldmapWidget.cpp" + "src/widgets/WorldmapWidget.h" ) if(APPLE) diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index 8bb8db4..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/FF8DiscArchive.cpp b/src/FF8DiscArchive.cpp index 412f45d..b7452a0 100644 --- a/src/FF8DiscArchive.cpp +++ b/src/FF8DiscArchive.cpp @@ -250,6 +250,11 @@ const FF8DiscFile &FF8DiscArchive::fieldBinFile() return rootFile(2); } +const FF8DiscFile &FF8DiscArchive::worldBinFile() +{ + return rootFile(26); +} + const QList &FF8DiscArchive::fieldDirectory() { if (!fieldFiles.isEmpty() || 2 >= rootCount()) return fieldFiles; diff --git a/src/FF8DiscArchive.h b/src/FF8DiscArchive.h index df154ec..8401b48 100644 --- a/src/FF8DiscArchive.h +++ b/src/FF8DiscArchive.h @@ -55,6 +55,7 @@ class FF8DiscArchive : public IsoArchive const FF8DiscFile &fieldBinFile(); // QList worldmapDirectory(); // FF8DiscFile worldmapFile(int id); + const FF8DiscFile &worldBinFile(); const QList &fieldDirectory(); int fieldCount(); const FF8DiscFile &fieldFile(int id); diff --git a/src/FieldArchive.cpp b/src/FieldArchive.cpp index 1424955..e1f2f65 100644 --- a/src/FieldArchive.cpp +++ b/src/FieldArchive.cpp @@ -369,9 +369,9 @@ QMap FieldArchive::searchAllOpcodeTypes() const return ret; } -QList FieldArchive::searchAllSavePoints() const +QList FieldArchive::searchAllSavePoints() const { - QList ret; + QList ret; for (Field *field: fields) { if (field->hasJsmFile()) { diff --git a/src/FieldArchive.h b/src/FieldArchive.h index 0501835..681dee5 100644 --- a/src/FieldArchive.h +++ b/src/FieldArchive.h @@ -19,7 +19,7 @@ #include #include "files/JsmFile.h" -#include "files/CaFile.h" +#include "Poly.h" class Field; struct ArchiveObserver; @@ -63,7 +63,7 @@ class FieldArchive QMap > searchAllBattles() const; QMultiMap searchAllMoments() const; QMap searchAllOpcodeTypes() const; - QList searchAllSavePoints() const; + QList searchAllSavePoints() const; QStringList fieldList() const; const QStringList &mapList() const; void setMapList(const QStringList &mapList); diff --git a/src/FsArchive.cpp b/src/FsArchive.cpp index b5cf3f2..6c5bf41 100644 --- a/src/FsArchive.cpp +++ b/src/FsArchive.cpp @@ -293,9 +293,11 @@ FsArchive::FsArchive(const QByteArray &fl_data, const QByteArray &fi_data) } FsArchive::FsArchive(const QString &path) - : fromFile(true), _isOpen(false) + : fromFile(false), _isOpen(false) { - load(path); + if (!path.isEmpty()) { + open(path); + } } FsArchive::~FsArchive() @@ -947,7 +949,7 @@ bool FsArchive::load(const QByteArray &fl_data, const QByteArray &fi_data) return true; } -bool FsArchive::load(const QString &path) +bool FsArchive::open(const QString &path) { if (_isOpen) return false; diff --git a/src/FsArchive.h b/src/FsArchive.h index ed73ef1..275d67b 100644 --- a/src/FsArchive.h +++ b/src/FsArchive.h @@ -79,9 +79,10 @@ class FsArchive // FsArchive(); FsArchive(const QByteArray &fl_data, const QByteArray &fi_data); - explicit FsArchive(const QString &path); + explicit FsArchive(const QString &path = QString()); virtual ~FsArchive(); - + + bool open(const QString &path); void addFile(const QString &path, FiCompression compression); FsHeader *getFile(const QString &path) const; void fileToTheEnd(const QString &path, QByteArray &fs_data); @@ -143,7 +144,6 @@ class FsArchive static QStringList listDirsRec(QDir *sourceDir); bool load(const QByteArray &fl_data, const QByteArray &fi_data); - bool load(const QString &path); static bool searchData(const QMultiMap &headers, QFile *fs, const QByteArray &data, quint32 &pos); diff --git a/src/Poly.cpp b/src/Poly.cpp new file mode 100644 index 0000000..728ecb2 --- /dev/null +++ b/src/Poly.cpp @@ -0,0 +1,123 @@ +#include "Poly.h" + +Poly::Poly(int count, const QList &vertices, + const QList &normals, const QList &colors, + const QList &texCoords) : + _count(count), _normals(normals) +{ + setVertices(vertices, colors, texCoords); +} + +Poly::Poly(int count, const QList &vertices, + const QList &normals, const QRgb &color, + const QList &texCoords) : + _count(count), _normals(normals) +{ + setVertices(vertices, color, texCoords); +} + +void Poly::setVertices(const QList &vertices, const QList &colors, + const QList &texCoords) +{ + _vertices = vertices; + _colors = colors; + _texCoords = texCoords; +} + +void Poly::setVertices(const QList &vertices, const QRgb &color, + const QList &texCoords) +{ + _vertices = vertices; + _colors.clear(); + _colors.append(color); + _texCoords = texCoords; +} + +const Vertex &Poly::vertex(quint8 id) const +{ + return _vertices.at(id); +} + +const Vertex &Poly::normal(quint8 id) const +{ + return _normals.at(id); +} + +const QRgb &Poly::color() const +{ + return _colors.first(); +} + +QRgb Poly::color(quint8 id) const +{ + return _colors.value(id, _colors.first()); +} + +const TexCoord &Poly::texCoord(quint8 id) const +{ + return _texCoords.at(id); +} + +void Poly::setTexCoord(quint8 id, const TexCoord &texCoord) +{ + _texCoords.replace(id, texCoord); +} + +bool Poly::isMonochrome() const +{ + return _colors.size() == 1; +} + +bool Poly::hasTexture() const +{ + return !_texCoords.isEmpty(); +} + +QuadPoly::QuadPoly(const QList &vertices, + const QList &normals, const QList &colors, + const QList &texCoords) : + Poly(4, vertices, normals, colors, texCoords) +{ + // swapping the two last vertices for right OpenGL quad order + + _vertices.swapItemsAt(2, 3); + _normals.swapItemsAt(2, 3); + + if(colors.size() == 4) { + _colors.swapItemsAt(2, 3); + } + + if(!texCoords.isEmpty()) { + _texCoords.swapItemsAt(2, 3); + } +} + +QuadPoly::QuadPoly(const QList &vertices, + const QList &normals, const QRgb &color, + const QList &texCoords) : + Poly(4, vertices, normals, color, texCoords) +{ + // swapping the two last vertices for right OpenGL quad order + + _vertices.swapItemsAt(2, 3); + _normals.swapItemsAt(2, 3); + + if(!texCoords.isEmpty()) { + _texCoords.swapItemsAt(2, 3); + } +} + +TrianglePoly::TrianglePoly(const QList &vertices, + const QList &normals, + const QList &colors, + const QList &texCoords) : + Poly(3, vertices, normals, colors, texCoords) +{ +} + +TrianglePoly::TrianglePoly(const QList &vertices, + const QList &normals, const QRgb &color, + const QList &texCoords) : + Poly(3, vertices, normals, color, texCoords) +{ +} diff --git a/src/Poly.h b/src/Poly.h new file mode 100644 index 0000000..386fc9d --- /dev/null +++ b/src/Poly.h @@ -0,0 +1,102 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include + +struct Vertex { + qint16 x, y, z; +}; + +inline bool operator==(const Vertex &v1, const Vertex &v2) +{ + return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; +} + +inline size_t qHash(const Vertex &key, uint seed) +{ + return qHash(quint64(key.x | (key.y << 16)) | (quint64(key.z) << 32), seed); +} + +struct TexCoord { + quint8 x, y; +}; + +class Poly +{ +public: + Poly(int count, const QList &vertices, const QList &normals, + const QList &colors, + const QList &texCoords=QList()); + Poly(int count, const QList &vertices, const QList &normals, + const QRgb &color, + const QList &texCoords=QList()); + virtual ~Poly() {} + void setVertices(const QList &vertices, const QList &colors, + const QList &texCoords=QList()); + void setVertices(const QList &vertices, const QRgb &color, + const QList &texCoords=QList()); + inline int count() const { + return _count; + } + const Vertex &vertex(quint8 id) const; + const Vertex &normal(quint8 id) const; + inline const QList &vertices() const { + return _vertices; + } + inline const QList &normals() const { + return _normals; + } + const QRgb &color() const; + QRgb color(quint8 id) const; + const TexCoord &texCoord(quint8 id) const; + void setTexCoord(quint8 id, const TexCoord &texCoord); + inline const QList &texCoords() const { + return _texCoords; + } + bool isMonochrome() const; + bool hasTexture() const; +protected: + int _count; + QList _vertices, _normals; + QList _colors; + QList _texCoords; +}; + +class QuadPoly : public Poly +{ +public: + QuadPoly(const QList &vertices, const QList &normals, + const QList &colors, + const QList &texCoords=QList()); + QuadPoly(const QList &vertices, const QList &normals, + const QRgb &color, + const QList &texCoords=QList()); +}; + +class TrianglePoly : public Poly +{ +public: + TrianglePoly(const QList &vertices, const QList &normals, + const QList &colors, + const QList &texCoords=QList()); + TrianglePoly(const QList &vertices, const QList &normals, + const QRgb &color, + const QList &texCoords=QList()); +}; diff --git a/src/VertexWidget.cpp b/src/VertexWidget.cpp index 4655d78..94d2f1a 100644 --- a/src/VertexWidget.cpp +++ b/src/VertexWidget.cpp @@ -41,16 +41,16 @@ VertexWidget::VertexWidget(QWidget *parent) : connect(z, SIGNAL(valueChanged(int)), SLOT(emitValuesChanged())); } -Vertex_s VertexWidget::values() const +Vertex VertexWidget::values() const { - Vertex_s v; + Vertex v; v.x = x->value(); v.y = y->value(); v.z = z->value(); return v; } -void VertexWidget::setValues(const Vertex_s &v) +void VertexWidget::setValues(const Vertex &v) { dontEmit = true; x->setValue(v.x); diff --git a/src/VertexWidget.h b/src/VertexWidget.h index a0c1d0a..2b59b44 100644 --- a/src/VertexWidget.h +++ b/src/VertexWidget.h @@ -18,21 +18,21 @@ #pragma once #include -#include "files/CaFile.h" +#include "Poly.h" class VertexWidget : public QWidget { Q_OBJECT public: explicit VertexWidget(QWidget *parent = nullptr); - Vertex_s values() const; - void setValues(const Vertex_s &v); + Vertex values() const; + void setValues(const Vertex &v); bool isReadOnly() const; void setReadOnly(bool ro); private slots: void emitValuesChanged(); signals: - void valuesChanged(const Vertex_s &v); + void valuesChanged(const Vertex &v); private: QSpinBox *x, *y, *z; bool dontEmit; diff --git a/src/WalkmeshGLWidget.cpp b/src/WalkmeshGLWidget.cpp index 3530fac..047700a 100644 --- a/src/WalkmeshGLWidget.cpp +++ b/src/WalkmeshGLWidget.cpp @@ -22,7 +22,7 @@ WalkmeshGLWidget::WalkmeshGLWidget(QWidget *parent) distance(0.0), xRot(0.0f), yRot(0.0f), zRot(0.0f), xTrans(0.0f), yTrans(0.0f), transStep(360.0f), lastKeyPressed(-1), camID(0), _selectedTriangle(-1), _selectedDoor(-1), _selectedGate(-1), - _lineToDrawPoint1(Vertex_s()), _lineToDrawPoint2(Vertex_s()), + _lineToDrawPoint1(Vertex()), _lineToDrawPoint2(Vertex()), fovy(70.0), data(nullptr), curFrame(0), gpuRenderer(nullptr), _drawLine(false) { // setMouseTracking(true); @@ -462,7 +462,7 @@ void WalkmeshGLWidget::setSelectedGate(int gate) update(); } -void WalkmeshGLWidget::setLineToDraw(const Vertex_s vertex[2]) +void WalkmeshGLWidget::setLineToDraw(const Vertex vertex[2]) { _lineToDrawPoint1 = vertex[0]; _lineToDrawPoint2 = vertex[1]; diff --git a/src/WalkmeshGLWidget.h b/src/WalkmeshGLWidget.h index f04e454..08847c9 100644 --- a/src/WalkmeshGLWidget.h +++ b/src/WalkmeshGLWidget.h @@ -40,7 +40,7 @@ public slots: void setSelectedTriangle(int triangle); void setSelectedDoor(int door); void setSelectedGate(int gate); - void setLineToDraw(const Vertex_s vertex[2]); + void setLineToDraw(const Vertex vertex[2]); void clearLineToDraw(); private: void computeFov(); @@ -53,7 +53,7 @@ public slots: int _selectedTriangle; int _selectedDoor; int _selectedGate; - Vertex_s _lineToDrawPoint1, _lineToDrawPoint2; + Vertex _lineToDrawPoint1, _lineToDrawPoint2; double fovy; Field *data; QPoint moveStart; diff --git a/src/files/CaFile.h b/src/files/CaFile.h index f55dd50..fac2eea 100644 --- a/src/files/CaFile.h +++ b/src/files/CaFile.h @@ -18,13 +18,10 @@ #pragma once #include "files/File.h" - -struct Vertex_s { - qint16 x, y, z; -}; +#include "Poly.h" struct Camera { - Vertex_s camera_axis[3]; + Vertex camera_axis[3]; qint16 camera_axis2z;// copy (padding) qint32 camera_position[3]; qint32 blank; diff --git a/src/files/IdFile.cpp b/src/files/IdFile.cpp index 23604d3..5acbcab 100644 --- a/src/files/IdFile.cpp +++ b/src/files/IdFile.cpp @@ -162,7 +162,7 @@ qint16 IdFile::unknown() const return _unknown; } -Vertex_sr IdFile::fromVertex_s(const Vertex_s &vertex_s) +Vertex_sr IdFile::fromVertex_s(const Vertex &vertex_s) { Vertex_sr vertex_sr; @@ -174,9 +174,9 @@ Vertex_sr IdFile::fromVertex_s(const Vertex_s &vertex_s) return vertex_sr; } -Vertex_s IdFile::toVertex_s(const Vertex_sr &vertex_sr) +Vertex IdFile::toVertex_s(const Vertex_sr &vertex_sr) { - Vertex_s vertex_s; + Vertex vertex_s; vertex_s.x = vertex_sr.x; vertex_s.y = vertex_sr.y; diff --git a/src/files/IdFile.h b/src/files/IdFile.h index 1d538cf..d3aaca8 100644 --- a/src/files/IdFile.h +++ b/src/files/IdFile.h @@ -18,7 +18,7 @@ #pragma once #include "files/File.h" -#include "files/CaFile.h" +#include "Poly.h" struct Vertex_sr { qint16 x, y, z, res;// res = Triangle[0].z (padding) @@ -52,8 +52,8 @@ class IdFile : public File void setAccess(int triangleID, const Access &access); bool hasUnknownData() const; qint16 unknown() const; - static Vertex_sr fromVertex_s(const Vertex_s &vertex_s); - static Vertex_s toVertex_s(const Vertex_sr &vertex_sr); + static Vertex_sr fromVertex_s(const Vertex &vertex_s); + static Vertex toVertex_s(const Vertex_sr &vertex_sr); private: QList triangles; QList _access; diff --git a/src/files/InfFile.cpp b/src/files/InfFile.cpp index afb2233..2e28567 100644 --- a/src/files/InfFile.cpp +++ b/src/files/InfFile.cpp @@ -72,7 +72,7 @@ bool InfFile::open(const QByteArray &inf) memcpy(&infStruct.cameraFocusHeight, &constInf[14], 2 + 10 * sizeof(Range)); for (int i = 0; i < 12; ++i) { - memcpy(&infStruct.gateways[i], &constInf[96 + i*24], 3 * sizeof(Vertex_s) + 2); + memcpy(&infStruct.gateways[i], &constInf[96 + i*24], 3 * sizeof(Vertex) + 2); quint16 val = infStruct.gateways[i].fieldId == 0x7FFF ? 0x7FFF : 0; infStruct.gateways[i].unknown1[0] = val; infStruct.gateways[i].unknown1[1] = val; @@ -99,7 +99,7 @@ bool InfFile::open(const QByteArray &inf) infStruct.screenRange[1] = infStruct.screenRange[0]; for (int i = 0; i < 12; ++i) { - memcpy(&infStruct.gateways[i], &constInf[24 + i*24], 3 * sizeof(Vertex_s) + 2); + memcpy(&infStruct.gateways[i], &constInf[24 + i*24], 3 * sizeof(Vertex) + 2); quint16 val = infStruct.gateways[i].fieldId == 0x7FFF ? 0x7FFF : 0; infStruct.gateways[i].unknown1[0] = val; infStruct.gateways[i].unknown1[1] = val; diff --git a/src/files/InfFile.h b/src/files/InfFile.h index 68e7985..4f7138b 100644 --- a/src/files/InfFile.h +++ b/src/files/InfFile.h @@ -18,7 +18,7 @@ #pragma once #include "files/File.h" -#include "files/CaFile.h" +#include "Poly.h" struct Range { qint16 top; @@ -28,15 +28,15 @@ struct Range { }; struct Gateway { - Vertex_s exitLine[2]; - Vertex_s destinationPoint; + Vertex exitLine[2]; + Vertex destinationPoint; quint16 fieldId; quint16 unknown1[4]; quint32 unknown2; }; struct Trigger { - Vertex_s trigger_line[2]; + Vertex trigger_line[2]; quint8 doorID; quint8 _blank[3]; }; diff --git a/src/files/JsmFile.cpp b/src/files/JsmFile.cpp index 860890a..30d206c 100644 --- a/src/files/JsmFile.cpp +++ b/src/files/JsmFile.cpp @@ -493,7 +493,7 @@ void JsmFile::searchDefaultBGStates(QMultiMap ¶ms) const } } -/*void JsmFile::searchLines(QMap &lines) const +/*void JsmFile::searchLines(QMap &lines) const { // qDebug() << "JsmFile::searchDefaultBGStates"; int nbGroup = scripts.nbGroup(), nbOpcode; @@ -1340,7 +1340,7 @@ void JsmFile::searchAllOpcodeTypes(QMap &ret/*, QMap &st } } /* -void JsmFile::searchAllSavePoints(QList &ret) +void JsmFile::searchAllSavePoints(QList &ret) { int nbOpcode = scripts.data().nbOpcode(); quint32 key; diff --git a/src/files/MskFile.cpp b/src/files/MskFile.cpp index e3f10a9..6aa736e 100644 --- a/src/files/MskFile.cpp +++ b/src/files/MskFile.cpp @@ -24,7 +24,7 @@ MskFile::MskFile() MskFile::~MskFile() { - for (Vertex_s *vertex: vertices) { + for (Vertex *vertex: vertices) { delete[] vertex; } } @@ -34,7 +34,7 @@ bool MskFile::open(const QByteArray &msk) const char *mskData = msk.constData(); quint32 count; - for (Vertex_s *vertex: vertices) { + for (Vertex *vertex: vertices) { delete[] vertex; } vertices.clear(); @@ -56,7 +56,7 @@ bool MskFile::open(const QByteArray &msk) } for (quint32 i = 0; i < count; ++i) { - Vertex_s *vertex = new Vertex_s[4]; + Vertex *vertex = new Vertex[4]; memcpy(vertex, &mskData[4 + i*24], 24); @@ -71,7 +71,7 @@ bool MskFile::save(QByteArray &msk) qint32 count = vertices.size(); msk.append((char *)&count, 4); - for (Vertex_s *vertex: vertices) { + for (Vertex *vertex: vertices) { msk.append((char *)&vertex, 24); } @@ -83,12 +83,12 @@ int MskFile::cameraPositionCount() const return vertices.size(); } -Vertex_s *MskFile::cameraPosition(int frame) const +Vertex *MskFile::cameraPosition(int frame) const { return vertices.value(frame, nullptr); } -void MskFile::setCameraPosition(int frame, Vertex_s camPos[4]) +void MskFile::setCameraPosition(int frame, Vertex camPos[4]) { if (frame < vertices.size()) { delete[] vertices.at(frame); @@ -96,7 +96,7 @@ void MskFile::setCameraPosition(int frame, Vertex_s camPos[4]) } } -void MskFile::insertCameraPosition(int frame, Vertex_s camPos[4]) +void MskFile::insertCameraPosition(int frame, Vertex camPos[4]) { vertices.insert(frame, camPos); } diff --git a/src/files/MskFile.h b/src/files/MskFile.h index 47fa262..b2bdbf2 100644 --- a/src/files/MskFile.h +++ b/src/files/MskFile.h @@ -18,7 +18,7 @@ #pragma once #include "files/File.h" -#include "files/CaFile.h" +#include "Poly.h" class MskFile : public File { @@ -31,10 +31,10 @@ class MskFile : public File return QObject::tr("Fichier caméra cinématique écran PC (*.msk)"); } int cameraPositionCount() const; - Vertex_s *cameraPosition(int frame) const; - void setCameraPosition(int frame, Vertex_s camPos[4]); - void insertCameraPosition(int frame, Vertex_s camPos[4]); + Vertex *cameraPosition(int frame) const; + void setCameraPosition(int frame, Vertex camPos[4]); + void insertCameraPosition(int frame, Vertex camPos[4]); void removeCameraPosition(int frame); private: - QList vertices; + QList vertices; }; diff --git a/src/files/TextureFile.cpp b/src/files/TextureFile.cpp index f5a1867..cab1361 100644 --- a/src/files/TextureFile.cpp +++ b/src/files/TextureFile.cpp @@ -54,6 +54,15 @@ const QImage &TextureFile::image() const return _image; } +QImage TextureFile::image(int colorTable) const +{ + QImage ret = _image; + + ret.setColorTable(_colorTables.at(colorTable)); + + return ret; +} + QImage *TextureFile::imagePtr() { return &_image; diff --git a/src/files/TextureFile.h b/src/files/TextureFile.h index d3e7ee4..0a323bc 100644 --- a/src/files/TextureFile.h +++ b/src/files/TextureFile.h @@ -35,6 +35,7 @@ class TextureFile : public File bool isValid() const; void clear(); const QImage &image() const; + QImage image(int colorTable) const; QImage *imagePtr(); bool isPaletted() const; const QList< QVector > &colorTables() const; diff --git a/src/files/TimFile.cpp b/src/files/TimFile.cpp index 6c31a7a..44c6f28 100644 --- a/src/files/TimFile.cpp +++ b/src/files/TimFile.cpp @@ -300,3 +300,25 @@ bool TimFile::save(QByteArray &data) return true; } + +QImage TimFile::palImage() const +{ + QImage img(palW, palH, QImage::Format_ARGB32); + + int y = 0; + foreach(const QVector &colorTable, _colorTables) { + int x = 0; + foreach(const QRgb &color, colorTable) { + img.setPixel(x, y, color); + + if(x == palW - 1) { + x = 0; + ++y; + } else { + ++x; + } + } + } + + return img; +} diff --git a/src/files/TimFile.h b/src/files/TimFile.h index 6d0e52a..05027e2 100644 --- a/src/files/TimFile.h +++ b/src/files/TimFile.h @@ -28,6 +28,39 @@ class TimFile : public TextureFile TimFile(const TextureFile &texture, quint8 bpp, quint16 palX, quint16 palY, quint16 palW, quint16 palH, quint16 imgX, quint16 imgY); bool open(const QByteArray &data); bool save(QByteArray &data); + inline QPoint imgPos() const { + quint16 x = imgX; + + if (bpp == 0) { + x *= 4; + } else if (bpp == 1) { + x *= 2; + } + + return QPoint(x, imgY); + } + inline QPoint palPos() const { + return QPoint(palX, palY); + } + inline QSize imgSize() const { + return image().size(); + } + inline QSize palSize() const { + return QSize(palW, palH); + } + inline QRect imgRect() const { + return QRect(imgPos(), imgSize()); + } + inline QRect palRect() const { + return QRect(palPos(), palSize()); + } + inline quint8 depth() const { + if (bpp == 0) { + return 4; + } + return bpp * 8; + } + QImage palImage() const; private: quint8 bpp; quint16 palX, palY; diff --git a/src/game/worldmap/Map.cpp b/src/game/worldmap/Map.cpp new file mode 100644 index 0000000..fc6dce2 --- /dev/null +++ b/src/game/worldmap/Map.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "Map.h" +#include + +Map::Map() +{ + +} + +QList Map::segments(SegmentFiltering filtering) const +{ + if (filtering == NoFiltering) { + return _segments; + } + + int endOfMap = 32 * 24; + QList ret = _segments.mid(0, endOfMap); + + // 32 * 24 + for (int i = NoEsthar; i <= WithDesertPrison; i <<= 1) { + if (filtering & i) { + switch (i) { + case NoEsthar: + for (int j = 0; j < 7; ++j) { + for (int k = 0; k < 8; ++k) { + ret.replace(373 + j * 32 + k, _segments.at(endOfMap + j * 8 + k)); + } + } + break; + case TGUAlternative: + ret.replace(149 + 0, _segments.at(endOfMap + 7 * 8 + 0)); + ret.replace(149 + 1, _segments.at(endOfMap + 7 * 8 + 1)); + break; + case WithGGU: + ret.replace(267, _segments.at(endOfMap + 7 * 8 + 2)); + break; + case WithBGU: + ret.replace(274 + 0, _segments.at(endOfMap + 7 * 8 + 2 + 1 + 0)); + ret.replace(275 + 1, _segments.at(endOfMap + 7 * 8 + 2 + 1 + 1)); + break; + case WithMissileBase: + ret.replace(327, _segments.at(endOfMap + 7 * 8 + 2 + 1 + 2)); + break; + case TrabiaCraterAlternative: + for (int j = 0; j < 2; ++j) { + for (int k = 0; k < 2; ++k) { + ret.replace(214 + j * 32 + k, _segments.at(endOfMap + 7 * 8 + 2 + 1 + 2 + 1 + j * 2 + k)); + } + } + break; + case WithDesertPrison: + ret.replace(361, _segments.at(endOfMap + 7 * 8 + 2 + 1 + 2 + 1 + 2 * 2)); + break; + } + } + } + + return ret; +} + +QList Map::encounters(quint8 region) const +{ + QList ret; + + foreach (const WmEncounter &enc, _encounters) { + if (enc.region() == region) { + ret.append(enc); + } + } + + return ret; +} + +QList > > Map::textureImages() const +{ + QList > > ret; + + foreach (const TimFile &tim, _textures) { + QList > images; + for (int palID = 0; palID < tim.colorTableCount(); ++palID) { + QImage img = tim.image(palID); + QVector colorTable = img.colorTable(); + bool hasAlpha = false; + for (int i = 0 ; i < colorTable.size(); ++i) { + if (colorTable.at(i) == qRgba(0, 0, 0, 0)) { + hasAlpha = true; + break; + } + } + img.setColorTable(colorTable); + images.append(qMakePair(img, hasAlpha)); + } + ret.append(images); + } + + return ret; +} + +QImage Map::specialTextureImage(SpecialTextureName name) const +{ + return _specialTextures.value(name).image(); +} + +QImage Map::composeTextureImage(const QList &tims) +{ + QRect maxImgRect; + + foreach (const TimFile &tim, tims) { + maxImgRect |= tim.imgRect(); + } + + QImage retImg(maxImgRect.size(), QImage::Format_ARGB32); + retImg.fill(qRgba(0, 0, 0, 0)); + + QPainter pImg(&retImg); + + foreach (const TimFile &tim, tims) { + pImg.drawImage(tim.imgPos() - maxImgRect.topLeft(), tim.image()); + } + + pImg.end(); + + return retImg; +} + +QImage Map::debugTextureCoords(int textureId) const +{ + const TimFile &tim = _textures.at(textureId); + + QImage retImg(tim.imgSize(), QImage::Format_ARGB32); + retImg.fill(qRgba(0, 0, 0, 0)); + + QPainter p(&retImg); + p.setBrush(Qt::white); + //p.drawImage(QPoint(0, 0), tim.image()); + + for (const MapSegment &segment: _segments) { + for (const MapBlock &block: segment.blocks()) { + for (const MapPoly &polygon: block.polygons()) { + if (polygon.texPage() == textureId) { + p.drawLine( + QPoint(polygon.texCoord(0).x, polygon.texCoord(0).y), + QPoint(polygon.texCoord(1).x, polygon.texCoord(1).y) + ); + } + } + + } + } + + p.end(); + + return retImg; +} + +QImage Map::specialTextureImage(SpecialTextureName min, SpecialTextureName max) const +{ + QList tims; + + for (int i = int(min); i <= int(max); ++i) { + tims.append(_specialTextures[SpecialTextureName(i)]); + } + + return composeTextureImage(tims); +} + +static double triangleArea(const TexCoord &a, const TexCoord &b, const TexCoord &c) +{ + return abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0); +} + +static double triangleArea(const QList &tc) +{ + return triangleArea(tc.at(0), tc.at(1), tc.at(2)); +} + +bool Map::searchBlackPixelsTexture(const QImage &texture, + const QList &tc) +{ + double area = triangleArea(tc); + TexCoord point; + quint8 maxY = texture.height() - 1, + maxX = texture.width() - 1; + + for(int y = 0; y <= maxY; y++) { + for(int x = 0; x <= maxX; x++) { + point.x = x; + point.y = y; + double area1 = triangleArea(point, tc.at(1), tc.at(2)), + area2 = triangleArea(tc.at(0), point, tc.at(2)), + area3 = triangleArea(tc.at(0), tc.at(1), point); + + if (area == area1 + area2 + area3 + && texture.color(point.y * texture.width() + point.x) == qRgba(0, 0, 0, 0)) { + return true; + } + } + } + + return false; +} + +void Map::searchBlackPixels(const QList > &textures, + const QImage &seaTexture, const QImage &roadTexture) +{ + QSet visited; + int segmentId = 0; + foreach (MapSegment segment, segments()) { + int blockId = 0; + foreach (MapBlock block, segment.blocks()) { + int polyId = 0; + foreach (MapPoly poly, block.polygons()) { + const QList &tc = poly.texCoords(); + quint64 coordHash = tc.at(0).x | (quint64(tc.at(0).y) << 8) + | (quint64(tc.at(1).x) << 16) | (quint64(tc.at(1).y) << 24) + | (quint64(tc.at(2).x) << 32) | (quint64(tc.at(2).y) << 40); + + if (visited.contains(coordHash)) { + polyId++; + continue; + } + + QImage texture; + + if (poly.isRoadTexture()) { + texture = roadTexture; + } else if (poly.isWaterTexture()) { + texture = seaTexture; + } else { + texture = textures.at(poly.texPage()).at(poly.clutId()); + } + + poly.setHasBlackPixels(searchBlackPixelsTexture(texture, tc)); + visited.insert(coordHash); + block.setPolygon(polyId++, poly); + } + + segment.setBlock(blockId++, block); + } + + _segments[segmentId++] = segment; + } +} diff --git a/src/game/worldmap/Map.h b/src/game/worldmap/Map.h new file mode 100644 index 0000000..5b678b8 --- /dev/null +++ b/src/game/worldmap/Map.h @@ -0,0 +1,171 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "game/worldmap/MapSegment.h" +#include "game/worldmap/WmEncounter.h" +#include "files/TimFile.h" + +struct DrawPoint { + quint8 x, y; + quint16 magicID; +}; + +class Map +{ +public: + enum SpecialTextureName { + Moon = 0, + Sky, + MiniMap, + Unknown1, + Unknown2, + Explosion, + Unknown3, + Sea1, + Sea2, + Sea3, + Cascade, + Sea4, + Beach1, + Beach2, + Sea5, + Unknown4, + Unknown5, + Unknown6, + Unknown7, + Unknown8, + Unknown9, + Unknown10, + Unknown11, + Unknown12, + Unknown13, + Unknown14, + Unknown15, + Unknown16 + }; + enum SegmentFiltering { + NoFiltering = 0, + NoEsthar = 1, + TGUAlternative = 2, + WithGGU = 4, + WithBGU = 8, + WithMissileBase = 16, + TrabiaCraterAlternative = 32, + WithDesertPrison = 64, + NoHiddenSegments = 128 + }; + + Map(); + + inline const QList &segments() const { + return _segments; + } + + QList segments(SegmentFiltering filtering) const; + + inline void setSegments(const QList &segments) { + _segments = segments; + } + + inline const QList &encounters() const { + return _encounters; + } + + QList encounters(quint8 region) const; + + inline void setEncounters(const QList &encounters) { + _encounters = encounters; + } + + inline const QList &encounterRegions() const { + return _encounterRegions; + } + + inline void setEncounterRegions(const QList ®ions) { + _encounterRegions = regions; + } + + inline const QList &textures() const { + return _textures; + } + + inline void setTextures(const QList &textures) { + _textures = textures; + } + + inline const QList &lowResTextures() const { + return _lowResTextures; + } + + inline void setLowResTextures(const QList &textures) { + _lowResTextures = textures; + } + + inline const TimFile specialTexture(SpecialTextureName name) const { + return _specialTextures[name]; + } + + inline void setSpecialTextures(const QMap &textures) { + _specialTextures = textures; + } + + inline const QList &roadTextures() const { + return _roadTextures; + } + + inline void setRoadTextures(const QList &textures) { + _roadTextures = textures; + } + + inline const QList &drawPoints() const { + return _drawPoints; + } + + inline void setDrawPoints(const QList &drawPoints) { + _drawPoints = drawPoints; + } + + QList > > textureImages() const; + QImage specialTextureImage(SpecialTextureName name) const; + QImage seaTextureImage() const { + return specialTextureImage(Sea1, Sea5); + } + QImage roadTextureImage() const { + return composeTextureImage(_roadTextures); + } + void searchBlackPixels(const QList > &textures, + const QImage &seaTexture, const QImage &roadTexture); + + QImage debugTextureCoords(int textureId) const; + +private: + QImage specialTextureImage(SpecialTextureName min, SpecialTextureName max) const; + static QImage composeTextureImage(const QList &tims); + static bool searchBlackPixelsTexture(const QImage &texture, + const QList &tc); + + QList _segments; + QList _encounters; + QList _encounterRegions; + QList _textures, _lowResTextures; + QMap _specialTextures; + QList _roadTextures; + QList _drawPoints; +}; diff --git a/src/game/worldmap/MapBlock.cpp b/src/game/worldmap/MapBlock.cpp new file mode 100644 index 0000000..17344cc --- /dev/null +++ b/src/game/worldmap/MapBlock.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "MapBlock.h" + +MapBlock::MapBlock() +{ + +} diff --git a/src/game/worldmap/MapBlock.h b/src/game/worldmap/MapBlock.h new file mode 100644 index 0000000..3d7962d --- /dev/null +++ b/src/game/worldmap/MapBlock.h @@ -0,0 +1,38 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "game/worldmap/MapPoly.h" + +class MapBlock +{ +public: + MapBlock(); + inline const QList &polygons() const { + return _polygons; + } + inline void setPolygons(const QList &polygons) { + _polygons = polygons; + } + inline void setPolygon(int id, const MapPoly &polygon) { + _polygons[id] = polygon; + } +private: + QList _polygons; +}; diff --git a/src/game/worldmap/MapPoly.cpp b/src/game/worldmap/MapPoly.cpp new file mode 100644 index 0000000..3f3eb73 --- /dev/null +++ b/src/game/worldmap/MapPoly.cpp @@ -0,0 +1,28 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "MapPoly.h" + +MapPoly::MapPoly(const QList &vertices, const QList &normals, + const QList &texCoords, + quint8 texPage, quint8 clutId, quint8 groundType, + quint8 flags1, quint8 flags2) : + TrianglePoly(vertices, normals, QList(), texCoords), + _texPage(texPage), _clutId(clutId), _groundType(groundType), + _flags1(flags1), _flags2(flags2) +{ +} diff --git a/src/game/worldmap/MapPoly.h b/src/game/worldmap/MapPoly.h new file mode 100644 index 0000000..2ea7f8b --- /dev/null +++ b/src/game/worldmap/MapPoly.h @@ -0,0 +1,84 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include "Poly.h" + +class MapPoly : public TrianglePoly +{ +public: + MapPoly(const QList &vertices, const QList &normals, + const QList &texCoords, + quint8 texPage, quint8 clutId, quint8 groundType, + quint8 flags1, quint8 flags2); + inline quint8 texPage() const { + return _texPage; + } + inline void setTexPage(quint8 texPage) { + _texPage = texPage; + } + inline quint8 clutId() const { + return _clutId; + } + inline void setClutId(quint8 clutId) { + _clutId = clutId; + } + inline quint8 groundType() const { + return _groundType; + } + inline void setGroundType(quint8 groundType) { + _groundType = groundType; + } + inline quint8 flags1() const { + return _flags1; + } + inline bool isWaterTexture() const { + return (_flags1 & 0x60) == 0x40; + } + inline bool isRoadTexture() const { + return _flags1 & 0x20; + } + inline bool isTransparent() const { + return _flags1 & 0x10; + } + inline bool isCity() const { + return _flags1 & 0x8; + } + inline void setFlags1(quint8 flags) { + _flags1 = flags; + } + inline quint8 flags2() const { + return _flags2; + } + inline void setFlags2(quint8 flags) { + _flags2 = flags; + } + inline bool hasBlackPixels() const { + return _flags1 & 0x4; + } + inline void setHasBlackPixels(bool hasBlackPixels) { + if (hasBlackPixels) { + _flags1 |= 0x4; + } else { + _flags1 &= 0xFB; + } + } +private: + quint8 _texPage, _clutId; + quint8 _groundType, _flags1, _flags2; +}; diff --git a/src/game/worldmap/MapSegment.cpp b/src/game/worldmap/MapSegment.cpp new file mode 100644 index 0000000..444d23e --- /dev/null +++ b/src/game/worldmap/MapSegment.cpp @@ -0,0 +1,22 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "MapSegment.h" + +MapSegment::MapSegment() +{ +} diff --git a/src/game/worldmap/MapSegment.h b/src/game/worldmap/MapSegment.h new file mode 100644 index 0000000..0af3dfa --- /dev/null +++ b/src/game/worldmap/MapSegment.h @@ -0,0 +1,47 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include "game/worldmap/MapBlock.h" + +class MapSegment +{ +public: + MapSegment(); + + inline const QList &blocks() const { + return _blocks; + } + inline void setBlocks(const QList &blocks) { + _blocks = blocks; + } + inline void setBlock(int id, const MapBlock &block) { + _blocks[id] = block; + } + + inline quint32 groupId() const { + return _groupId; + } + inline void setGroupId(quint32 groupId) { + _groupId = groupId; + } + +private: + QList _blocks; + quint32 _groupId; +}; diff --git a/src/game/worldmap/ObjFile.cpp b/src/game/worldmap/ObjFile.cpp new file mode 100644 index 0000000..e7b38be --- /dev/null +++ b/src/game/worldmap/ObjFile.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "ObjFile.h" + +ObjFile::ObjFile(const QString &fileName) : + QFile(fileName) +{ + +} + +bool ObjFile::extract(quint32 offset, quint32 size, const QString &fileName) +{ + if (!seek(offset)) { + return false; + } + + QByteArray data = read(size); + if (data.size() != size) { + return false; + } + + QFile f(fileName); + if (!f.open(QIODevice::WriteOnly)) { + return false; + } + + return f.write(data) == data.size(); +} + +bool ObjFile::extract(quint32 id, const QString &fileName) +{ + quint32 offset, size; + + if (!sectionInfos(id, offset, size)) { + return false; + } + + return extract(offset, size, fileName); +} + +bool ObjFile::extract(const QString &dirName) +{ + QList toc = openToc(); + if (toc.isEmpty()) { + return false; + } + qDebug() << toc; + QFileInfo info(fileName()); + QString name = info.completeBaseName(), ext = info.suffix(); + + QDir dir(dirName); + for (int i = 0; i < toc.size() - 1; ++i) { + quint32 offset = toc.at(i); + int size = toc.at(i + 1) - offset; + if (size < 0) { + qWarning() << "ObjFile::extract Invalid section size" << i; + } else if (!extract(offset, size, dir.filePath(QString("%1.part%2.%3") + .arg(name) + .arg(i, 2, 10, QChar('0')) + .arg(ext)))) { + qWarning() << "ObjFile::extract Cannot extract file" << i; + } + } + + return true; +} + +bool ObjFile::build(const QString &dirName, const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + + // Fake empty toc + int headerSize = (OBJFILE_SECTION_COUNT + 1) * sizeof(quint32); + if (file.write(QByteArray(headerSize, '\0')) != headerSize) { + return false; + } + + QDir dir(dirName); + QStringList fileNames = dir.entryList(QStringList() << "*.part*.*", + QDir::Files, QDir::Name); + quint32 toc[OBJFILE_SECTION_COUNT]; + QRegularExpression regExpPart("\\.part(\\d+)\\."); + + // Data + int oldCurToc = 0; + foreach (const QString &name, fileNames) { + + QRegularExpressionMatch match = regExpPart.match(name); + bool ok; + int number = match.captured(1).toInt(&ok); + + if (ok && number < OBJFILE_SECTION_COUNT) { + if (oldCurToc + 1 < number) { + // Not found files set to empty size + for (int i = oldCurToc; i < number; ++i) { + toc[i] = file.pos(); + qDebug() << i << QString::number(file.pos(), 16); + } + } + toc[number] = file.pos(); + oldCurToc = number; + + QFile f(dir.filePath(name)); + if (!f.open(QIODevice::ReadOnly)) { + qWarning() << "ObjFile::build Cannot open file" << f.fileName(); + } else { + file.write(f.readAll()); + } + } else { + qWarning() << "ObjFile::build Cannot find partN in filename" + << dir.filePath(name); + } + } + + if (oldCurToc + 1 < OBJFILE_SECTION_COUNT) { + // Not found files set to empty size + for (int i = oldCurToc; i < OBJFILE_SECTION_COUNT; ++i) { + toc[i] = file.pos(); + } + } + + // Toc + file.reset(); + headerSize -= sizeof(quint32); + if (file.write((char *)toc, headerSize) != headerSize) { + return false; + } + + return true; +} + +QList ObjFile::openToc() +{ + reset(); + const int r = OBJFILE_SECTION_COUNT * sizeof(quint32); + QByteArray data = read(r); + if (data.size() != r) { + qWarning() << "ObjFile::openToc File too short"; + return QList(); + } + const quint32 *constData = (const quint32 *)data.constData(); + QList ret; + + for (int i = 0; i < OBJFILE_SECTION_COUNT; ++i) { + ret.append(constData[i]); + } + ret.append(size()); + + return ret; +} + +bool ObjFile::sectionInfos(quint32 id, quint32 &offset, quint32 &size) +{ + const int entrySize = sizeof(quint32); + + if (!seek(id * entrySize)) { + qWarning() << "ObjFile::sectionInfos Cannot seek to" << id; + return false; + } + + if (read((char *)&offset, entrySize) != entrySize) { + qWarning() << "ObjFile::sectionInfos File too short 1" << id; + return false; + } + + quint32 nextOffset; + if (read((char *)&nextOffset, entrySize) != entrySize) { + qWarning() << "ObjFile::sectionInfos File too short 2" << id; + return false; + } + + if (nextOffset < offset) { + qWarning() << "ObjFile::sectionInfos Wrong order" << id + << offset << nextOffset; + return false; + } + + size = nextOffset - offset; + + return true; +} diff --git a/src/game/worldmap/ObjFile.h b/src/game/worldmap/ObjFile.h new file mode 100644 index 0000000..c1aa1be --- /dev/null +++ b/src/game/worldmap/ObjFile.h @@ -0,0 +1,35 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include + +#define OBJFILE_SECTION_COUNT 48 + +class ObjFile : public QFile +{ +public: + ObjFile(const QString &fileName); + bool extract(quint32 id, const QString &fileName); + bool extract(const QString &dirName); + static bool build(const QString &dirName, const QString &fileName); +private: + bool extract(quint32 offset, quint32 size, const QString &fileName); + QList openToc(); + bool sectionInfos(quint32 id, quint32 &offset, quint32 &size); +}; diff --git a/src/game/worldmap/TexlFile.cpp b/src/game/worldmap/TexlFile.cpp new file mode 100644 index 0000000..b4b25f2 --- /dev/null +++ b/src/game/worldmap/TexlFile.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "TexlFile.h" + +TexlFile::TexlFile(QIODevice *io) : + _io(io) +{ +} + +bool TexlFile::seekTexture(quint8 id) +{ + return _io->seek(id * TEXLFILE_TEXTURE_SIZE); +} + +bool TexlFile::readTexture(TimFile &tim) +{ + QByteArray data = _io->read(TEXLFILE_TEXTURE_SIZE); + + return tim.open(data); +} + +bool TexlFile::readTextures(Map &map) +{ + QList textures; + + _io->reset(); + + for (int i = 0; i < TEXLFILE_TEXTURE_COUNT; ++i) { + TimFile tim; + if (!readTexture(tim)) { + qDebug() << "TexlFile::readTextures cannot read texture"; + return false; + } + + textures.append(tim); + } + + map.setTextures(textures); + + return true; +} diff --git a/src/game/worldmap/TexlFile.h b/src/game/worldmap/TexlFile.h new file mode 100644 index 0000000..ae69932 --- /dev/null +++ b/src/game/worldmap/TexlFile.h @@ -0,0 +1,42 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "game/worldmap/Map.h" +#include "files/TimFile.h" + +#define TEXLFILE_TEXTURE_SIZE 0x12800 +#define TEXLFILE_TEXTURE_COUNT 20 + +class TexlFile +{ +public: + explicit TexlFile(QIODevice *io = Q_NULLPTR); + inline void setDevice(QIODevice *device) { + _io = device; + } + + bool readTextures(Map &map); + +private: + bool seekTexture(quint8 id); + bool readTexture(TimFile &tim); + + QIODevice *_io; +}; diff --git a/src/game/worldmap/WmArchive.cpp b/src/game/worldmap/WmArchive.cpp new file mode 100644 index 0000000..9a254b7 --- /dev/null +++ b/src/game/worldmap/WmArchive.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WmArchive.h" +#include "game/worldmap/WmsetFile.h" +#include "game/worldmap/WmxFile.h" +#include "game/worldmap/TexlFile.h" + +WmArchive::WmArchive() : + _isOpen(false) +{ +} + +bool WmArchive::open(const QString &filename, Map &map, ArchiveObserver *progress) +{ + if (progress) { + progress->setObserverMaximum(3); + } + + QString archivePath = filename; + archivePath.chop(1); + + if (!_fsArchive.open(archivePath)) { + _errorString = QObject::tr("Impossible d'ouvrir l'archive."); + return false; + } + + if (!_fsArchive.fileExists("*world\\dat\\wmx.obj")) { + _errorString = QObject::tr("Pas une archive mappemonde."); + return false; + } + + _isOpen = false; + + WmxFile wmx; + WmsetFile wmset; + TexlFile texl; + + QByteArray wmxData = _fsArchive.fileData("*world\\dat\\wmx.obj"); + QBuffer wmxBuffer(&wmxData); + wmxBuffer.open(QIODevice::ReadOnly); + wmx.setDevice(&wmxBuffer); + + if(!wmx.readSegments(map, 768)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (1)."); + return false; + } + + wmxData.clear(); + + if (progress) { + progress->setObserverValue(1); + } + + QByteArray wmsetData = _fsArchive.fileData("*world\\dat\\wmsetfr.obj"); + QBuffer wmsetBuffer(&wmsetData); + wmsetBuffer.open(QIODevice::ReadOnly); + wmset.setDevice(&wmsetBuffer); + + if (!wmset.readEncounterRegions(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (2)."); + return false; + } + + if (!wmset.readEncounters(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (3)."); + return false; + } + + if (!wmset.readSpecialTextures(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (4)."); + return false; + } + + if (!wmset.readRoadTextures(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (4)."); + return false; + } + + if (!wmset.readDrawPoints(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (5)."); + return false; + } + + wmsetData.clear(); + + if (progress) { + progress->setObserverValue(2); + } + + QByteArray texlData = _fsArchive.fileData("*world\\dat\\texl.obj"); + QBuffer texlBuffer(&texlData); + texlBuffer.open(QIODevice::ReadOnly); + texl.setDevice(&texlBuffer); + + if (!texl.readTextures(map)) { + _errorString = QObject::tr("Impossible de lire la mappemonde (6)."); + return false; + } + + if (progress) { + progress->setObserverValue(3); + } + + _isOpen = true; + + return true; +} diff --git a/src/game/worldmap/WmArchive.h b/src/game/worldmap/WmArchive.h new file mode 100644 index 0000000..7c6a5bd --- /dev/null +++ b/src/game/worldmap/WmArchive.h @@ -0,0 +1,36 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include "game/worldmap/Map.h" +#include "ArchiveObserver.h" +#include "FsArchive.h" + +class WmArchive +{ +public: + WmArchive(); + bool open(const QString &filename, Map &map, ArchiveObserver *progress); + inline const QString &errorString() const { + return _errorString; + } +private: + bool _isOpen; + QString _errorString; + FsArchive _fsArchive; +}; diff --git a/src/game/worldmap/WmEncounter.cpp b/src/game/worldmap/WmEncounter.cpp new file mode 100644 index 0000000..8fc5f74 --- /dev/null +++ b/src/game/worldmap/WmEncounter.cpp @@ -0,0 +1,28 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WmEncounter.h" + +WmEncounter::WmEncounter(const QList &sceneIds, + quint8 flags, quint8 region, quint8 ground, + const QList &lunarCrySceneIds, + quint8 lunarCryFlags) : + _sceneIds(sceneIds), _lunarCrySceneIds(lunarCrySceneIds), + _flags(flags), _lunarCryFlags(lunarCryFlags), + _region(region), _ground(ground) +{ +} diff --git a/src/game/worldmap/WmEncounter.h b/src/game/worldmap/WmEncounter.h new file mode 100644 index 0000000..7c221a2 --- /dev/null +++ b/src/game/worldmap/WmEncounter.h @@ -0,0 +1,81 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include + +class WmEncounter +{ +public: + WmEncounter(const QList &sceneIds, + quint8 flags, quint8 region, quint8 ground, + const QList &lunarCrySceneIds = QList(), + quint8 lunarCryFlags = 0); + + inline const QList &sceneIds() const { + return _sceneIds; + } + + inline void setSceneIds(const QList &sceneIds) { + _sceneIds = sceneIds; + } + + inline const QList &lunarCrySceneIds() const { + return _lunarCrySceneIds; + } + + inline void setLunarCrySceneIds(const QList &sceneIds) { + _lunarCrySceneIds = sceneIds; + } + + inline quint8 flags() const { + return _flags; + } + + inline void setFlags(quint8 flags) { + _flags = flags; + } + + inline quint8 lunarCryFlags() const { + return _lunarCryFlags; + } + + inline void setLunarCryFlags(quint8 flags) { + _lunarCryFlags = flags; + } + + inline quint8 region() const { + return _region; + } + + inline void setRegion(quint8 region) { + _region = region; + } + + inline quint8 ground() const { + return _ground; + } + + inline void setGround(quint8 ground) { + _ground = ground; + } + +private: + QList _sceneIds, _lunarCrySceneIds; + quint8 _flags, _lunarCryFlags, _region, _ground; +}; diff --git a/src/game/worldmap/WmsetFile.cpp b/src/game/worldmap/WmsetFile.cpp new file mode 100644 index 0000000..32885fe --- /dev/null +++ b/src/game/worldmap/WmsetFile.cpp @@ -0,0 +1,425 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WmsetFile.h" +#include "game/worldmap/WmEncounter.h" + +WmsetFile::WmsetFile(QIODevice *io) : + _io(io) +{ +} + +bool WmsetFile::extract(quint32 offset, quint32 size, const QString &fileName) +{ + if (!_io->seek(offset)) { + return false; + } + + QByteArray data = _io->read(size); + if (data.size() != size) { + return false; + } + + QFile f(fileName); + if (!f.open(QIODevice::WriteOnly)) { + return false; + } + + return f.write(data) == data.size(); +} + +bool WmsetFile::extract(quint32 id, const QString &fileName) +{ + quint32 offset, size; + + if (!sectionInfos(id, offset, size)) { + return false; + } + + return extract(offset, size, fileName); +} + +bool WmsetFile::extract(const QString &fileName, const QString &dirName) +{ + if (_toc.isEmpty() && !openToc()) { + return false; + } + + QFileInfo info(fileName); + QString name = info.completeBaseName(), ext = info.suffix(); + + QDir dir(dirName); + for (int i = 0; i < _toc.size() - 1; ++i) { + quint32 offset = _toc.at(i); + int size = _toc.at(i + 1) - offset; + if (size < 0) { + qWarning() << "WmsetFile::extract Invalid section size" << i; + } else if (!extract(offset, size, dir.filePath(QString("%1.part%2.%3") + .arg(name) + .arg(i, 2, 10, QChar('0')) + .arg(ext)))) { + qWarning() << "WmsetFile::extract Cannot extract file" << i; + } + } + + return true; +} + +bool WmsetFile::build(const QString &dirName, const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + + // Fake empty toc + int headerSize = (OBJFILE_SECTION_COUNT + 1) * sizeof(quint32); + if (file.write(QByteArray(headerSize, '\0')) != headerSize) { + return false; + } + + QDir dir(dirName); + QStringList fileNames = dir.entryList(QStringList() << "*.part*.*", + QDir::Files, QDir::Name); + quint32 toc[OBJFILE_SECTION_COUNT]; + QRegularExpression regExpPart("\\.part(\\d+)\\."); + + // Data + int oldCurToc = 0; + foreach (const QString &name, fileNames) { + + QRegularExpressionMatch match = regExpPart.match(name); + bool ok; + int number = match.captured(1).toInt(&ok); + + if (ok && number < OBJFILE_SECTION_COUNT) { + if (oldCurToc + 1 < number) { + // Not found files set to empty size + for (int i = oldCurToc; i < number; ++i) { + toc[i] = file.pos(); + qDebug() << i << QString::number(file.pos(), 16); + } + } + toc[number] = file.pos(); + oldCurToc = number; + + QFile f(dir.filePath(name)); + if (!f.open(QIODevice::ReadOnly)) { + qWarning() << "WmsetFile::build Cannot open file" << f.fileName(); + } else { + file.write(f.readAll()); + } + } else { + qWarning() << "WmsetFile::build Cannot find partN in filename" + << dir.filePath(name); + } + } + + if (oldCurToc + 1 < OBJFILE_SECTION_COUNT) { + // Not found files set to empty size + for (int i = oldCurToc; i < OBJFILE_SECTION_COUNT; ++i) { + toc[i] = file.pos(); + } + } + + // Toc + file.reset(); + headerSize -= sizeof(quint32); + if (file.write((char *)toc, headerSize) != headerSize) { + return false; + } + + return true; +} + +bool WmsetFile::readEncounters(Map &map) +{ + if ((_toc.isEmpty() && !openToc()) || _toc.size() < 7) { + return false; + } + + const quint32 + offsetSection1 = _toc.at(0), + offsetSection2 = _toc.at(1), + offsetSection3 = _toc.at(2), + offsetSection4 = _toc.at(3), + offsetSection5 = _toc.at(4), + offsetSection6 = _toc.at(5), + offsetSection7 = _toc.at(6), + sizeSection1 = (offsetSection1 - offsetSection2) - 8, + sizeSection3 = offsetSection4 - offsetSection3, + sizeSection4 = offsetSection5 - offsetSection4, + sizeSection5 = offsetSection6 - offsetSection5, + sizeSection6 = offsetSection7 - offsetSection6; + + _io->reset(); + QByteArray data = _io->read(offsetSection7); + const char *d = data.constData(); + + QList encounters; + + for (int i = 0; i < int(sizeSection1); i += 4) { + quint8 regionId = quint8(data.at(int(offsetSection1) + i)), + groundId = quint8(data.at(int(offsetSection1) + i + 1)), + esi = quint8(data.at(int(offsetSection1) + i + 2)); + + if (quint32(esi) >= sizeSection3 || quint32(esi) * 16 >= sizeSection4) { + qDebug() << "Warning: Bad ESI" << esi; + continue; + } + + quint8 flags = quint8(data.at(int(offsetSection3 + esi))), + flagsLunarCry = 0; + QList scenes, scenesLunarCry; + + for (int j = 0; j < 8; ++j) { + quint16 sceneId; + memcpy(&sceneId, d + offsetSection4, sizeof(quint16)); + scenes.append(sceneId); + } + + if (regionId == 10) { // Esthar + esi -= 80; + + if (esi >= sizeSection5 || esi * 16 >= sizeSection6 || int(offsetSection5 + esi) < 0) { + qDebug() << "Warning: Bad ESI lunar cry" << esi; + continue; + } + + flagsLunarCry = quint8(data.at(int(offsetSection5 + esi))); + + for (int j = 0; j < 8; ++j) { + quint16 sceneId; + memcpy(&sceneId, d + sizeSection6, sizeof(quint16)); + scenesLunarCry.append(sceneId); + } + + } + + encounters.append(WmEncounter( + scenes, flags, regionId, groundId, scenesLunarCry, flagsLunarCry + )); + } + + map.setEncounters(encounters); + + return true; +} + +bool WmsetFile::readEncounterRegions(Map &map) +{ + if ((_toc.isEmpty() && !openToc()) || _toc.size() < 3) { + return false; + } + + const quint32 sizeSection2 = _toc.at(2) - _toc.at(1); + + if (sizeSection2 < 24 * 32) { + qDebug() << "Section 2 size is not right" << sizeSection2; + return false; + } + + _io->seek(_toc.at(1)); + QByteArray data = _io->read(sizeSection2); + + QList regions; + + for (int i = 0; i < 24 * 32; ++i) { + regions.append(quint8(data.at(i))); + } + + map.setEncounterRegions(regions); + + return true; +} + +bool WmsetFile::readDrawPoints(Map &map) +{ + if ((_toc.isEmpty() && !openToc()) || _toc.size() < 35) { + return false; + } + + const quint32 sizeSection35 = _toc.at(35) - _toc.at(34); + + if (!_io->seek(_toc.at(34))) { + return false; + } + QByteArray data = _io->read(sizeSection35); + + if (data.size() != int(sizeSection35)) { + return false; + } + + const char *constData = data.constData(); + + QList drawPoints; + const int entryCount = data.size() / 4; + + for (int i = 0; i < entryCount; ++i) { + DrawPoint dp; + dp.x = data.at(i * 4), + dp.y = data.at(i * 4 + 1); + dp.magicID = 0; + memcpy(&dp.magicID, constData + i * 4 + 2, 2); + drawPoints.append(dp); + } + + map.setDrawPoints(drawPoints); + + return true; +} + +bool WmsetFile::readSpecialTextures(Map &map) +{ + if ((_toc.isEmpty() && !openToc()) || _toc.size() < 38) { + return false; + } + + const quint32 sizeSection38 = _toc.at(38) - _toc.at(37); + + if (!_io->seek(_toc.at(37))) { + return false; + } + const int entryCount = OBJFILE_SPECIAL_TEX_COUNT; + QByteArray data = _io->read(entryCount * 4); + + if (data.size() != entryCount * 4) { + return false; + } + + const quint32 *constData = (const quint32 *)data.constData(); + + QList toc; + + for (quint8 i = 0; i < entryCount; ++i) { + toc.append(constData[i]); + } + toc.append(sizeSection38); + + QList textures; + + for (int i = 0; i < OBJFILE_SPECIAL_TEX_OFFSET; ++i) { + _io->seek(_toc.at(37) + toc.at(i)); + QByteArray data = _io->read(toc.at(i + 1) - toc.at(i)); + textures.append(TimFile(data)); + } + + map.setLowResTextures(textures); + + QMap specialTextures; + + for (int i = OBJFILE_SPECIAL_TEX_OFFSET; i < toc.size() - 1; ++i) { + _io->seek(_toc.at(37) + toc.at(i)); + QByteArray data = _io->read(toc.at(i + 1) - toc.at(i)); + specialTextures.insert(Map::SpecialTextureName(i - OBJFILE_SPECIAL_TEX_OFFSET), TimFile(data)); + } + + map.setSpecialTextures(specialTextures); + + return true; +} + +bool WmsetFile::readRoadTextures(Map &map) +{ + if ((_toc.isEmpty() && !openToc()) || _toc.size() < 39) { + return false; + } + + const quint32 sizeSection39 = _toc.at(39) - _toc.at(38); + + if (!_io->seek(_toc.at(38))) { + return false; + } + const int entryCount = 13; + QByteArray data = _io->read(entryCount * 4); + + if (data.size() != entryCount * 4) { + return false; + } + + const quint32 *constData = (const quint32 *)data.constData(); + + QList toc; + + for (quint8 i = 0; i < entryCount; ++i) { + toc.append(constData[i]); + } + toc.append(sizeSection39); + + QList textures; + + for (int i = 0; i < toc.size() - 1; ++i) { + _io->seek(_toc.at(38) + toc.at(i)); + QByteArray data = _io->read(toc.at(i + 1) - toc.at(i)); + textures.append(TimFile(data)); + } + + map.setRoadTextures(textures); + + return true; +} + +bool WmsetFile::openToc() +{ + _io->reset(); + const int r = OBJFILE_SECTION_COUNT * sizeof(quint32); + QByteArray data = _io->read(r); + if (data.size() != r) { + qWarning() << "WmsetFile::openToc File too short"; + return false; + } + const quint32 *constData = (const quint32 *)data.constData(); + _toc.clear(); + + for (int i = 0; i < OBJFILE_SECTION_COUNT; ++i) { + _toc.append(constData[i]); + } + _toc.append(_io->size()); + + return true; +} + +bool WmsetFile::sectionInfos(quint32 id, quint32 &offset, quint32 &size) +{ + const int entrySize = sizeof(quint32); + + if (!_io->seek(id * entrySize)) { + qWarning() << "WmsetFile::sectionInfos Cannot seek to" << id; + return false; + } + + if (_io->read((char *)&offset, entrySize) != entrySize) { + qWarning() << "WmsetFile::sectionInfos File too short 1" << id; + return false; + } + + quint32 nextOffset; + if (_io->read((char *)&nextOffset, entrySize) != entrySize) { + qWarning() << "WmsetFile::sectionInfos File too short 2" << id; + return false; + } + + if (nextOffset < offset) { + qWarning() << "WmsetFile::sectionInfos Wrong order" << id + << offset << nextOffset; + return false; + } + + size = nextOffset - offset; + + return true; +} diff --git a/src/game/worldmap/WmsetFile.h b/src/game/worldmap/WmsetFile.h new file mode 100644 index 0000000..443f620 --- /dev/null +++ b/src/game/worldmap/WmsetFile.h @@ -0,0 +1,49 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "game/worldmap/Map.h" + +#define OBJFILE_SECTION_COUNT 48 +#define OBJFILE_SPECIAL_TEX_OFFSET 9 +#define OBJFILE_SPECIAL_TEX_COUNT 36 + +class WmsetFile +{ +public: + explicit WmsetFile(QIODevice *io = Q_NULLPTR); + inline void setDevice(QIODevice *device) { + _io = device; + } + bool extract(quint32 id, const QString &fileName); + bool extract(const QString &name, const QString &dirName); + static bool build(const QString &dirName, const QString &fileName); + bool readEncounters(Map &map); + bool readEncounterRegions(Map &map); + bool readDrawPoints(Map &map); + bool readSpecialTextures(Map &map); + bool readRoadTextures(Map &map); +private: + bool extract(quint32 offset, quint32 size, const QString &fileName); + bool openToc(); + bool sectionInfos(quint32 id, quint32 &offset, quint32 &size); + + QIODevice *_io; + QList _toc; +}; diff --git a/src/game/worldmap/WmxFile.cpp b/src/game/worldmap/WmxFile.cpp new file mode 100644 index 0000000..c1ded9c --- /dev/null +++ b/src/game/worldmap/WmxFile.cpp @@ -0,0 +1,544 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WmxFile.h" + +struct MapBlockHeader +{ + quint8 polyCount, vertexCount, normalCount, padding; +}; + +struct MapBlockPolygon +{ + quint8 vi[3]; + quint8 ni[3]; + TexCoord pos[3]; + quint8 texi, groundType, flags1, flags2; + /* texi: + * unknown (5-bits) | clut id (3-bits) + * Ground type: + * - 0 : galbadia forests + * - 1 : trabia forests + * - 2 : esthar forests + * - 3 : centra forests + * - 4 : balamb forests + * - 5 : trabia forests + * - 6 : normal grass galbadia, balamb + * - 7 : normal ground galbadia + * - 8 : normal desert galbadia + * - 9 : normal ice trabia + * - 10 : water steppable (waves) + * - 12 : esthar city roads + * - 14 : plateau + * - 15 : ground to mountain galbadia + * - 16 : ground to mountain galbadia, esthar, centra + * - 17 : ground to mountain trabia (1) + * - 18 : ground to mountain trabia (2) + * - 23 : ground to mountain trabia (3) + * - 24 : grass to mountain balamb + * - 25 : some borders in Esthar, ground to mountain (?) + * - 27 : railroads + * - 28 : roads + FH + some towns + * - 29 : inaccessible locations + * - 31 : lakes + * - 32 : water coasts + * - 33 : water less close to coasts + * - 34 : water + * u1: + * on walking area (1-bit) | water tex affected + roads (1-bit) + * | road/railroad tex affected (except esthar roads) + esthar fence (1-bit) + * | lakes + esthar fence + esthar city roads (1-bit) | enter city (1-bit) + * | unused (1-bit) + * | trabia frozen lakes, tunnel, beyond gates (below the surface), tunnels (2-bits) + * u2: + * movability (1-bit) | walking area, except forests (1-bit) + * | all, except mountains and rocks and Esthar and some grounds (?) (1-bit) + * | most of continents, except northest galbadia peninsula, some mountains, Esthar City, South of Esthar (1-bit) + * | unused (1-bit) | walking area, except forests (1-bit) + * | Galbadia, center of centra, trabia, balamb, horizon, some pieces of esthar (1-bit) | on walking area, except half-galbadia and balamb (1-bit) + */ + + bool operator==(const MapBlockPolygon &other) { + return memcmp(this->vi, other.vi, 3) == 0; + } +}; + +struct MapBlockVertex +{ + qint16 x, y, z, padding; +}; + +WmxFile::WmxFile(QIODevice *io) : + _io(io) +{ +} + +bool WmxFile::seekSegment(int segment) +{ + return _io->seek(segment * WMXFILE_SEGMENT_SIZE); +} + +bool WmxFile::readSegments(Map &map, int segmentCount) +{ + //_collect.clear(); + + QList segments; + bool toTheEnd = segmentCount < 0; + qint64 end = toTheEnd ? 0 : segmentCount * WMXFILE_SEGMENT_SIZE; + + while (canReadSegment() && (toTheEnd || _io->pos() < end)) { + MapSegment segment; + if (!readSegment(segment)) { + return false; + } + segments.append(segment); + } + + map.setSegments(segments); + + /* QList collect = _collect.values(); + qSort(collect); + qDebug() << _collect << collect.last(); */ + + return true; +} + +bool WmxFile::writeSegments(const Map &map) +{ + foreach (const MapSegment &segment, map.segments()) { + if (!writeSegment(segment)) { + return false; + } + } + + return true; +} + +bool WmxFile::canReadSegment() const +{ + return _io->size() - _io->pos() >= WMXFILE_SEGMENT_SIZE; +} + +bool WmxFile::readSegment(MapSegment &segment) +{ + QList toc; + qint64 initialPos = _io->pos(); + quint32 groupId; + + if (_io->read((char *)&groupId, sizeof(quint32)) != sizeof(quint32)) { + return false; + } + + if (!readSegmentToc(toc)) { + return false; + } + + QList blocks; + + foreach (quint32 pos, toc) { + /* // Check data between blocks + if (pos != _io->pos() - initialPos) { + if (pos > _io->pos() - initialPos) { + QByteArray padd = _io->read(pos - (_io->pos() - initialPos)); + if (padd != QByteArray(padd.size(), '\0')) { + qWarning() << "WmxFile::readSegment padding with bytes" + << padd.toHex(); + } + } else { + qWarning() << "WmxFile::readSegment pos not in toc" + << pos << (_io->pos() - initialPos); + _io->seek(initialPos + pos); + } + } */ + if (!_io->seek(initialPos + pos / 4 * 4)) { + qWarning() << "WmxFile::readSegment cannot seek"; + return false; + } + + MapBlock block; + + if (!readBlock(block)) { + return false; + } + + blocks.append(block); + } + + /* qDebug() << "WmxFile::readSegment bytes remaining" + << (WMXFILE_SEGMENT_SIZE - (_io->pos() % WMXFILE_SEGMENT_SIZE)); */ + + if (!_io->seek(initialPos + WMXFILE_SEGMENT_SIZE)) { + qWarning() << "WmxFile::readSegment cannot seek"; + return false; + } + + segment.setBlocks(blocks); + segment.setGroupId(groupId); + + return true; +} + +bool WmxFile::writeSegment(const MapSegment &segment) +{ + qint64 initialPos = _io->pos(); + quint32 groupId = segment.groupId(); + + if (_io->write((char *)&groupId, sizeof(quint32)) != sizeof(quint32)) { + return false; + } + + // Empty toc + if (!writeSegmentToc(QByteArray(WMXFILE_BLOCK_COUNT * sizeof(quint32), '\0'))) { + return false; + } + + if (segment.blocks().size() != WMXFILE_BLOCK_COUNT) { + qWarning() << "WmxFile::writeSegment wrong blocks size" + << segment.blocks().size(); + Q_ASSERT(false); + return false; + } + + QByteArray toc; + + foreach (const MapBlock &block, segment.blocks()) { + quint32 pos = _io->pos() - initialPos; + + if (!writeBlock(block)) { + return false; + } + + toc.append((char *)&pos, sizeof(quint32)); + } + + qint64 oldPos = _io->pos(); + + if (!_io->seek(initialPos + sizeof(quint32))) { + qWarning() << "WmxFile::writeSegment cannot seek 1"; + return false; + } + + if (!writeSegmentToc(toc)) { + return false; + } + + if (!_io->seek(oldPos)) { + qWarning() << "WmxFile::writeSegment cannot seek 2"; + return false; + } + + qint64 segmentSize = _io->pos() - initialPos; + if (segmentSize < WMXFILE_SEGMENT_SIZE + && !_io->write(QByteArray(WMXFILE_SEGMENT_SIZE - segmentSize, '\0'))) { + qWarning() << "WmxFile::writeSegment cannot write segment padding"; + return false; + } else if (segmentSize > WMXFILE_SEGMENT_SIZE) { + qWarning() << "WmxFile::writeSegment segment too big" << segmentSize; + return false; + } + + return true; +} + +bool WmxFile::readSegmentToc(QList &toc) +{ + for (quint8 i = 0; i < WMXFILE_BLOCK_COUNT; ++i) { + quint32 pos; + + if (_io->read((char *)&pos, sizeof(quint32)) != sizeof(quint32)) { + return false; + } + + toc.append(pos); + } + + return true; +} + +bool WmxFile::writeSegmentToc(const QByteArray &toc) +{ + if (toc.size() != WMXFILE_BLOCK_COUNT * sizeof(quint32)) { + qWarning() << "WmxFile::writeSegmentToc wrong toc size" + << toc.size(); + Q_ASSERT(false); + return false; + } + if (_io->write(toc) != toc.size()) { + return false; + } + + return true; +} + +static bool appendVertex(int index, + const QList &vertices, + QList &pVertices) +{ + if (index > vertices.size()) { + qWarning() << "WmxFile::appendVertex bad vertice index" << index; + return false; + } + pVertices.append(vertices.at(index)); + return true; +} + +bool WmxFile::readBlock(MapBlock &block) +{ + MapBlockHeader header; + + if (!readBlockHeader(header)) { + return false; + } + + QList polys, polys2; + for (quint16 i = 0; i < header.polyCount; ++i) { + MapBlockPolygon poly; + if (!readPolygon(poly)) { + return false; + } + //if (!polys.contains(poly)) { + polys.append(poly); + /* } else { + polys2.append(poly); + qDebug() << "Poly already exist" << i; + } */ + if (_io->pos() / WMXFILE_SEGMENT_SIZE == 827) { + qDebug() << QString("%1 vi(%2, %3, %4) ni(%5, %6, %7) pos(%8, %9) (%10, %11) (%12, %13) texi=%14 groundType=%15 flags=%16 %17") + .arg(i) + .arg(poly.vi[0]) + .arg(poly.vi[1]) + .arg(poly.vi[2]) + .arg(poly.ni[0]) + .arg(poly.ni[1]) + .arg(poly.ni[2]) + .arg(poly.pos[0].x) + .arg(poly.pos[0].y) + .arg(poly.pos[1].x) + .arg(poly.pos[1].y) + .arg(poly.pos[2].x) + .arg(poly.pos[2].y) + .arg(poly.texi) + .arg(poly.groundType) + .arg(poly.flags1) + .arg(poly.flags2).toLatin1().constData(); + } + } + + QList vertices; + for (quint16 i = 0; i < header.vertexCount; ++i) { + MapBlockVertex vertex; + if (!readVertex(vertex)) { + return false; + } + Vertex v; + v.x = vertex.x; + v.y = vertex.y; + v.z = vertex.z; + vertices.append(v); + } + + QList normals; + for (quint16 i = 0; i < header.normalCount; ++i) { + MapBlockVertex normal; + if (!readVertex(normal)) { + return false; + } + Vertex n; + n.x = normal.x; + n.y = normal.y; + n.z = normal.z; + normals.append(n); + } + + // QSet coll; + + QList polygons; + foreach (const MapBlockPolygon &poly, polys) { + QList pVertices, pNormals; + if (!appendVertex(poly.vi[0], vertices, pVertices)) { + return false; + } + if (!appendVertex(poly.vi[1], vertices, pVertices)) { + return false; + } + if (!appendVertex(poly.vi[2], vertices, pVertices)) { + return false; + } + if (!appendVertex(poly.ni[0], normals, pNormals)) { + return false; + } + if (!appendVertex(poly.ni[1], normals, pNormals)) { + return false; + } + if (!appendVertex(poly.ni[2], normals, pNormals)) { + return false; + } + + QList texCoords; + texCoords.append(poly.pos[0]); + texCoords.append(poly.pos[1]); + texCoords.append(poly.pos[2]); + + // _collect.insert(poly.groundType, 0); + + /* if(poly.groundType < 10) { + _collect.insert(poly.texi, _collect.value(poly.texi) + 1); + coll.insert(poly.texi); + } */ + + polygons.append(MapPoly(pVertices, pNormals, texCoords, + poly.texi >> 4, poly.texi & 0xF, + poly.groundType, + poly.flags1, poly.flags2)); + } + + /* foreach(quint32 texi, coll) { + qDebug() << QString("%1").arg(texi, 8, 2, QChar('0')); + } */ + + block.setPolygons(polygons); + + return true; +} + +static int storeVertex(const Vertex &vertex, QHash &hashed, + QList &ordered, int &cur) +{ + if (hashed.contains(vertex)) { // Fast lookup + return hashed.value(vertex); + } + hashed.insert(vertex, cur); + MapBlockVertex v; + v.x = vertex.x; + v.y = vertex.y; + v.z = vertex.z; + v.padding = 0; + ordered.append(v); + cur += 1; + return cur - 1; +} + +bool WmxFile::writeBlock(const MapBlock &block) +{ + QHash hashedVertices, hashedNormals; + QList vertices, normals; + int curVertex = 0, curNormal = 0; + QList polys; + + foreach (const MapPoly &polygon, block.polygons()) { + MapBlockPolygon poly; + poly.vi[0] = storeVertex(polygon.vertex(0), hashedVertices, vertices, + curVertex); + poly.vi[1] = storeVertex(polygon.vertex(1), hashedVertices, vertices, + curVertex); + poly.vi[2] = storeVertex(polygon.vertex(2), hashedVertices, vertices, + curVertex); + poly.ni[0] = storeVertex(polygon.normal(0), hashedNormals, normals, + curNormal); + poly.ni[1] = storeVertex(polygon.normal(1), hashedNormals, normals, + curNormal); + poly.ni[2] = storeVertex(polygon.normal(2), hashedNormals, normals, + curNormal); + poly.pos[0] = polygon.texCoord(0); + poly.pos[1] = polygon.texCoord(1); + poly.pos[2] = polygon.texCoord(2); + if (curVertex < 4) { + poly.texi = 0; + poly.groundType = polygon.groundType(); + poly.flags1 = polygon.flags1(); + poly.flags2 = polygon.flags2(); + } else { + poly.texi = (polygon.texPage() << 4) | (polygon.clutId() & 0xF); + poly.groundType = polygon.groundType(); + poly.flags1 = polygon.flags1(); + poly.flags2 = polygon.flags2(); + } + + polys.append(poly); + } + + MapBlockHeader header; + header.polyCount = polys.size(); + header.vertexCount = curVertex; + header.normalCount = curNormal; + header.padding = 0; + + if (!writeBlockHeader(header)) { + return false; + } + + foreach (const MapBlockPolygon &poly, polys) { + if (!writePolygon(poly)) { + return false; + } + } + + foreach (const MapBlockVertex &vertex, vertices) { + if (!writeVertex(vertex)) { + return false; + } + } + + foreach (const MapBlockVertex &normal, normals) { + if (!writeVertex(normal)) { + return false; + } + } + + // Padding + if (_io->write(QByteArray(sizeof(quint32), '\0')) != sizeof(quint32)) { + return false; + } + + return true; +} + +bool WmxFile::readBlockHeader(MapBlockHeader &header) +{ + return _io->read((char *)&header, sizeof(MapBlockHeader)) + == sizeof(MapBlockHeader); +} + +bool WmxFile::writeBlockHeader(const MapBlockHeader &header) +{ + return _io->write((char *)&header, sizeof(MapBlockHeader)) + == sizeof(MapBlockHeader); +} + +bool WmxFile::readPolygon(MapBlockPolygon &polygon) +{ + return _io->read((char *)&polygon, sizeof(MapBlockPolygon)) + == sizeof(MapBlockPolygon); +} + +bool WmxFile::writePolygon(const MapBlockPolygon &polygon) +{ + return _io->write((char *)&polygon, sizeof(MapBlockPolygon)) + == sizeof(MapBlockPolygon); +} + +bool WmxFile::readVertex(MapBlockVertex &vertex) +{ + return _io->read((char *)&vertex, sizeof(MapBlockVertex)) + == sizeof(MapBlockVertex); +} + +bool WmxFile::writeVertex(const MapBlockVertex &vertex) +{ + return _io->write((char *)&vertex, sizeof(MapBlockVertex)) + == sizeof(MapBlockVertex); +} diff --git a/src/game/worldmap/WmxFile.h b/src/game/worldmap/WmxFile.h new file mode 100644 index 0000000..ec2a32e --- /dev/null +++ b/src/game/worldmap/WmxFile.h @@ -0,0 +1,57 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "game/worldmap/Map.h" + +#define WMXFILE_BLOCK_COUNT 16 +#define WMXFILE_SEGMENT_SIZE 0x9000 + +struct MapBlockHeader; +struct MapBlockPolygon; +struct MapBlockVertex; + +class WmxFile +{ +public: + explicit WmxFile(QIODevice *io = Q_NULLPTR); + inline void setDevice(QIODevice *device) { + _io = device; + } + bool readSegments(Map &map, int segmentCount = -1); + bool writeSegments(const Map &map); +private: + bool seekSegment(int segment); + bool canReadSegment() const; + bool readSegment(MapSegment &segment); + bool writeSegment(const MapSegment &segment); + bool readSegmentToc(QList &toc); + bool writeSegmentToc(const QByteArray &toc); + bool readBlock(MapBlock &block); + bool writeBlock(const MapBlock &block); + bool readBlockHeader(MapBlockHeader &header); + bool writeBlockHeader(const MapBlockHeader &header); + bool readPolygon(MapBlockPolygon &polygon); + bool writePolygon(const MapBlockPolygon &polygon); + bool readVertex(MapBlockVertex &vertex); + bool writeVertex(const MapBlockVertex &polygon); + + QIODevice *_io; + //QMap _collect; +}; diff --git a/src/qt/Deling.qrc b/src/qt/Deling.qrc index 2465c65..41e9aa0 100644 --- a/src/qt/Deling.qrc +++ b/src/qt/Deling.qrc @@ -64,5 +64,7 @@ fonts/sysfnt_jp.txt shaders/main.vert shaders/main.frag + shaders/vertex.glsl + shaders/fragment.glsl diff --git a/src/qt/shaders/fragment.glsl b/src/qt/shaders/fragment.glsl new file mode 100644 index 0000000..23caf09 --- /dev/null +++ b/src/qt/shaders/fragment.glsl @@ -0,0 +1,21 @@ +uniform sampler2D texture; +uniform sampler2D palettes; +uniform float palId; +uniform float palMultiplier; +uniform float alpha; +varying mediump vec4 texc; + +void main(void) +{ + //if (palMultiplier == 0.0f) { + gl_FragColor = texture2D(texture, texc.st); + if (gl_FragColor.a == 0.0) { + discard; + } else { + gl_FragColor.a = alpha; + } + /* } else { + vec4 i = texture2D(texture, texc.st); + gl_FragColor = texture2D(palettes, vec2(i.r * palMultiplier, palId)); + } */ +} diff --git a/src/qt/shaders/vertex.glsl b/src/qt/shaders/vertex.glsl new file mode 100644 index 0000000..b6b418c --- /dev/null +++ b/src/qt/shaders/vertex.glsl @@ -0,0 +1,11 @@ +attribute highp vec4 vertex; +attribute mediump vec4 texCoord; +varying mediump vec4 texc; +uniform mediump mat4 projMatrix; +uniform mediump mat4 mvMatrix; + +void main(void) +{ + gl_Position = projMatrix * mvMatrix * vertex; + texc = texCoord; +} diff --git a/src/widgets/JsmWidget.cpp b/src/widgets/JsmWidget.cpp index a8d5843..d382491 100644 --- a/src/widgets/JsmWidget.cpp +++ b/src/widgets/JsmWidget.cpp @@ -383,7 +383,7 @@ void JsmWidget::showPreview(const QString &line, QPoint cursorPos) if (posLine >= 0) { QStringList texts = matchSetLine.capturedTexts(); - Vertex_s vertex[2]; + Vertex vertex[2]; vertex[0].x = qint16(texts.at(1).toInt()); vertex[0].y = qint16(texts.at(2).toInt()); vertex[0].z = qint16(texts.at(3).toInt()); diff --git a/src/widgets/WalkmeshWidget.cpp b/src/widgets/WalkmeshWidget.cpp index 8d16db2..b35e4ae 100644 --- a/src/widgets/WalkmeshWidget.cpp +++ b/src/widgets/WalkmeshWidget.cpp @@ -139,9 +139,9 @@ QWidget *WalkmeshWidget::buildCameraPage() connect(camList, SIGNAL(currentRowChanged(int)), SLOT(setCurrentCamera(int))); - connect(caVectorXEdit, SIGNAL(valuesChanged(Vertex_s)), SLOT(editCaVector(Vertex_s))); - connect(caVectorYEdit, SIGNAL(valuesChanged(Vertex_s)), SLOT(editCaVector(Vertex_s))); - connect(caVectorZEdit, SIGNAL(valuesChanged(Vertex_s)), SLOT(editCaVector(Vertex_s))); + connect(caVectorXEdit, SIGNAL(valuesChanged(Vertex)), SLOT(editCaVector(Vertex))); + connect(caVectorYEdit, SIGNAL(valuesChanged(Vertex)), SLOT(editCaVector(Vertex))); + connect(caVectorZEdit, SIGNAL(valuesChanged(Vertex)), SLOT(editCaVector(Vertex))); connect(caSpaceXEdit, SIGNAL(valueChanged(double)), SLOT(editCaPos(double))); connect(caSpaceYEdit, SIGNAL(valueChanged(double)), SLOT(editCaPos(double))); @@ -201,9 +201,9 @@ QWidget *WalkmeshWidget::buildWalkmeshPage() layout->setRowStretch(6, 1); connect(idList, SIGNAL(currentRowChanged(int)), SLOT(setCurrentId(int))); - connect(idVertices[0], SIGNAL(valuesChanged(Vertex_s)), SLOT(editIdTriangle(Vertex_s))); - connect(idVertices[1], SIGNAL(valuesChanged(Vertex_s)), SLOT(editIdTriangle(Vertex_s))); - connect(idVertices[2], SIGNAL(valuesChanged(Vertex_s)), SLOT(editIdTriangle(Vertex_s))); + connect(idVertices[0], SIGNAL(valuesChanged(Vertex)), SLOT(editIdTriangle(Vertex))); + connect(idVertices[1], SIGNAL(valuesChanged(Vertex)), SLOT(editIdTriangle(Vertex))); + connect(idVertices[2], SIGNAL(valuesChanged(Vertex)), SLOT(editIdTriangle(Vertex))); connect(idAccess[0], SIGNAL(valueChanged(int)), SLOT(editIdAccess(int))); connect(idAccess[1], SIGNAL(valueChanged(int)), SLOT(editIdAccess(int))); connect(idAccess[2], SIGNAL(valueChanged(int)), SLOT(editIdAccess(int))); @@ -253,9 +253,9 @@ QWidget *WalkmeshWidget::buildGatewaysPage() layout->setRowStretch(4, 1); connect(gateList, SIGNAL(currentRowChanged(int)), SLOT(setCurrentGateway(int))); - connect(exitPoints[0], SIGNAL(valuesChanged(Vertex_s)), SLOT(editExitPoint(Vertex_s))); - connect(exitPoints[1], SIGNAL(valuesChanged(Vertex_s)), SLOT(editExitPoint(Vertex_s))); - connect(entryPoint, SIGNAL(valuesChanged(Vertex_s)), SLOT(editEntryPoint(Vertex_s))); + connect(exitPoints[0], SIGNAL(valuesChanged(Vertex)), SLOT(editExitPoint(Vertex))); + connect(exitPoints[1], SIGNAL(valuesChanged(Vertex)), SLOT(editExitPoint(Vertex))); + connect(entryPoint, SIGNAL(valuesChanged(Vertex)), SLOT(editEntryPoint(Vertex))); connect(fieldId, SIGNAL(valueChanged(int)), SLOT(editFieldId(int))); for (int i = 0; i < 4; ++i) { connect(unknownGate1[i], SIGNAL(valueChanged(int)), SLOT(editUnknownGate(int))); @@ -294,8 +294,8 @@ QWidget *WalkmeshWidget::buildDoorsPage() layout->setRowStretch(3, 1); connect(doorList, SIGNAL(currentRowChanged(int)), SLOT(setCurrentDoor(int))); - connect(doorPosition[0], SIGNAL(valuesChanged(Vertex_s)), SLOT(editDoorPoint(Vertex_s))); - connect(doorPosition[1], SIGNAL(valuesChanged(Vertex_s)), SLOT(editDoorPoint(Vertex_s))); + connect(doorPosition[0], SIGNAL(valuesChanged(Vertex)), SLOT(editDoorPoint(Vertex))); + connect(doorPosition[1], SIGNAL(valuesChanged(Vertex)), SLOT(editDoorPoint(Vertex))); connect(doorUsed, SIGNAL(toggled(bool)), SLOT(editDoorUsed(bool))); connect(doorId, SIGNAL(valueChanged(int)), SLOT(editDoorId(int))); @@ -682,7 +682,7 @@ void WalkmeshWidget::removeCamera() } } -void WalkmeshWidget::editCaVector(const Vertex_s &values) +void WalkmeshWidget::editCaVector(const Vertex &values) { QObject *s = sender(); @@ -691,12 +691,12 @@ void WalkmeshWidget::editCaVector(const Vertex_s &values) else if (s == caVectorZEdit) editCaVector(2, values); } -void WalkmeshWidget::editCaVector(int id, const Vertex_s &values) +void WalkmeshWidget::editCaVector(int id, const Vertex &values) { if (data()->hasCaFile() && data()->getCaFile()->cameraCount() > 0) { const int camID = currentCamera(); Camera cam = data()->getCaFile()->camera(camID); - Vertex_s oldV = cam.camera_axis[id]; + Vertex oldV = cam.camera_axis[id]; if (oldV.x != values.x || oldV.y != values.y || oldV.z != values.z) { cam.camera_axis[id] = values; @@ -806,7 +806,7 @@ void WalkmeshWidget::removeTriangle() } } -void WalkmeshWidget::editIdTriangle(const Vertex_s &values) +void WalkmeshWidget::editIdTriangle(const Vertex &values) { QObject *s = sender(); @@ -815,7 +815,7 @@ void WalkmeshWidget::editIdTriangle(const Vertex_s &values) else if (s == idVertices[2]) editIdTriangle(2, values); } -void WalkmeshWidget::editIdTriangle(int id, const Vertex_s &values) +void WalkmeshWidget::editIdTriangle(int id, const Vertex &values) { if (data()->hasIdFile()) { const int triangleID = idList->currentRow(); @@ -904,7 +904,7 @@ void WalkmeshWidget::setCurrentDoor(int id) walkmeshGL->setSelectedDoor(id); } -void WalkmeshWidget::editExitPoint(const Vertex_s &values) +void WalkmeshWidget::editExitPoint(const Vertex &values) { QObject *s = sender(); @@ -912,12 +912,12 @@ void WalkmeshWidget::editExitPoint(const Vertex_s &values) else if (s == exitPoints[1]) editExitPoint(1, values); } -void WalkmeshWidget::editExitPoint(int id, const Vertex_s &values) +void WalkmeshWidget::editExitPoint(int id, const Vertex &values) { if (data()->hasInfFile()) { int gateId = gateList->currentRow(); Gateway old = data()->getInfFile()->getGateway(gateId); - Vertex_s oldVertex = old.exitLine[id]; + Vertex oldVertex = old.exitLine[id]; if (oldVertex.x != values.x || oldVertex.y != values.y || oldVertex.z != values.z) { old.exitLine[id] = values; data()->getInfFile()->setGateway(gateId, old); @@ -927,12 +927,12 @@ void WalkmeshWidget::editExitPoint(int id, const Vertex_s &values) } } -void WalkmeshWidget::editEntryPoint(const Vertex_s &values) +void WalkmeshWidget::editEntryPoint(const Vertex &values) { if (data()->hasInfFile()) { int gateId = gateList->currentRow(); Gateway old = data()->getInfFile()->getGateway(gateId); - Vertex_s oldVertex = old.destinationPoint; + Vertex oldVertex = old.destinationPoint; if (oldVertex.x != values.x || oldVertex.y != values.y || oldVertex.z != values.z) { old.destinationPoint = values; data()->getInfFile()->setGateway(gateId, old); @@ -941,7 +941,7 @@ void WalkmeshWidget::editEntryPoint(const Vertex_s &values) } } -void WalkmeshWidget::editDoorPoint(const Vertex_s &values) +void WalkmeshWidget::editDoorPoint(const Vertex &values) { QObject *s = sender(); @@ -949,12 +949,12 @@ void WalkmeshWidget::editDoorPoint(const Vertex_s &values) else if (s == doorPosition[1]) editDoorPoint(1, values); } -void WalkmeshWidget::editDoorPoint(int id, const Vertex_s &values) +void WalkmeshWidget::editDoorPoint(int id, const Vertex &values) { if (data()->hasInfFile()) { int gateId = gateList->currentRow(); Trigger old = data()->getInfFile()->getTrigger(gateId); - Vertex_s oldVertex = old.trigger_line[id]; + Vertex oldVertex = old.trigger_line[id]; if (oldVertex.x != values.x || oldVertex.y != values.y || oldVertex.z != values.z) { old.trigger_line[id] = values; data()->getInfFile()->setTrigger(gateId, old); @@ -1184,9 +1184,9 @@ void WalkmeshWidget::addMovieCameraPosition() int row = frameList->currentRow(); if (row < 0) row = 0; - Vertex_s *camPos = new Vertex_s[4]; + Vertex *camPos = new Vertex[4]; - memset(camPos, 0, sizeof(Vertex_s) * 4); + memset(camPos, 0, sizeof(Vertex) * 4); data()->getMskFile()->insertCameraPosition(row+1, camPos); diff --git a/src/widgets/WalkmeshWidget.h b/src/widgets/WalkmeshWidget.h index 758f484..7616689 100644 --- a/src/widgets/WalkmeshWidget.h +++ b/src/widgets/WalkmeshWidget.h @@ -41,19 +41,19 @@ public slots: private slots: void addCamera(); void removeCamera(); - void editCaVector(const Vertex_s &values); + void editCaVector(const Vertex &values); void editCaPos(double value); void editCaZoom(int value); void setCurrentId(int i); void addTriangle(); void removeTriangle(); - void editIdTriangle(const Vertex_s &values); + void editIdTriangle(const Vertex &values); void editIdAccess(int value); void setCurrentGateway(int id); void setCurrentDoor(int id); - void editExitPoint(const Vertex_s &values); - void editEntryPoint(const Vertex_s &values); - void editDoorPoint(const Vertex_s &values); + void editExitPoint(const Vertex &values); + void editEntryPoint(const Vertex &values); + void editDoorPoint(const Vertex &values); void editFieldId(int v); void editDoorUsed(bool enable); void editDoorId(int v); @@ -81,12 +81,12 @@ private slots: QWidget *buildCameraRangePage(); QWidget *buildMovieCameraPage(); QWidget *buildMiscPage(); - void editCaVector(int id, const Vertex_s &values); + void editCaVector(int id, const Vertex &values); void editCaPos(int id, double value); - void editIdTriangle(int id, const Vertex_s &values); + void editIdTriangle(int id, const Vertex &values); void editIdAccess(int id, int value); - void editExitPoint(int id, const Vertex_s &values); - void editDoorPoint(int id, const Vertex_s &values); + void editExitPoint(int id, const Vertex &values); + void editDoorPoint(int id, const Vertex &values); void editRange1(int id, int v); void editRange2(int id, int v); void editUnknownGate(int id, int val); diff --git a/src/widgets/WorldmapGLWidget.cpp b/src/widgets/WorldmapGLWidget.cpp new file mode 100644 index 0000000..f8fd8dd --- /dev/null +++ b/src/widgets/WorldmapGLWidget.cpp @@ -0,0 +1,883 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WorldmapGLWidget.h" +#include + +WorldmapGLWidget::WorldmapGLWidget(QWidget *parent, + Qt::WindowFlags f) : + QOpenGLWidget(parent, f), _map(Q_NULLPTR), + _distance(-0.714248f), _xRot(-90.0f), _yRot(180.0f), _zRot(180.0f), + _xTrans(-0.5f), _yTrans(0.5f), _transStep(360.0f), _lastKeyPressed(-1), + _texture(-1), _segmentGroupId(-1), _segmentId(-1), _blockId(-1), + _groundType(-1), _polyId(-1), _clutId(-1), _limits(QRect(0, 0, 32, 24)), + _segmentFiltering(Map::NoFiltering) +{ + mGL.initializeOpenGLFunctions(); + //extraGL = QOpenGLVersionFunctionsFactory::get(); + /* _xTrans = 0; + _yTrans = .12f; + _distance = -0.011123f; + _xRot = -90; + _yRot = 180; + _zRot = 180; */ +} + +WorldmapGLWidget::~WorldmapGLWidget() +{ + makeCurrent(); + buf.destroy(); + + for (const QList> &texs : qAsConst(_textures)) { + for (const QPair &tex : qAsConst(texs)) { + delete tex.first; + } + } + + delete _seaTexture; + delete _roadTexture; + delete _redTexture; +} + +/* +QImage WorldmapGLWidget::toImage(int w, int h) +{ + QSize sz = size(); + if ((w > 0) && (h > 0)) + sz = QSize(w, h); + + QImage pm; + if (context()->isValid()) { + context()->makeCurrent(); + QGLFramebufferObject fbo(sz, QGLFramebufferObject::CombinedDepthStencil); + fbo.bind(); + context()->setInitialized(false); + uint prevDefaultFbo = d->glcx->d_ptr->default_fbo; + d->glcx->d_ptr->default_fbo = fbo.handle(); + d->glcx->d_ptr->readback_target_size = sz; + updateGL(); + fbo.release(); + pm = fbo.toImage(); + d->glcx->d_ptr->default_fbo = prevDefaultFbo; + d->glcx->setInitialized(false); + d->glcx->d_ptr->readback_target_size = QSize(); + } + + saveGLState(); + const int nrPics = 360 / DEGREES_BETWEEN_PICTURES; + for (int i = 0; i < nrPics; i++) { + catchFbo->bind(); + mGL.glColorMask(true, true, true, true); + mGL.glClearColor(0,0,0,0); + mGL.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + mGL.glEnable(GL_DEPTH_TEST); + mGL.glDepthFunc(GL_LESS); + mGL.glEnable(GL_MULTISAMPLE); + mGL.glLoadIdentity(); + + GLfloat x = GLfloat(PICTURE_SIZE) / PICTURE_SIZE; + mGL.glFrustum(-x, +x, -1.0, +1.0, 1.0, 1000.0); + mGL.glViewport(0, 0, PICTURE_SIZE, PICTURE_SIZE); + + drawScreenshot(i); + catchFbo->release(); + + QImage catchImage = catchFbo->toImage(); + catchImage.save("object/test" + QString::number(i) + ".png"); + } + mGL.glDisable(GL_MULTISAMPLE); + + restoreGLState(); +} +*/ +void WorldmapGLWidget::setMap(Map *map) +{ + _map = map; + + _colorRegions[0] = qRgb(128, 0, 0); + //_colorRegions[1] = qRgb(128, 0, 128); + //_colorRegions[2] = qRgb(128, 0, 0); + /* _colorRegions[3] = qRgb(0, 128, 0); + _colorRegions[4] = qRgb(0, 0, 255); + _colorRegions[6] = qRgb(128, 128, 128); + _colorRegions[15] = qRgb(0, 128, 128); + _colorRegions[19] = qRgb(128, 128, 0); */ + /* _colorRegions[5] = qRgb(255, 0, 255); + _colorRegions[6] = qRgb(255, 255, 255); + _colorRegions[7] = qRgb(128, 0, 0); + _colorRegions[8] = qRgb(0, 128, 0); + _colorRegions[9] = qRgb(0, 0, 128); + _colorRegions[10] = qRgb(128, 128, 0); + _colorRegions[11] = qRgb(0, 128, 128); + _colorRegions[12] = qRgb(128, 0, 128); + _colorRegions[13] = qRgb(128, 128, 128); */ + + importVertices(); + + update(); +} + +void WorldmapGLWidget::setLimits(const QRect &rect) +{ + _limits = rect; + importVertices(); + update(); +} + +void WorldmapGLWidget::setXTrans(float trans) +{ + qDebug() << "WorldmapGLWidget::setXTrans" << trans; + _xTrans = trans; + update(); +} + +void WorldmapGLWidget::setYTrans(float trans) +{ + qDebug() << "WorldmapGLWidget::setYTrans" << trans; + _yTrans = trans; + update(); +} + +void WorldmapGLWidget::setZTrans(float trans) +{ + qDebug() << "WorldmapGLWidget::setZTrans" << trans; + _distance = trans; + update(); +} + +void WorldmapGLWidget::setXRot(float rot) +{ + _xRot = rot; + update(); +} + +void WorldmapGLWidget::setYRot(float rot) +{ + _yRot = rot; + update(); +} + +void WorldmapGLWidget::setZRot(float rot) +{ + _zRot = rot; + update(); +} + +void WorldmapGLWidget::setTexture(int texture) +{ + _texture = texture; + update(); +} + +void WorldmapGLWidget::setSegmentGroupId(int segmentGroupId) +{ + _segmentGroupId = segmentGroupId; + update(); +} + +void WorldmapGLWidget::setSegmentId(int segmentId) +{ + _segmentId = segmentId; + update(); +} + +void WorldmapGLWidget::setBlockId(int blockId) +{ + _blockId = blockId; + update(); +} + +void WorldmapGLWidget::setGroundType(int groundType) +{ + _groundType = groundType; + update(); +} + +void WorldmapGLWidget::setPolyId(int polyId) +{ + _polyId = polyId; + update(); +} + +void WorldmapGLWidget::setClutId(int clutId) +{ + _clutId = clutId; + update(); +} + +void WorldmapGLWidget::setSegmentFiltering(Map::SegmentFiltering filtering) +{ + _segmentFiltering = filtering; + + importVertices(); + update(); +} + +void WorldmapGLWidget::dumpCurrent() +{ + const MapPoly &poly = _map->segments().at(_segmentId).blocks().at(_blockId).polygons().at(_polyId); + qDebug() << QString::number(poly.flags1(), 16) << QString::number(poly.flags2(), 16) + << poly.groundType() << "texPage" << poly.texPage() << "clutId" << poly.clutId() << "hasTexture" << poly.hasTexture() << "isMonochrome" << poly.isMonochrome(); + for (const TexCoord &coord: poly.texCoords()) { + qDebug() << "texcoord" << coord.x << coord.y; + } + for (const Vertex &vertex: poly.vertices()) { + qDebug() << "vertex" << vertex.x << vertex.y << vertex.z; + } +} + +void WorldmapGLWidget::initializeGL() +{ + initializeOpenGLFunctions(); + + importVertices(); + + mGL.glEnable(GL_DEPTH_TEST); + mGL.glDepthFunc(GL_LEQUAL); + mGL.glDisable(GL_CULL_FACE); + mGL.glDisable(GL_LIGHTING); + mGL.glEnable(GL_BLEND); + mGL.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + mGL.glEnable(GL_MULTISAMPLE); + +#define PROGRAM_VERTEX_ATTRIBUTE 0 +#define PROGRAM_TEXCOORD_ATTRIBUTE 1 + + QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this); + vshader->compileSourceFile(":/shaders/vertex.glsl"); + + QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this); + fshader->compileSourceFile(":/shaders/fragment.glsl"); + + program = new QOpenGLShaderProgram; + program->addShader(vshader); + program->addShader(fshader); + program->bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); + program->bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE); + program->link(); + + program->bind(); + program->setUniformValue("texture", 0); + program->setUniformValue("palettes", 1); +} + +static quint16 normalizeY(qint16 y) +{ + return quint16(-(y - 128)); +} + +static QOpenGLTexture *textureFromImage(const QImage &image) +{ + QOpenGLTexture *texture; + /* if (image.format() == QImage::Format_Indexed8 || image.format() == QImage::Format_Grayscale8) { + qDebug() << "Indexed"; + texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + texture->setMinificationFilter(QOpenGLTexture::Nearest); + texture->setMagnificationFilter(QOpenGLTexture::Nearest); + texture->setFormat(QOpenGLTexture::R8_UNorm); + texture->setSize(image.width(), image.height()); + texture->setMipLevels(0); + texture->allocateStorage(); + // Upload pixel data + QOpenGLPixelTransferOptions uploadOptions; + uploadOptions.setAlignment(1); + texture->setData(0, QOpenGLTexture::Red, QOpenGLTexture::UInt8, image.constBits(), &uploadOptions); + } else { */ + texture = new QOpenGLTexture(image); + texture->setMinificationFilter(QOpenGLTexture::NearestMipMapLinear); + texture->setMagnificationFilter(QOpenGLTexture::Nearest); + texture->setAutoMipMapGenerationEnabled(false); + //} + + return texture; +} + +void WorldmapGLWidget::importVertices() +{ + if (Q_NULLPTR == _map) { + return; + } + + QList< QList< QPair > > images = _map->textureImages(); + QImage seaImage = _map->seaTextureImage(), + roadImage = _map->roadTextureImage(); + + // Set HasBlackPixels attribute on polys + //_map->searchBlackPixels(images, seaImage, roadImage); + + if (_textures.empty()) { + for (const QList< QPair > &images : qAsConst(images)) { + QList< QPair > texs; + + for (const QPair &image : qAsConst(images)) { + texs.append(qMakePair(textureFromImage(image.first), image.second)); + } + + _textures.append(texs); + } + + _seaTexture = textureFromImage(seaImage); + _roadTexture = textureFromImage(roadImage); + } + + QImage redImage(256, 256, QImage::Format_RGB32); + redImage.fill(Qt::red); + _redTexture = textureFromImage(redImage); + + /* for (int i = 0; i < 9; ++i) { + _specialTextures.append(QList() + << textureFromImage( + _map->specialTextureImage( + Map::SpecialTextureName(int(Map::Sea1) + i)))); + } */ + + QVector vertData; + + const int segmentPerLine = 32, blocksPerLine = 4, + diffSize = _limits.width() - _limits.height(); + const float scaleVect = 2048.0f, scaleTex = 255.0f, scale = _limits.width() * blocksPerLine; + const float xShift = -_limits.x() * blocksPerLine + (diffSize < 0 ? -diffSize : 0) * blocksPerLine / 2.0f; + const float zShift = -_limits.y() * blocksPerLine + (diffSize > 0 ? diffSize : 0) * blocksPerLine / 2.0f; + int xs = 0, ys = 0; + QMap, QList< QPair > > collect4; + QMap, QList > collect1; + QMap collect2, collect3; + /* QMap maxWPerGround, maxHPerGround; + + foreach (const MapSegment &segment, _map->segments()) { + foreach (const MapBlock &block, segment.blocks()) { + foreach (const MapPoly &poly, block.polygons()) { + if (maxW.contains(poly.groundType())) { + + } else { + maxW.insert(poly.groundType(), poly.) + } + + + for (quint8 i = 0; i < 3; ++i) { + const TexCoord &tc = poly.texCoord(i); + + } + } + } + } */ + + QList< QVector > drawAfter; + QList segments = _map->segments(_segmentFiltering); + foreach (const MapSegment &segment, segments) { + int xb = 0, yb = 0; + foreach (const MapBlock &block, segment.blocks()) { + foreach (const MapPoly &poly, block.polygons()) { + const int x = xs * blocksPerLine + xb, z = ys * blocksPerLine + yb; + + if (poly.vertices().size() != 3) { + qWarning() << "Wrong vertices size" << poly.vertices().size(); + return; + } + + QVector vertDataPoly; + + for (quint8 i = 0; i < 3; ++i) { + const Vertex &v = poly.vertex(i); + const TexCoord &tc = poly.texCoord(i); + vertDataPoly.append((xShift + x + v.x / scaleVect) / scale); + vertDataPoly.append(normalizeY(v.y) / scaleVect / scale); + vertDataPoly.append((zShift + z - v.z / scaleVect) / scale); + if (poly.isRoadTexture()) { + vertDataPoly.append(tc.x / float(_roadTexture->width() - 1)); + vertDataPoly.append(tc.y / float(_roadTexture->height() - 1)); + } else if (poly.isWaterTexture()) { + vertDataPoly.append(tc.x / float(_seaTexture->width() - 1)); + vertDataPoly.append(tc.y / float(_seaTexture->height() - 1)); + } else { + vertDataPoly.append(tc.x / scaleTex); + vertDataPoly.append(tc.y / scaleTex); + } + } + + if (false && poly.isTransparent()) { + drawAfter.append(vertDataPoly); + } else { + vertData.append(vertDataPoly); + } + + /* for (quint8 i = 0; i < 16; ++i) { + quint8 val = ((i < 8 ? poly.u1() : poly.u2()) >> (i % 8)) & 1; + if (val) { + if (poly.groundType() == 10 || poly.groundType() >= 31) { + collect2.insert(i, true); + } else if (poly.groundType() != 10 && poly.groundType() < 25) { + collect3.insert(i, true); + } + } + } */ + /* if (poly.groundType() == 31) { //poly.groundType() == 10 || poly.groundType() >= 31) { + collect2.insert(poly.clutId(), true); + } else if (poly.groundType() != 10 && poly.groundType() < 25) { + collect3.insert(poly.u1(), true); + } */ + + /* if (poly.groundType() == 10) { + qDebug() << poly.texCoord(0).x << poly.texCoord(0).y + << poly.texCoord(1).x << poly.texCoord(1).y + << poly.texCoord(2).x << poly.texCoord(2).y; + } */ + } + + xb += 1; + + if (xb >= blocksPerLine) { + xb = 0; + yb += 1; + } + } + + xs += 1; + + if (xs >= segmentPerLine) { + xs = 0; + ys += 1; + } + } + + foreach (const QVector &floats, drawAfter) { + vertData.append(floats); + } + + QMapIterator, QList > it(collect1); + while(it.hasNext()) { + it.next(); + qDebug() << it.key() << it.value(); + } + /* qDebug() << collect4.keys(); */ + qDebug() << collect2.keys(); + qDebug() << collect3.keys(); + + if (buf.isCreated()) { + buf.destroy(); + } + buf.create(); + buf.bind(); + buf.allocate(vertData.constData(), vertData.count() * int(sizeof(GLfloat))); +} + +static GLdouble deg2rad(GLdouble deg) +{ + const GLdouble pi = 3.1415926535897932384626433832795; + return deg / 360 * pi; +} + +void WorldmapGLWidget::resizeGL(int w, int h) +{ + //mGL.glViewport(0, 0, GLint(w), GLint(h)); + + //extraGL->glMatrixMode(GL_PROJECTION); + //extraGL->glLoadIdentity(); + + _matrixProj.setToIdentity(); + _matrixProj.perspective(70.0f, GLfloat(w) / h, 0.000001f, 1000.0f); + + //mGL.glMatrixMode(GL_MODELVIEW); +} + +void WorldmapGLWidget::paintGL() +{ + //extraGL->glMatrixMode(GL_MODELVIEW); + mGL.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + //extraGL->glLoadIdentity(); + + if (Q_NULLPTR == _map) { + return; + } + + QMatrix4x4 m; + //m.ortho(-1.0f, 1.0f, -1.0f, 1.0f, 4.0f, 15.0f); + m.translate(_xTrans, _yTrans, _distance); + m.rotate(_xRot, 1.0f, 0.0f, 0.0f); + m.rotate(_yRot, 0.0f, 1.0f, 0.0f); + m.rotate(_zRot, 0.0f, 0.0f, 1.0f); + + program->setUniformValue("projMatrix", _matrixProj); + program->setUniformValue("mvMatrix", m); + program->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); + program->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); + program->setAttributeBuffer(PROGRAM_VERTEX_ATTRIBUTE, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat)); + program->setAttributeBuffer(PROGRAM_TEXCOORD_ATTRIBUTE, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat)); + + int bufIndex = 0, alphaLocation = program->uniformLocation("alpha"); + QElapsedTimer t; + t.start(); + + program->setUniformValue(alphaLocation, 1.0f); + const QList ®ions = _map->encounterRegions(); + + QList drawAfter; + int segmentId = 0; + QList segments = _map->segments(_segmentFiltering); + foreach (const MapSegment &segment, segments) { + bool isInRegion = _texture >= 0 && regions.at(segmentId) == _texture; + int blockId = 0; + foreach (const MapBlock &block, segment.blocks()) { + int polyId = 0; + foreach (const MapPoly &poly, block.polygons()) { + + if (_texture >= 0) { + program->setUniformValue(alphaLocation, 0.5f); + + if (poly.isTransparent()) { + program->setUniformValue(alphaLocation, 0.0f); + } + + if (isInRegion && poly.groundType() <= 31) { + program->setUniformValue(alphaLocation, 1.0f); + + if (poly.isTransparent()) { + program->setUniformValue(alphaLocation, 0.5f); + } + } + } + + if (_segmentGroupId >= 0) { + if (_segmentGroupId == segment.groupId()) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + if (_segmentId >= 0) { + if (_segmentId == segmentId) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + if (_blockId >= 0) { + if (_blockId == blockId) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + if (_groundType >= 0) { + if (_groundType == poly.groundType()) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + if (_polyId >= 0) { + if (_polyId == polyId) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + if (_clutId >= 0) { + if (poly.clutId() == _clutId) { + program->setUniformValue(alphaLocation, 1.0f); + } else { + program->setUniformValue(alphaLocation, 0.5f); + } + } + + /* if ((poly.flags1() & 0x60) == 0x60) { + program->setUniformValue(alphaLocation, 0.5f); + } else { + program->setUniformValue(alphaLocation, 1.0f); + } */ + + /* if (poly.isTransparent()) { + program->setUniformValue(alphaLocation, 0.0f); + } */ + + polyId += 1; + + if (poly.isRoadTexture()) { + _roadTexture->bind(); + } else if (poly.isWaterTexture()) { + _seaTexture->bind(); + } else if (poly.texPage() < _textures.size() && poly.clutId() < _textures.at(poly.texPage()).size()) { + QPair p = _textures.at(poly.texPage()).at(poly.clutId()); + p.first->bind(); + /* const OpenGLPalettedTexture &paltex = _textures.at(poly.texPage()); + program->setUniformValue(palIdLocation, (poly.clutId() + 0.5f) / paltex.palettes()->height()); + program->setUniformValue(palMultiplierLocation, paltex.paletteMultiplier()); + mGL.glActiveTexture(GL_TEXTURE0); + paltex.texture()->bind(); + mGL.glActiveTexture(GL_TEXTURE1); + paltex.palettes()->bind(); */ + } else { + qDebug() << poly.texPage() << poly.clutId(); + //mGL.glActiveTexture(GL_TEXTURE0); + _redTexture->bind(); + /* mGL.glActiveTexture(GL_TEXTURE1); + _redTexture->bind(); */ + } + + mGL.glDrawArrays(GL_TRIANGLES, bufIndex * 3, 3); + + bufIndex += 1; + } + blockId += 1; + } + segmentId += 1; + } + + //mGL.glEnable(GL_BLEND); + //mGL.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + //program->setUniformValue(alphaLocation, 0.5f); + + /* foreach (const MapPoly &poly, drawAfter) { + if (poly.isRoadTexture()) { + _roadTexture->bind(); + } else if (poly.isWaterTexture()) { + _seaTexture->bind(); + } else if (poly.texPage() < _textures.size() && poly.clutId() < _textures.at(poly.texPage()).size()) { + _textures.at(poly.texPage()).at(poly.clutId()).first->bind(); + } else { + qDebug() << poly.texPage() << poly.clutId(); + _redTexture->bind(); + } + + mGL.glDrawArrays(GL_TRIANGLES, bufIndex * 3, 3); + + bufIndex += 1; + } */ + + qDebug() << t.elapsed(); +} + +void WorldmapGLWidget::wheelEvent(QWheelEvent *event) +{ + setFocus(); + _distance += double(event->angleDelta().y()) / 4096.0; + update(); +} + +void WorldmapGLWidget::mousePressEvent(QMouseEvent *event) +{ + setFocus(); + _moveStart = event->pos(); + if(event->button() == Qt::MiddleButton) + { + _distance = -35; + update(); + } +} + +void WorldmapGLWidget::mouseMoveEvent(QMouseEvent *event) +{ + /* if(event->modifiers() == Qt::CTRL) { + if(event->button() == Qt::LeftButton) + { + _xRot += fmod((event->pos().x() - _moveStart.x()) / 4096.0f, 360.0f); + } + else if(event->button() == Qt::RightButton) + { + _yRot -= fmod((event->pos().y() - _moveStart.y()) / 4096.0f, 360.0f); + } + } else if(event->button() == Qt::LeftButton) { + _xTrans += (event->pos().x() - _moveStart.x()) / 4096.0; + _yTrans -= (event->pos().y() - _moveStart.y()) / 4096.0; + } + _moveStart = event->pos(); + updateGL(); */ +} + +void WorldmapGLWidget::keyPressEvent(QKeyEvent *event) +{ + if(_lastKeyPressed == event->key() + && (event->key() == Qt::Key_Left + || event->key() == Qt::Key_Right + || event->key() == Qt::Key_Down + || event->key() == Qt::Key_Up)) { + if(_transStep > 100.0f) { + _transStep *= 0.90f; // accelerator + } + } else { + _transStep = 360.0f; + } + _lastKeyPressed = event->key(); + + switch(event->key()) + { + case Qt::Key_Left: + _xTrans += 1.0f/_transStep; + update(); + break; + case Qt::Key_Right: + _xTrans -= 1.0f/_transStep; + update(); + break; + case Qt::Key_Down: + _yTrans += 1.0f/_transStep; + update(); + break; + case Qt::Key_Up: + _yTrans -= 1.0f/_transStep; + update(); + break; + case Qt::Key_7: + _xRot += 0.1f; + update(); + break; + case Qt::Key_1: + _xRot -= 0.1f; + update(); + break; + case Qt::Key_8: + _yRot += 0.1f; + update(); + break; + case Qt::Key_2: + _yRot -= 0.1f; + update(); + break; + case Qt::Key_9: + _zRot += 0.1f; + update(); + break; + case Qt::Key_3: + _zRot -= 0.1f; + update(); + break; + default: + QWidget::keyPressEvent(event); + return; + } +} + +void WorldmapGLWidget::focusInEvent(QFocusEvent *event) +{ + grabKeyboard(); + QWidget::focusInEvent(event); +} + +void WorldmapGLWidget::focusOutEvent(QFocusEvent *event) +{ + releaseKeyboard(); + QWidget::focusOutEvent(event); +} + +static void qNormalizeAngle(int &angle) +{ + while (angle < 0) + angle += 360 * 16; + while (angle > 360 * 16) + angle -= 360 * 16; +} + +QRgb WorldmapGLWidget::groundColor(quint8 groundType, quint8 region, + const QSet &grounds) +{ + QRgb c; + + switch(groundType) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + c = qRgb(57, 71, 45); + break; + case 6: + case 15: + case 24: + c = qRgb(75, 80, 55); + break; + case 7: + c = qRgb(106, 78, 63); + break; + case 8: + c = qRgb(145, 124, 109); + break; + case 9: + c = qRgb(144, 157, 164); + break; + case 10: + //c = qRgb(122, 143, 158); + //c = qRgb(0xD0, 0xD0, 0xBA); // yellow + c = qRgb(0xA1, 0xA1, 0x8C); + break; + case 14: + c = qRgb(116, 100, 90); + break; + case 16: + case 25: + c = qRgb(103, 85, 72); + break; + case 17: + case 18: + case 23: + c = qRgb(57, 60, 53); + break; + case 27: + c = qRgb(112, 97, 86); + break; + case 28: + c = qRgb(69, 65, 64); + break; + case 29: + c = qRgb(133, 108, 91); + break; + case 31: + c = qRgb(53, 74, 75); + break; + case 32: + c = qRgb(56, 88, 99); + break; + case 33: + c = qRgb(40, 65, 81); + break; + case 34: + c = qRgb(35, 60, 75);// 56, 88, 99 + break; + default: + const quint8 grey = 55 + groundType * 200 / 34; + c = qRgb(grey, grey, grey); + break; + } + + if (/* grounds.contains(groundType) &&*/groundType >= 29 || !_colorRegions.contains(region)) { + //QRgb cRegion = _colorRegions.value(region, Qt::transparent); + QRgb cRegion = qRgb(128, 128, 128); + /* int r = qRed(cRegion) * .5 + qRed(c), + g = qGreen(cRegion) * .5 + qGreen(c), + b = qBlue(cRegion) * .5 + qBlue(c); */ + int r = qRed(c) - qRed(cRegion) * .33, + g = qGreen(c) - qGreen(cRegion) * .33, + b = qBlue(c) - qBlue(cRegion) * .33; + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + + c = qRgb(r, g, b); + } + + return c; +} diff --git a/src/widgets/WorldmapGLWidget.h b/src/widgets/WorldmapGLWidget.h new file mode 100644 index 0000000..98ff120 --- /dev/null +++ b/src/widgets/WorldmapGLWidget.h @@ -0,0 +1,149 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include "game/worldmap/Map.h" + +class OpenGLPalettedTexture { +public: + OpenGLPalettedTexture() : + _texture(nullptr), _palettes(nullptr), _paletteMultiplier(1.0f) {} + OpenGLPalettedTexture(QOpenGLTexture *tex, QOpenGLTexture *pal) : + _texture(tex), _palettes(pal), + _paletteMultiplier(256.0f / float(pal->width())) {} + inline QOpenGLTexture *texture() const { + return _texture; + } + inline QOpenGLTexture *palettes() const { + return _palettes; + } + inline float paletteMultiplier() const { + return _paletteMultiplier; + } +private: + QOpenGLTexture *_texture, *_palettes; + float _paletteMultiplier; +}; + +class WorldmapGLWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT +public: + explicit WorldmapGLWidget(QWidget *parent = Q_NULLPTR, + Qt::WindowFlags f = Qt::WindowFlags()); + + virtual ~WorldmapGLWidget(); + void setMap(Map *map); + inline const Map *map() const { + return _map; + } + void setLimits(const QRect &rect); + // QImage toImage(int w, int h); + void setXTrans(float trans); + inline float xTrans() const { + return _xTrans; + } + void setYTrans(float trans); + inline float yTrans() const { + return _yTrans; + } + void setZTrans(float trans); + inline float zTrans() const { + return _distance; + } + void setXRot(float rot); + inline float xRot() const { + return _xRot; + } + void setYRot(float rot); + inline float yRot() const { + return _yRot; + } + void setZRot(float rot); + inline float zRot() const { + return _zRot; + } + inline int texture() const { + return _texture; + } + inline int segmentGroupId() const { + return _segmentGroupId; + } + inline int segmentId() const { + return _segmentId; + } + inline int blockId() const { + return _blockId; + } + inline int polyId() const { + return _polyId; + } + inline int clutId() const { + return _clutId; + } + inline int groundType() const { + return _groundType; + } + QRgb groundColor(quint8 groundType, quint8 region, + const QSet &grounds); +public slots: + void setTexture(int texture); + void setSegmentGroupId(int segmentGroupId); + void setSegmentId(int segmentId); + void setBlockId(int blockId); + void setGroundType(int groundType); + void setPolyId(int polyId); + void setClutId(int clutId); + void setSegmentFiltering(Map::SegmentFiltering filtering); + void dumpCurrent(); +protected: + virtual void initializeGL(); + virtual void resizeGL(int w, int h); + virtual void paintGL(); + virtual void wheelEvent(QWheelEvent *event); + virtual void mousePressEvent(QMouseEvent *event); + virtual void mouseMoveEvent(QMouseEvent *event); + virtual void keyPressEvent(QKeyEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); +private: + void importVertices(); + + Map *_map; + float _distance; + float _xRot, _yRot, _zRot; + float _xTrans, _yTrans, _transStep; + int _lastKeyPressed, _texture, _segmentGroupId, _segmentId, _blockId; + int _groundType, _polyId, _clutId; + QRect _limits; + QPoint _moveStart; + QMap _colorRegions; + QList< QList< QPair > > _textures; + QList< QList > _specialTextures; + QOpenGLTexture *_seaTexture, *_roadTexture, *_redTexture; + QOpenGLBuffer buf; + QOpenGLShaderProgram *program; + QOpenGLFunctions mGL; + QMatrix4x4 _matrixProj; + Map::SegmentFiltering _segmentFiltering; +}; diff --git a/src/widgets/WorldmapWidget.cpp b/src/widgets/WorldmapWidget.cpp new file mode 100644 index 0000000..73fe8b0 --- /dev/null +++ b/src/widgets/WorldmapWidget.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#include "WorldmapWidget.h" + +WorldmapWidget::WorldmapWidget(QWidget *parent, Qt::WindowFlags f) : + QWidget(parent, f) +{ + const int min = 0, max = 100, minRot = -360, maxRot = 360; + + _scene = new WorldmapGLWidget(this); + + _xTransSlider = new QSlider(Qt::Vertical, this); + _xTransSlider->setRange(min, max); + _yTransSlider = new QSlider(Qt::Vertical, this); + _yTransSlider->setRange(min, max); + _zTransSlider = new QSlider(Qt::Vertical, this); + _zTransSlider->setRange(min, max); + + _xRotSlider = new QSlider(Qt::Vertical, this); + _xRotSlider->setRange(minRot, maxRot); + _yRotSlider = new QSlider(Qt::Vertical, this); + _yRotSlider->setRange(minRot, maxRot); + _zRotSlider = new QSlider(Qt::Vertical, this); + _zRotSlider->setRange(minRot, maxRot); + + QPushButton *butt = new QPushButton(tr("dump"), this); + + _textureSpinBox = new QSpinBox(this); + _textureSpinBox->setRange(-1, 255); + + _segmentGroupSpinBox = new QSpinBox(this); + _segmentGroupSpinBox->setRange(-1, 255); + + _segmentSpinBox = new QSpinBox(this); + _segmentSpinBox->setRange(-1, 2147483647); + + _blockSpinBox = new QSpinBox(this); + _blockSpinBox->setRange(-1, 15); + + _groundTypeSpinBox = new QSpinBox(this); + _groundTypeSpinBox->setRange(-1, 255); + + _polyIdSpinBox = new QSpinBox(this); + _polyIdSpinBox->setRange(-1, 2147483647); + + _clutIdSpinBox = new QSpinBox(this); + _clutIdSpinBox->setRange(-1, 32); + + QGridLayout *layout = new QGridLayout(this); + layout->addWidget(_scene, 0, 0, 2, 1); + layout->addWidget(_xTransSlider, 0, 1); + layout->addWidget(_yTransSlider, 0, 2); + layout->addWidget(_zTransSlider, 0, 3); + layout->addWidget(_xRotSlider, 0, 4); + layout->addWidget(_yRotSlider, 0, 5); + layout->addWidget(_zRotSlider, 0, 6); + layout->addWidget(_textureSpinBox, 1, 1); + layout->addWidget(_segmentGroupSpinBox, 1, 2); + layout->addWidget(_segmentSpinBox, 1, 3); + layout->addWidget(_blockSpinBox, 1, 4); + layout->addWidget(_groundTypeSpinBox, 1, 5); + layout->addWidget(_polyIdSpinBox, 1, 6); + layout->addWidget(_clutIdSpinBox, 1, 7); + layout->addWidget(butt, 1, 8); + layout->setColumnStretch(0, 1); + + _xTransSlider->setValue((_scene->xTrans() + 1.0) * _xTransSlider->maximum() / 2.0); + _yTransSlider->setValue((_scene->yTrans() + 1.0) * _yTransSlider->maximum() / 2.0); + _zTransSlider->setValue((_scene->zTrans() + 1.0) * _zTransSlider->maximum() / 2.0); + + _xRotSlider->setValue(_scene->xRot()); + _yRotSlider->setValue(_scene->yRot()); + _zRotSlider->setValue(_scene->zRot()); + + _textureSpinBox->setValue(_scene->texture()); + _segmentGroupSpinBox->setValue(_scene->segmentGroupId()); + _segmentSpinBox->setValue(_scene->segmentId()); + _blockSpinBox->setValue(_scene->blockId()); + _groundTypeSpinBox->setValue(_scene->groundType()); + _polyIdSpinBox->setValue(_scene->polyId()); + _clutIdSpinBox->setValue(_scene->clutId()); + + connect(_xTransSlider, SIGNAL(sliderMoved(int)), SLOT(setXTrans(int))); + connect(_yTransSlider, SIGNAL(sliderMoved(int)), SLOT(setYTrans(int))); + connect(_zTransSlider, SIGNAL(sliderMoved(int)), SLOT(setZTrans(int))); + + connect(_xRotSlider, SIGNAL(sliderMoved(int)), SLOT(setXRot(int))); + connect(_yRotSlider, SIGNAL(sliderMoved(int)), SLOT(setYRot(int))); + connect(_zRotSlider, SIGNAL(sliderMoved(int)), SLOT(setZRot(int))); + + connect(_textureSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setTexture(int))); + connect(_segmentGroupSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setSegmentGroupId(int))); + connect(_segmentSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setSegmentId(int))); + connect(_blockSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setBlockId(int))); + connect(_groundTypeSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setGroundType(int))); + connect(_polyIdSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setPolyId(int))); + connect(_clutIdSpinBox, SIGNAL(valueChanged(int)), _scene, SLOT(setClutId(int))); + connect(butt, SIGNAL(released()), _scene, SLOT(dumpCurrent())); +} + +void WorldmapWidget::setXTrans(int value) +{ + _scene->setXTrans((value * 2.0 / _xTransSlider->maximum()) - 1.0); +} + +void WorldmapWidget::setYTrans(int value) +{ + _scene->setYTrans((value * 2.0 / _yTransSlider->maximum()) - 1.0); +} + +void WorldmapWidget::setZTrans(int value) +{ + _scene->setZTrans((value * 2.0 / _zTransSlider->maximum()) - 1.0); +} + +void WorldmapWidget::setXRot(int value) +{ + _scene->setXRot(value); +} + +void WorldmapWidget::setYRot(int value) +{ + _scene->setYRot(value); +} + +void WorldmapWidget::setZRot(int value) +{ + _scene->setZRot(value); +} diff --git a/src/widgets/WorldmapWidget.h b/src/widgets/WorldmapWidget.h new file mode 100644 index 0000000..a47d509 --- /dev/null +++ b/src/widgets/WorldmapWidget.h @@ -0,0 +1,54 @@ +/**************************************************************************** + ** Deling Final Fantasy VIII Field Editor + ** Copyright (C) 2009-2024 Arzel Jérôme + ** + ** This program is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** This program is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with this program. If not, see . + ****************************************************************************/ +#pragma once + +#include +#include "widgets/WorldmapGLWidget.h" + +class WorldmapWidget : public QWidget +{ + Q_OBJECT +public: + explicit WorldmapWidget(QWidget *parent = Q_NULLPTR, + Qt::WindowFlags f = Qt::WindowFlags()); + inline void setMap(Map *map) { + if (map) { + _segmentSpinBox->setMaximum(map->segments().size()); + } + _scene->setMap(map); + } + inline const Map *map() const { + return _scene->map(); + } + inline WorldmapGLWidget *scene() const { + return _scene; + } +private slots: + void setXTrans(int value); + void setYTrans(int value); + void setZTrans(int value); + void setXRot(int value); + void setYRot(int value); + void setZRot(int value); +private: + WorldmapGLWidget *_scene; + QSlider *_xTransSlider, *_yTransSlider, *_zTransSlider; + QSlider *_xRotSlider, *_yRotSlider, *_zRotSlider; + QSpinBox *_textureSpinBox, *_segmentGroupSpinBox, *_segmentSpinBox; + QSpinBox *_blockSpinBox, *_groundTypeSpinBox, *_polyIdSpinBox, *_clutIdSpinBox; +}; diff --git a/translations/Deling_en.ts b/translations/Deling_en.ts index 3edd8d5..03df24a 100644 --- a/translations/Deling_en.ts +++ b/translations/Deling_en.ts @@ -2797,6 +2797,34 @@ It is strongly recommended to save the archive (fs, fi and fl) before continuing Fichier map tiles écran PC (*.map) Map Tiles PC Field File (*.map) + + Pas une archive mappemonde. + + + + Impossible de lire la mappemonde (1). + + + + Impossible de lire la mappemonde (2). + + + + Impossible de lire la mappemonde (3). + + + + Impossible de lire la mappemonde (4). + + + + Impossible de lire la mappemonde (5). + + + + Impossible de lire la mappemonde (6). + + Search @@ -3497,4 +3525,11 @@ poursuite de la recherche dans le dernier fichier. Position %1 + + WorldmapWidget + + dump + + + diff --git a/translations/Deling_es.ts b/translations/Deling_es.ts index cc505a2..16c0b3c 100644 --- a/translations/Deling_es.ts +++ b/translations/Deling_es.ts @@ -2824,6 +2824,34 @@ Indarrez gomendatzen zaitu artxiboa (fs, fi and fl) gorde egitea jarraitu baino Fichier map tiles écran PC (*.map) Map Tiles PC Field File (*.map) + + Pas une archive mappemonde. + + + + Impossible de lire la mappemonde (1). + + + + Impossible de lire la mappemonde (2). + + + + Impossible de lire la mappemonde (3). + + + + Impossible de lire la mappemonde (4). + + + + Impossible de lire la mappemonde (5). + + + + Impossible de lire la mappemonde (6). + + Search @@ -3528,4 +3556,11 @@ poursuite de la recherche dans le dernier fichier. Maila %1 + + WorldmapWidget + + dump + + + diff --git a/translations/Deling_ja.ts b/translations/Deling_ja.ts index 4c7f383..db9aabf 100644 --- a/translations/Deling_ja.ts +++ b/translations/Deling_ja.ts @@ -2975,6 +2975,34 @@ It is strongly recommended to save the archive (fs, fi and fl) before continuing Fichier map tiles écran PC (*.map) Map Tiles PC Field File (*.map) + + Pas une archive mappemonde. + + + + Impossible de lire la mappemonde (1). + + + + Impossible de lire la mappemonde (2). + + + + Impossible de lire la mappemonde (3). + + + + Impossible de lire la mappemonde (4). + + + + Impossible de lire la mappemonde (5). + + + + Impossible de lire la mappemonde (6). + + Search @@ -3711,4 +3739,11 @@ poursuite de la recherche dans le dernier fichier. Position %1 + + WorldmapWidget + + dump + + +