From a5fec28e655ae53e35a6a7f89e1ad6956ec2dacf Mon Sep 17 00:00:00 2001
From: Julian Dreuth <julian.dreuth@informatik.hs-fulda.de>
Date: Mon, 3 Feb 2025 19:47:13 +0100
Subject: [PATCH] Feature/editor implement liblvl

---
 editor/CMakeLists.txt      |  3 +-
 editor/src/CenterGrid.cpp  | 24 +++++++++++----
 editor/src/CenterGrid.hpp  | 18 ++++++++++-
 editor/src/HDF5Handler.cpp | 63 ++++++++++++++++++++++++++++++++++++++
 editor/src/HDF5Handler.hpp | 56 +++++++++++++++++++++++++++++++++
 editor/src/MainWindow.cpp  | 53 +++++++++++++++++++++++---------
 editor/src/MainWindow.hpp  | 18 +++++++++++
 7 files changed, 213 insertions(+), 22 deletions(-)
 create mode 100644 editor/src/HDF5Handler.cpp
 create mode 100644 editor/src/HDF5Handler.hpp

diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt
index 6c99e28..ddda5b2 100644
--- a/editor/CMakeLists.txt
+++ b/editor/CMakeLists.txt
@@ -12,7 +12,8 @@ set(EDITOR_SOURCES src/main.cpp src/EditorApp.cpp
         src/TileBar.cpp
         src/TileBar.hpp
         src/GridItem.cpp
-        src/GridItem.hpp)
+        src/GridItem.hpp
+        src/HDF5Handler.cpp)
 
 set(CMAKE_AUTOMOC ON)
 
diff --git a/editor/src/CenterGrid.cpp b/editor/src/CenterGrid.cpp
index dc2ca40..ede515b 100644
--- a/editor/src/CenterGrid.cpp
+++ b/editor/src/CenterGrid.cpp
@@ -15,19 +15,24 @@ CenterGrid::CenterGrid(QWidget* parent)
     view->setAlignment(Qt::AlignCenter | Qt::AlignCenter);
 
     // Initialize the 2D container
+    m_currentSpriteID = -1;
     const int rows = 64;
     const int cols = 64;
     gridItems.resize(rows);
-    for (int row = 0; row < rows; ++row) 
-    {
+    m_tileIDs.resize(rows);
+    for (int row = 0; row < rows; row++) {
         gridItems[row].resize(cols);
+        m_tileIDs[row].resize(cols);
+        for (int col = 0; col < cols; col++) {
+            m_tileIDs[row][col] = m_currentSpriteID;
+        }
     }
 
     // Create tiles
     const qreal tileSize = 20.0;
-    for (int row = 0; row < rows; ++row) 
+    for (int row = 0; row < rows; row++) 
     {
-        for (int col = 0; col < cols; ++col) 
+        for (int col = 0; col < cols; col++) 
         {
             GridItem* tile = new GridItem(row, col, tileSize);
             tile->setPos(col * tileSize, row * tileSize);
@@ -38,7 +43,7 @@ CenterGrid::CenterGrid(QWidget* parent)
     }
 
     // Set initial grid boundaries
-    scene->setSceneRect(0, 0, 64*tileSize, 64*tileSize);
+    scene->setSceneRect(0, 0, 64 * tileSize, 64 * tileSize);
 
     // Set Layout
     QVBoxLayout* layout = new QVBoxLayout(this);
@@ -59,15 +64,17 @@ void CenterGrid::resizeEvent(QResizeEvent* event)
     view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
 }
 
-void CenterGrid::setCurrentSprite(const QPixmap pixmap) 
+void CenterGrid::setCurrentSprite(const QPixmap pixmap, const int id) 
 {
     m_currentSprite = pixmap;
+    m_currentSpriteID = id;
 }
 
 void CenterGrid::onTileClicked(int row, int col) 
 {
     GridItem* tile = getTile(row,col);
     tile->setPixmap(m_currentSprite);
+    m_tileIDs[row][col] = m_currentSpriteID;
 }
 
 GridItem* CenterGrid::getTile(int row, int col) const 
@@ -79,6 +86,11 @@ GridItem* CenterGrid::getTile(int row, int col) const
     return nullptr;
 }
 
+QVector<QVector<int>> CenterGrid::getTileIDs() 
+{
+    return m_tileIDs;
+}
+
 void CenterGrid::drawPreset(int index)
 {
     QList<QGraphicsItem*> items = scene->items();
diff --git a/editor/src/CenterGrid.hpp b/editor/src/CenterGrid.hpp
index d14d51b..3148085 100644
--- a/editor/src/CenterGrid.hpp
+++ b/editor/src/CenterGrid.hpp
@@ -30,7 +30,7 @@ public:
     * @brief Setter function to update which sprite is currently selected and needs to be places when grid clicked
     * @param message TEMPORARY message that is shown when tile is clicked replace with sprite data
     */
-    void setCurrentSprite(const QPixmap pixmap);
+    void setCurrentSprite(const QPixmap pixmap, const int id);
 
     /**
     * @brief Function for drawing the currently selected sprite on a tile when tile is clicked
@@ -53,6 +53,12 @@ public:
     */
     GridItem* getTile(int row, int col) const;
 
+    /**
+    * @brief getter function for Vector of TileIDs
+    * @return QVector<QVector<int>> of TileIDs
+    */
+    QVector<QVector<int>> getTileIDs();
+
 private:
 
     /**
@@ -76,10 +82,20 @@ private:
     */
     QPixmap m_currentSprite;
 
+    /**
+    * @brief ID of the currently selected sprite
+    */
+    int m_currentSpriteID;
+
     /**
     * @brief 2D Vector holding all GridItems, making them accessible by position on grid
     */
     QVector<QVector<GridItem*>> gridItems;
+
+    /**
+    * @brief 2D Vector holding all GridItems, making them accessible by position on grid
+    */
+    QVector<QVector<int>> m_tileIDs;
 };
 
 } // namespace editor
\ No newline at end of file
diff --git a/editor/src/HDF5Handler.cpp b/editor/src/HDF5Handler.cpp
new file mode 100644
index 0000000..a3f9062
--- /dev/null
+++ b/editor/src/HDF5Handler.cpp
@@ -0,0 +1,63 @@
+#include "HDF5Handler.hpp"
+
+namespace editor
+{
+
+HDF5Handler::HDF5Handler() : m_Level("./tileset.png", 16)
+{
+}
+
+HDF5Handler::HDF5Handler(const std::filesystem::path path) : m_Level(path)
+{
+}
+
+void HDF5Handler::loadTilesheet(TileBar* tileBar) const
+{
+    const unsigned char* tiledata = m_Level.getSpritesheet().getSpritedata().data();
+    const int width = m_Level.getSpritesheet().getWidth();
+    const int height = m_Level.getSpritesheet().getHeight();
+
+    const QImage image(tiledata, width, height, QImage::Format_RGBA8888);
+    QPixmap pixmap = QPixmap::fromImage(image);
+
+    // TODO: Move this to the Spritesheet class in Liblvl
+    const int32_t maxId = (width / 16) * (height / 16);
+
+    QStringList tileNames;
+    QList<QPixmap> tiles;
+
+    for (int32_t i = 0; i < maxId; i++)
+    {
+        auto [x, y] = m_Level.getSpritesheet().getTilePosition(i);
+        QRect rect(x, y, 16, 16);
+        tileNames << QString("tile_%1").arg(i);
+        tiles << pixmap.copy(rect);
+    }
+
+    tileBar->populateList(tileNames, tiles);
+}
+
+void HDF5Handler::saveRoom(int id, QString name, QVector<QVector<int>> tileIDs, QVector<QVector<int>> mobIDs)
+{
+    lvl::Room newRoom(id, name.toStdString(), 64, 64);
+    for (int row = 0; row < 64; row++) 
+    {
+        for (int col = 0; col < 64; col++) 
+        {
+            newRoom.placeTile({col,row}, tileIDs[row][col]);
+            //newRoom->placeEnemy(pos, mobIDs[row][col]);
+        }
+    }
+    std::vector<lvl::Room>& roomsList = m_Level.getRooms();
+    roomsList.push_back(newRoom);
+    m_Level.saveChanges("./level.h5");
+}
+
+int HDF5Handler::getMaxID()
+{
+    std::vector<lvl::Room>& roomsList = m_Level.getRooms();
+    lvl::Room lastRoom = roomsList.back();
+    return lastRoom.getId();
+}
+
+}
\ No newline at end of file
diff --git a/editor/src/HDF5Handler.hpp b/editor/src/HDF5Handler.hpp
new file mode 100644
index 0000000..4000cce
--- /dev/null
+++ b/editor/src/HDF5Handler.hpp
@@ -0,0 +1,56 @@
+#pragma once
+#include <QImage>
+#include <QPixmap>
+#include <level.hpp>
+#include <room.hpp>
+#include "TileBar.hpp"
+#include <QMessageBox>
+
+namespace editor
+{
+
+class HDF5Handler
+{
+
+public:
+    /**
+    * @brief Create a new HDF5Handler (main interface with liblvl library)
+    */
+    HDF5Handler();
+
+    /**
+    * @brief Create a new HDF5Handler (main interface with liblvl library)
+    */
+    HDF5Handler(std::filesystem::path path);
+
+    /**
+    * @brief destructor for HDF5Handler, default
+    */
+    ~HDF5Handler() = default;
+
+    /**
+    * @brief loads the tilesheet and puts tiles into TileBar
+    * @param tileBar pointer to the TileBar to show the tiles in
+    */
+    void loadTilesheet(TileBar* tileBar) const;
+
+    /**
+    * @brief saves configured room to HDF5
+    * @param id ID of the room, has to be unique!
+    * @param name Name of the room
+    * @param tileIDs 2D QVector of the tileIDs
+    * @param mobIDs 2D QVector of the tileIDs
+    */
+    void saveRoom(int id, QString name, QVector<QVector<int>> tileIDs, QVector<QVector<int>> mobIDs);
+
+    /**
+    * @brief find maximum room-ID in current HDF5-file
+    * @return highest room-ID
+    */
+    int getMaxID();
+
+private:
+    lvl::Level m_Level;
+};
+
+} // editor
\ No newline at end of file
diff --git a/editor/src/MainWindow.cpp b/editor/src/MainWindow.cpp
index bf0ed0a..9451217 100644
--- a/editor/src/MainWindow.cpp
+++ b/editor/src/MainWindow.cpp
@@ -16,10 +16,24 @@ MainWindow::MainWindow(DialogSelection selected, const std::filesystem::path& pa
     QWidget* centralWidget = new QWidget(this);
     QHBoxLayout* mainLayout = new QHBoxLayout;
 
+    if (QFile::exists("./level.h5"))
+    {
+        std::filesystem::path path = "./level.h5";
+        HDF5Handler handler = HDF5Handler(path);
+        m_ID = handler.getMaxID() + 1;
+        std::cout << m_ID << std::endl;
+    } else
+    {
+        m_ID = 2;
+    }
+
     // Create the menu bar
     QMenuBar* menuBar = new QMenuBar(this);
     QMenu* fileMenu = menuBar->addMenu("File");
-    QMenu* settingsMenu = menuBar->addMenu("Settings");
+    //QMenu* settingsMenu = menuBar->addMenu("Settings");
+    QAction* saveRoomAction = new QAction("Save Room", this);
+    connect(saveRoomAction, &QAction::triggered, this, &MainWindow::saveLevel);
+    fileMenu->addAction(saveRoomAction);
     QAction* quitAction = new QAction("Quit", this);
     connect(quitAction, &QAction::triggered, this, &MainWindow::close);
     fileMenu->addAction(quitAction);
@@ -31,15 +45,8 @@ MainWindow::MainWindow(DialogSelection selected, const std::filesystem::path& pa
     m_leftWidget = leftWidget;
     connect(leftWidget, &editor::TileBar::itemClicked, this, &MainWindow::handleTileBarClick);
 
-    //temporary example, replace with real list of tileset
-    QStringList itemsLeft;
-    QList<QPixmap> pixmapsLeft;
-    for (int i = 1; i <= 100; ++i)
-    {
-        itemsLeft << QString("Item %1").arg(i);
-        pixmapsLeft << QPixmap("./editor/assets/images.png");
-    }
-    leftWidget->populateList(itemsLeft, pixmapsLeft);
+    const HDF5Handler handler;
+    handler.loadTilesheet(m_leftWidget);
 
     // Center grid
     CenterGrid* middleWidget = new CenterGrid(this);
@@ -72,9 +79,6 @@ MainWindow::MainWindow(DialogSelection selected, const std::filesystem::path& pa
     centralWidget->setLayout(mainLayout);
     setCentralWidget(centralWidget);
 
-    //testing centerGrid buttons
-    middleWidget->setCurrentSprite(QString::fromStdString("from MainWindow"));
-
     //initialize middleWidget as fully custom board
     middleWidget->drawPreset(5);
 }
@@ -83,7 +87,7 @@ void MainWindow::handleTileBarClick(int index, int id)
 {
     if (id == 0) 
     {
-        m_centerGrid->setCurrentSprite(m_leftWidget->getPixmapByIndex(index));
+        m_centerGrid->setCurrentSprite(m_leftWidget->getPixmapByIndex(index), index);
     } else 
     {
         QMessageBox::StandardButton confirmationPopup;
@@ -100,5 +104,26 @@ void MainWindow::handleTileBarClick(int index, int id)
     }
 }
 
+void MainWindow::saveLevel() 
+{
+    QVector<QVector<int>> tileIDs = m_centerGrid->getTileIDs();
+    QVector<QVector<int>> mobIDs;
+    QString name = "testlevel1234";
+
+    if (QFile::exists("./level.h5")) 
+    {
+        std::cout << "using new handler" << std::endl;
+        std::filesystem::path path = "./level.h5";
+        HDF5Handler handler = HDF5Handler(path);
+        handler.saveRoom(m_ID, name, tileIDs, mobIDs);
+    } else 
+    {
+        std::cout << "using default handler" << std::endl;
+        HDF5Handler handler = HDF5Handler();
+        handler.saveRoom(m_ID, name, tileIDs, mobIDs);
+    }
+    m_ID++;
+}
+
 }
 
diff --git a/editor/src/MainWindow.hpp b/editor/src/MainWindow.hpp
index de730ae..adb32c0 100644
--- a/editor/src/MainWindow.hpp
+++ b/editor/src/MainWindow.hpp
@@ -13,6 +13,7 @@
 #include "StartupDialog.hpp"
 #include "CenterGrid.hpp"
 #include "TileBar.hpp"
+#include "HDF5Handler.hpp"
 
 
 namespace editor
@@ -42,6 +43,18 @@ private slots:
   void handleTileBarClick(int index, int id);
 
 private:
+
+  /**
+  * @brief Saves room to HDF5 when called in menu
+  */
+  void saveLevel();
+
+  /**
+  * @brief Converts 2D QVector to 1D std::vector for saving
+  * @return std::vector<int> 1D std::vector with tileIDs that can be saved in HDF5
+  */
+  std::vector<int> flatten(const QVector<QVector<int>>& input);
+
   /**
   * @brief Pointer to the centerGrid 
   */
@@ -52,6 +65,11 @@ private:
   */
   TileBar* m_leftWidget;
 
+  /**
+  * @brief Room ID to use for saving next room
+  */
+  int m_ID;
+
 };
 
 } // editor
-- 
GitLab