From b9d8fd853c56d4de5c16d6c42c4c6e2151e4a786 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 12:17:17 +0300 Subject: [PATCH 01/80] Texture Editor initial commit: Added support of TEX file format (all operations) Migrated Conan to 2.x version Removed primitives viewer (unstable thing, will be replaced later) --- .github/workflows/build.yml | 11 +- BMEdit/Editor/CMakeLists.txt | 8 +- .../Editor/Include/Editor/TextureProcessor.h | 32 ++ .../Models/ScenePrimitivesFilterModel.h | 52 -- .../Include/Models/ScenePrimitivesModel.h | 46 -- .../Include/Models/SceneTextureFilterModel.h | 26 + .../Include/Models/SceneTexturesModel.h | 60 +++ BMEdit/Editor/Include/Types/QTextureREF.h | 20 + .../Editor/Source/Edtor/TextureProcessor.cpp | 344 ++++++++++++++ .../Models/ScenePrimitivesFilterModel.cpp | 128 ----- .../Source/Models/ScenePrimitivesModel.cpp | 168 ------- .../Source/Models/SceneTextureFilterModel.cpp | 57 +++ .../Source/Models/SceneTexturesModel.cpp | 168 +++++++ BMEdit/Editor/UI/Include/BMEditMainWindow.h | 14 +- .../Editor/UI/Include/ImportTextureDialog.h | 42 ++ BMEdit/Editor/UI/Include/ViewTexturesDialog.h | 60 +++ BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 154 ++---- .../Editor/UI/Source/ImportTextureDialog.cpp | 161 +++++++ .../Editor/UI/Source/ViewTexturesDialog.cpp | 444 ++++++++++++++++++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 170 +------ BMEdit/Editor/UI/UI/ImportTextureDialog.ui | 132 ++++++ BMEdit/Editor/UI/UI/ViewTexturesDialog.ui | 196 ++++++++ BMEdit/GameLib/CMakeLists.txt | 3 +- BMEdit/GameLib/Include/GameLib/Level.h | 14 + BMEdit/GameLib/Include/GameLib/TEX/TEX.h | 8 + BMEdit/GameLib/Include/GameLib/TEX/TEXEntry.h | 91 ++++ .../Include/GameLib/TEX/TEXEntryType.h | 28 ++ .../GameLib/Include/GameLib/TEX/TEXHeader.h | 34 ++ .../GameLib/Include/GameLib/TEX/TEXReader.h | 33 ++ BMEdit/GameLib/Include/GameLib/TEX/TEXTypes.h | 10 + .../GameLib/Include/GameLib/TEX/TEXWriter.h | 17 + BMEdit/GameLib/Source/GameLib/Level.cpp | 42 ++ .../GameLib/Source/GameLib/TEX/TEXEntry.cpp | 221 +++++++++ .../GameLib/Source/GameLib/TEX/TEXHeader.cpp | 36 ++ .../GameLib/Source/GameLib/TEX/TEXReader.cpp | 91 ++++ .../GameLib/Source/GameLib/TEX/TEXWriter.cpp | 46 ++ CMakeLists.txt | 18 +- README.md | 15 +- conanfile.txt | 7 +- 39 files changed, 2493 insertions(+), 714 deletions(-) create mode 100644 BMEdit/Editor/Include/Editor/TextureProcessor.h delete mode 100644 BMEdit/Editor/Include/Models/ScenePrimitivesFilterModel.h delete mode 100644 BMEdit/Editor/Include/Models/ScenePrimitivesModel.h create mode 100644 BMEdit/Editor/Include/Models/SceneTextureFilterModel.h create mode 100644 BMEdit/Editor/Include/Models/SceneTexturesModel.h create mode 100644 BMEdit/Editor/Include/Types/QTextureREF.h create mode 100644 BMEdit/Editor/Source/Edtor/TextureProcessor.cpp delete mode 100644 BMEdit/Editor/Source/Models/ScenePrimitivesFilterModel.cpp delete mode 100644 BMEdit/Editor/Source/Models/ScenePrimitivesModel.cpp create mode 100644 BMEdit/Editor/Source/Models/SceneTextureFilterModel.cpp create mode 100644 BMEdit/Editor/Source/Models/SceneTexturesModel.cpp create mode 100644 BMEdit/Editor/UI/Include/ImportTextureDialog.h create mode 100644 BMEdit/Editor/UI/Include/ViewTexturesDialog.h create mode 100644 BMEdit/Editor/UI/Source/ImportTextureDialog.cpp create mode 100644 BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp create mode 100644 BMEdit/Editor/UI/UI/ImportTextureDialog.ui create mode 100644 BMEdit/Editor/UI/UI/ViewTexturesDialog.ui create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEX.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXEntry.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXEntryType.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXReader.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXTypes.h create mode 100644 BMEdit/GameLib/Include/GameLib/TEX/TEXWriter.h create mode 100644 BMEdit/GameLib/Source/GameLib/TEX/TEXEntry.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/TEX/TEXReader.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 629c826..175fe7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: id: conan uses: turtlebrowser/get-conan@main with: - version: 1.54.0 + version: 2.0.9 - name: Make build folder and install Conan dependencies run: | @@ -70,22 +70,13 @@ jobs: mv build/bin/Qt6OpenGLWidgets.dll dist mv build/bin/Qt6Svg.dll dist mv build/bin/Qt6Widgets.dll dist - mv build/bin/Qt63DCore.dll dist - mv build/bin/Qt63DRender.dll dist - mv build/bin/Qt63DInput.dll dist - mv build/bin/Qt63DAnimation.dll dist - mv build/bin/Qt63DExtras.dll dist mv build/bin/Qt6Network.dll dist mv build/bin/Qt6Concurrent.dll dist - mv build/bin/zip.dll dist mv build/bin/translations dist/translations mv build/bin/styles dist/styles mv build/bin/platforms dist/platforms - mv build/bin/renderers dist/renderers - mv build/bin/sceneparsers dist/sceneparsers mv build/bin/networkinformation dist/networkinformation mv build/bin/tls dist/tls - mv build/bin/geometryloaders dist/geometryloaders mv build/bin/imageformats dist/imageformats mv build/bin/iconengines dist/iconengines mv Assets/g1 dist/g1 diff --git a/BMEdit/Editor/CMakeLists.txt b/BMEdit/Editor/CMakeLists.txt index f66525f..b9effc8 100644 --- a/BMEdit/Editor/CMakeLists.txt +++ b/BMEdit/Editor/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC OFF) -find_package(Qt6 6.4.1 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets 3DCore 3DRender 3DInput 3DLogic) +find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGL OpenGLWidgets) find_package(OpenGL REQUIRED) message(STATUS "Found Qt component versions: ") @@ -62,6 +62,8 @@ target_compile_definitions(Editor ) # --- Required GameLib & Qt6 -target_link_libraries(Editor PUBLIC Qt6::Widgets OpenGL::GL Qt6::OpenGL Qt6::OpenGLWidgets Qt6::3DCore Qt6::3DRender Qt6::3DLogic) +target_link_libraries(Editor PUBLIC Qt6::Widgets OpenGL::GL Qt6::OpenGL Qt6::OpenGLWidgets) target_link_libraries(Editor PRIVATE GameLib) -target_link_libraries(Editor PRIVATE zip zlib bz2 lzma zstd_static) \ No newline at end of file +target_link_libraries(Editor PRIVATE ${libzip_LIBRARIES} ${ZLIB_LIBRARIES} ${libsquish_LIBRARIES}) +target_include_directories(Editor PRIVATE ${ZLIB_INCLUDE_DIRS} ${libzip_INCLUDE_DIRS} ${libsquish_INCLUDE_DIRS}) +target_compile_definitions(Editor PRIVATE ${libzip_DEFINITIONS} ${libsquish_DEFINITIONS}) \ No newline at end of file diff --git a/BMEdit/Editor/Include/Editor/TextureProcessor.h b/BMEdit/Editor/Include/Editor/TextureProcessor.h new file mode 100644 index 0000000..9612fe7 --- /dev/null +++ b/BMEdit/Editor/Include/Editor/TextureProcessor.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace editor +{ + class TextureProcessor + { + public: + // Compress/decompress + + /** + * @fn decompressRGBA + * @brief decompress TEXEntry into RGBA buffer from various formats (RGBA,U8V8,I8,DXT1,DXT3) + * @param texEntry - TEX file entry to decompress + * @param realWidth - [out] calculated width of texture (by mip level) + * @param realHeight - [out] calculated height of texture (by mip level) + * @param mipLevel - mip level, pass 0 when no mip levels presented + * @return valid pointer to memory of size W*H*4 with pixels, or nullptr on error occurred + */ + static std::unique_ptr decompressRGBA(const gamelib::tex::TEXEntry& texEntry, uint16_t& realWidth, uint16_t& realHeight, std::size_t mipLevel = 0); + + // Import/Export + static bool exportTEXEntryAsPNG(const gamelib::tex::TEXEntry& texEntry, const std::filesystem::path& filePath, std::size_t mipLevel = 0); + static bool importTextureToEntry(gamelib::tex::TEXEntry& texEntry, const QString& texturePath, const QString& textureName, gamelib::tex::TEXEntryType targetFormat, uint8_t mipLevelsCount); + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/ScenePrimitivesFilterModel.h b/BMEdit/Editor/Include/Models/ScenePrimitivesFilterModel.h deleted file mode 100644 index 058dfee..0000000 --- a/BMEdit/Editor/Include/Models/ScenePrimitivesFilterModel.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include -#include - - -namespace models -{ - constexpr std::array g_DefaultVertexFormats = { - gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_10, - gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_24, - gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_28, - gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_34 - }; - - enum ScenePrimitivesFilterEntry : int - { - FilterAllow_Zero = 1 << 0, - FilterAllow_Unknown = 1 << 1, - FilterAllow_Description = 1 << 2, - FilterAllow_Index = 1 << 3, - FilterAllow_Vertex = 1 << 4, - - // Common values - Allow_None = 0, - Allow_All = FilterAllow_Zero | FilterAllow_Unknown | FilterAllow_Description | FilterAllow_Index | FilterAllow_Vertex - }; - - class ScenePrimitivesFilterModel : public QSortFilterProxyModel - { - Q_OBJECT - public: - using QSortFilterProxyModel::QSortFilterProxyModel; - - [[nodiscard]] int getFilterMask() const; - void setAllowAll(); - void setAllowNone(); - void addFilterEntry(ScenePrimitivesFilterEntry entry); - void removeFilterEntry(ScenePrimitivesFilterEntry entry); - bool isVertexFormatAllowed(gamelib::prm::PRMVertexBufferFormat vertexBufferFormat); - void setVertexFormatAllowed(gamelib::prm::PRMVertexBufferFormat vertexBufferFormat, bool isAllowed); - void resetToDefaults(); - - protected: - [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - - private: - int m_filterMask { 0 }; - std::set m_allowedVertexFormats { g_DefaultVertexFormats.begin(), g_DefaultVertexFormats.end() }; - }; -} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/ScenePrimitivesModel.h b/BMEdit/Editor/Include/Models/ScenePrimitivesModel.h deleted file mode 100644 index a8332ed..0000000 --- a/BMEdit/Editor/Include/Models/ScenePrimitivesModel.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include - - -namespace models -{ - class ScenePrimitivesModel : public QAbstractTableModel - { - Q_OBJECT - - private: - enum ColumnID : int { - CID_INDEX = 0, // Index of chunk - CID_KIND, // Kind of chunk - CID_SIZE, // Size of chunk - CID_INDICES, // Only for index buffer - CID_VERTICES, // Only for vertex buffer - CID_PTR_OBJECTS, // Only for description - CID_PTR_PARTS, // Only for description - - //NOTE: Don't forgot to add string view at g_ColNames array (see .cpp for details) - - // --- END --- - CID_MAX_COLUMNS - }; - - public: - ScenePrimitivesModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - - void setLevel(gamelib::Level* level); - void resetLevel(); - - private: - gamelib::Level* m_level { nullptr }; - }; -} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/SceneTextureFilterModel.h b/BMEdit/Editor/Include/Models/SceneTextureFilterModel.h new file mode 100644 index 0000000..214a785 --- /dev/null +++ b/BMEdit/Editor/Include/Models/SceneTextureFilterModel.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + + +namespace models +{ + class SceneTextureFilterModel : public QSortFilterProxyModel + { + Q_OBJECT + public: + SceneTextureFilterModel(QObject* parent = nullptr); + + void setTextureNameFilter(const QString& query); + [[nodiscard]] const QString& getTextureNameFilter() const; + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + + private: + QString m_textureNameFilter {}; + mutable QMap m_filterResultsCache; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/SceneTexturesModel.h b/BMEdit/Editor/Include/Models/SceneTexturesModel.h new file mode 100644 index 0000000..17d3ac5 --- /dev/null +++ b/BMEdit/Editor/Include/Models/SceneTexturesModel.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + + +namespace gamelib +{ + class Level; +} + +namespace models +{ + class SceneTexturesModel : public QAbstractTableModel + { + Q_OBJECT + public: + enum ColumnID : int { + INDEX = 0, + NAME, + RESOLUTION, + FORMAT, + OFFSET, +#ifndef NDEBUG + CUBEMAP, +#endif + + MAX_COLUMNS, + }; + + /** + * @enum Roles + * @brief Contains all custom roles + */ + enum Roles : int { + R_TEXTURE_REF = Qt::UserRole + 1 ///< Get texture ref by QModelIndex + }; + + public: + SceneTexturesModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void setLevel(const gamelib::Level *level); + void resetLevel(); + const gamelib::Level *getLevel() const; + gamelib::Level *getLevel(); + + private: + bool isReady() const; + + private: + const gamelib::Level *m_level { nullptr }; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Types/QTextureREF.h b/BMEdit/Editor/Include/Types/QTextureREF.h new file mode 100644 index 0000000..0c95920 --- /dev/null +++ b/BMEdit/Editor/Include/Types/QTextureREF.h @@ -0,0 +1,20 @@ +#pragma once + +#include + + +namespace models +{ + class SceneTexturesModel; +} + +namespace types +{ + struct QTextureREF + { + uint32_t textureIndex; + const models::SceneTexturesModel* ownerModel { nullptr }; + }; +} + +Q_DECLARE_METATYPE(types::QTextureREF) \ No newline at end of file diff --git a/BMEdit/Editor/Source/Edtor/TextureProcessor.cpp b/BMEdit/Editor/Source/Edtor/TextureProcessor.cpp new file mode 100644 index 0000000..2bed69f --- /dev/null +++ b/BMEdit/Editor/Source/Edtor/TextureProcessor.cpp @@ -0,0 +1,344 @@ +#include +#include // for PNG/JPG exporters +#include // for BMP exporter +#include // for in-memory exporters +#include // for DXT1/DXT3 compression/decompression/import/export +#include // std::pow +#include + + +namespace editor +{ + std::unique_ptr TextureProcessor::decompressRGBA(const gamelib::tex::TEXEntry& textureEntry, uint16_t& realWidth, uint16_t& realHeight, std::size_t mipLevel) + { + using EntryType = gamelib::tex::TEXEntryType; + + realWidth = static_cast(textureEntry.m_width / std::pow(2, mipLevel)); + realHeight = static_cast(textureEntry.m_height / std::pow(2, mipLevel)); + + if (mipLevel > textureEntry.m_numOfMipMaps) + { + assert(false && "Bad mip"); + return nullptr; + } + + if (auto format = textureEntry.m_type1; format == EntryType::ET_BITMAP_32) + { + // RGBA: just copy into dest memory and return new buffer + auto result = std::make_unique(realWidth * realHeight * 4); + std::memcpy(result.get(), textureEntry.m_mipLevels.at(mipLevel).m_buffer.get(), realWidth * realHeight * 4); + return result; + } + else if (format == EntryType::ET_BITMAP_DXT1 || format == EntryType::ET_BITMAP_DXT3) + { + // DXT1, DXT3: Process via squish + int flags = 0; + + flags |= (format == EntryType::ET_BITMAP_DXT1 ? squish::kDxt1 : 0); + flags |= (format == EntryType::ET_BITMAP_DXT3 ? squish::kDxt3 : 0); + auto result = std::make_unique(static_cast(realWidth) * static_cast(realHeight) * 4); + squish::DecompressImage(result.get(), realWidth, realHeight, textureEntry.m_mipLevels.at(mipLevel).m_buffer.get(), flags); + + return result; + } + else if (format == EntryType::ET_BITMAP_PAL) + { + // PAL: This format based on palette. Generally we need to iterate over each "src pixel" and find color in palette (just jmp) + if (!textureEntry.m_palPalette.has_value()) + { + assert(false && "Bad PAL palette!"); + return nullptr; + } + + const auto& palette = textureEntry.m_palPalette.value(); + const int totalSrcPixels = static_cast(realWidth) * static_cast(realHeight); + const int totalDstPixels = totalSrcPixels * 4; + auto result = std::make_unique(totalDstPixels); + + for (int i = 0; i < totalSrcPixels; ++i) + { + *reinterpret_cast(&result.get()[i * 4]) = *reinterpret_cast(&palette.m_data.get()[4 * textureEntry.m_mipLevels.at(mipLevel).m_buffer.get()[i]]); + } + + return result; + } + else if (format == EntryType::ET_BITMAP_U8V8) + { + // Reversed by DronCode (ZBitmapU8V8::sub_43E8E0) + const int totalSrcPixels = static_cast(realWidth) * static_cast(realHeight); + const int totalPixels = totalSrcPixels * 4; + auto result = std::make_unique(totalPixels); + + for (int i = 0; i < totalSrcPixels; i++) + { + const uint16_t RG = *reinterpret_cast(&textureEntry.m_mipLevels.at(mipLevel).m_buffer.get()[i * 2 + 0]); + const uint32_t color = (RG << 8) | 0xFF0000FF; + *reinterpret_cast(&result.get()[i * 4 + 0]) = color; + } + + return result; + } + else if (format == EntryType::ET_BITMAP_I8) + { + // Reversed by DronCode (ZBitmapI8::sub_43EA90) + const int totalSrcPixels = static_cast(realWidth) * static_cast(realHeight); + const int totalPixels = totalSrcPixels * 4; + auto result = std::make_unique(totalPixels); + + for (int i = 0; i < totalSrcPixels; i++) + { + *reinterpret_cast(&result.get()[i * 4]) = 0x1010101u * static_cast(textureEntry.m_mipLevels.at(mipLevel).m_buffer.get()[i]); + } + + return result; + } + + //PALO: Obsolete thing. See ZBitmapPalOpac::sub_43E720 for details. To reverse when we will find at least 1 usage. + + assert(false && "Unsupported routine!"); + return nullptr; + } + + bool TextureProcessor::exportTEXEntryAsPNG(const gamelib::tex::TEXEntry& texEntry, const std::filesystem::path& filePath, std::size_t mipLevel) + { + uint16_t w, h; + auto buffer = TextureProcessor::decompressRGBA(texEntry, w, h, mipLevel); + + if (!buffer) + { + return false; + } + + QImage image(buffer.get(), w, h, QImage::Format::Format_RGBA8888); + image.save(QString::fromStdString(filePath.string()), "PNG", 100); + + return true; + } + + bool TextureProcessor::importTextureToEntry(gamelib::tex::TEXEntry& targetTexture, const QString& texturePath, const QString& textureName, gamelib::tex::TEXEntryType targetFormat, uint8_t mipLevelsNr) + { + // Read texture as RGBA, convert to final format, generate MIP levels and override TEXEntry, PALPalette and other stuff + QImage sourceImage { texturePath }; + if (sourceImage.isNull()) + return false; + + // Convert to RGBA8888 + sourceImage.convertTo(QImage::Format::Format_RGBA8888); + + auto isPow2 = [](uint32_t v) -> bool + { + return (v & (v - 1)) == 0; + }; + + // Check that source image is a pow of 2 + if (!isPow2(sourceImage.width()) || !isPow2(sourceImage.height())) + { + auto getPreviousPowOf2 = [](uint32_t current) -> uint32_t + { +#ifdef Q_CC_MSVC + return 1u << ((sizeof(current) * 8 - 1) - _lzcnt_u32(current)); +#else + return 1u << ((sizeof(current) * 8 - 1) - __builtin_clz(current)); +#endif + }; + + uint32_t w = sourceImage.width(); + uint32_t h = sourceImage.height(); + + if (!isPow2(sourceImage.width())) + { + w = getPreviousPowOf2(w); + } + + if (!isPow2(sourceImage.height())) + { + h = getPreviousPowOf2(h); + } + + auto newImage = sourceImage.scaled(static_cast(w), static_cast(h), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::FastTransformation); + sourceImage = std::move(newImage); + } + + if (targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_PAL || targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_PAL_OPAC) + { + // Because in PAL and PAL_OPAC we can't have more than 1 palette + mipLevelsNr = 1; + } + + std::vector mipLevels; + mipLevels.resize(mipLevelsNr); + + mipLevels[0] = std::move(sourceImage); + for (int mipLevel = 1; mipLevel < mipLevelsNr; mipLevel++) + { + auto currentW = static_cast(mipLevels[0].width() / std::pow(2, mipLevel)); + auto currentH = static_cast(mipLevels[0].height() / std::pow(2, mipLevel)); + + mipLevels[mipLevel] = mipLevels[0].scaled(currentW, currentH, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::FastTransformation); + } + + targetTexture.m_mipLevels.resize(mipLevelsNr); + + for (int i = 0; i < mipLevelsNr; i++) + { + const auto& sourceMIP = mipLevels[i]; + auto& textureMIP = targetTexture.m_mipLevels[i]; + + switch (targetFormat) + { + case gamelib::tex::TEXEntryType::ET_BITMAP_I8: + // Use simple grayscale and divide result by 0x10 + { + std::unique_ptr pixelBuffer = std::make_unique(sourceMIP.width() * sourceMIP.height()); + + for (int pixelIndex = 0; pixelIndex < sourceMIP.width() * sourceMIP.height(); pixelIndex++) + { + const uint8_t r = *reinterpret_cast(&sourceMIP.bits()[pixelIndex * 4 + 0]); + const uint8_t g = *reinterpret_cast(&sourceMIP.bits()[pixelIndex * 4 + 1]); + const uint8_t b = *reinterpret_cast(&sourceMIP.bits()[pixelIndex * 4 + 2]); + // Do we need to use alpha too? Idk + const uint32_t mid = ((r + g + b) / 3) / 0x10; + *reinterpret_cast(&pixelBuffer[pixelIndex]) = static_cast(mid); + } + + textureMIP.m_mipLevelSize = sourceMIP.width() * sourceMIP.height(); + textureMIP.m_buffer = std::move(pixelBuffer); + } + break; + case gamelib::tex::TEXEntryType::ET_BITMAP_U8V8: + // Just use only red and green pixels + { + std::unique_ptr pixelBuffer = std::make_unique(sourceMIP.width() * sourceMIP.height() * 2); + + for (int pixelIndex = 0; pixelIndex < sourceMIP.width() * sourceMIP.height(); pixelIndex++) + { + *reinterpret_cast(&pixelBuffer[pixelIndex * 2]) = + (*reinterpret_cast(&sourceMIP.bits()[pixelIndex * 4 + 0])) << 8 | + *reinterpret_cast(&sourceMIP.bits()[pixelIndex * 4 + 1]); + } + + textureMIP.m_mipLevelSize = sourceMIP.width() * sourceMIP.height(); + textureMIP.m_buffer = std::move(pixelBuffer); + } + break; + case gamelib::tex::TEXEntryType::ET_BITMAP_PAL: + // "EZ" + { + /** + * @note: In this format we using dithering and this need to have only 1 MIP level. + * We can't have more than 1 MIP level because we can't guarantee that for other MIP levels our palette will be OK. + * So, we will break loop after first entry done + * + * @note: Ok, it really stupid solution, but in Format_Indexed8 format itself has max 256 palette so we can convert image to this pixel format, + * write into memory and collect palette itself. + * + * @note: It's really weird results, quality bad, need to investigate it later. Workaround: use RGBA! + */ + QImage bitmap = sourceMIP.convertToFormat( + QImage::Format::Format_Indexed8, + Qt::ImageConversionFlag::ThresholdDither | Qt::ImageConversionFlag::ThresholdAlphaDither | Qt::ImageConversionFlag::AvoidDither + ); + std::unique_ptr pixelBuffer = std::make_unique(bitmap.width() * bitmap.height()); + + // Ok, now it loaded, process pixels + std::uint8_t currentIndex = 0; + std::map colorToIndex; + + std::uint32_t pixelIndex = 0; + for (int y = 0; y < bitmap.height(); y++) + { + for (int x = 0; x < bitmap.width(); x++) + { + QRgb pixel = bitmap.pixel(x, y); + + if (colorToIndex.contains(pixel)) + { + pixelBuffer[pixelIndex] = colorToIndex[pixel]; + } + else + { + currentIndex++; + colorToIndex[pixel] = currentIndex; + pixelBuffer[pixelIndex] = currentIndex; + } + + pixelIndex++; + } + } + + if (colorToIndex.size() > 256) + { + // Too big palette! + return false; + } + + // Ok, we have a palette in colorToIndex and now we need to save only values into our buffer + gamelib::tex::PALPalette palette; + palette.m_size = colorToIndex.size(); + palette.m_data = std::make_unique(palette.m_size * 4); + + std::uint32_t colorIndex = 0; + for (const auto& [color, _gIndex] : colorToIndex) + { + *reinterpret_cast(&palette.m_data.get()[colorIndex * 4]) = color; + ++colorIndex; + } + + // Save palette + targetTexture.m_palPalette.emplace(std::move(palette)); + + // Now save pixel buffer + textureMIP.m_buffer = std::move(pixelBuffer); + textureMIP.m_mipLevelSize = bitmap.width() * bitmap.height(); + } + break; + case gamelib::tex::TEXEntryType::ET_BITMAP_32: + // Copy data + { + uint32_t w = sourceMIP.width(); + uint32_t h = sourceMIP.height(); + textureMIP.m_mipLevelSize = w * h * 4; + textureMIP.m_buffer = std::make_unique(textureMIP.m_mipLevelSize); + std::memcpy(textureMIP.m_buffer.get(), sourceMIP.bits(), textureMIP.m_mipLevelSize); + } + break; + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT1: + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT3: + // Use libsquish to create DXT1/DXT3 buffers + { + uint32_t w = sourceMIP.width(); + uint32_t h = sourceMIP.height(); + int flags = 0; + + flags |= (targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_DXT1 ? squish::kDxt1 : 0); + flags |= (targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_DXT3 ? squish::kDxt3 : 0); + + // Calculate & allocate space + textureMIP.m_mipLevelSize = squish::GetStorageRequirements(static_cast(w), static_cast(h), flags); + textureMIP.m_buffer = std::make_unique(textureMIP.m_mipLevelSize); + + // Compress image directly into pre-allocated space + squish::CompressImage( + reinterpret_cast(sourceMIP.bits()), + static_cast(w), static_cast(h), + (void*)textureMIP.m_buffer.get(), + flags, + nullptr); + } + break; + default: + return false; + } + } + + // Update internals + targetTexture.m_width = static_cast(mipLevels[0].width()); + targetTexture.m_height = static_cast(mipLevels[0].height()); + targetTexture.m_fileName = textureName.isEmpty() ? std::nullopt : std::make_optional(textureName.toStdString()); + targetTexture.m_type1 = targetTexture.m_type2 = targetFormat; + targetTexture.m_numOfMipMaps = static_cast(targetTexture.m_mipLevels.size()); + targetTexture.m_fileSize = targetTexture.calculateSize(); // Update file size after all manipulations + + return true; + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/ScenePrimitivesFilterModel.cpp b/BMEdit/Editor/Source/Models/ScenePrimitivesFilterModel.cpp deleted file mode 100644 index fccbaef..0000000 --- a/BMEdit/Editor/Source/Models/ScenePrimitivesFilterModel.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include -#include -#include - - -using namespace models; - - - -int ScenePrimitivesFilterModel::getFilterMask() const -{ - return m_filterMask; -} - -void ScenePrimitivesFilterModel::setAllowAll() -{ - if (m_filterMask != ScenePrimitivesFilterEntry::Allow_All) - { - m_filterMask = ScenePrimitivesFilterEntry::Allow_All; - invalidateRowsFilter(); - } -} - -void ScenePrimitivesFilterModel::setAllowNone() -{ - if (m_filterMask != ScenePrimitivesFilterEntry::Allow_None) - { - m_filterMask = ScenePrimitivesFilterEntry::Allow_None; - invalidateRowsFilter(); - } -} - -void ScenePrimitivesFilterModel::addFilterEntry(ScenePrimitivesFilterEntry entry) -{ - if (!(m_filterMask & entry)) - { - m_filterMask |= entry; - invalidateRowsFilter(); - } -} - -void ScenePrimitivesFilterModel::removeFilterEntry(ScenePrimitivesFilterEntry entry) -{ - if (m_filterMask & entry) - { - m_filterMask &= ~entry; - invalidateRowsFilter(); - } -} - -bool ScenePrimitivesFilterModel::isVertexFormatAllowed(gamelib::prm::PRMVertexBufferFormat vertexBufferFormat) -{ - return m_allowedVertexFormats.contains(vertexBufferFormat); -} - -void ScenePrimitivesFilterModel::setVertexFormatAllowed(gamelib::prm::PRMVertexBufferFormat vertexBufferFormat, bool isAllowed) -{ - if (isAllowed) - { - auto [iter, isInserted] = m_allowedVertexFormats.emplace(vertexBufferFormat); - if (isInserted) - { - invalidateRowsFilter(); - } - } - else - { - if (auto it = m_allowedVertexFormats.find(vertexBufferFormat); it != m_allowedVertexFormats.end()) - { - m_allowedVertexFormats.erase(vertexBufferFormat); - invalidateRowsFilter(); - } - } -} - -void ScenePrimitivesFilterModel::resetToDefaults() -{ - setAllowAll(); - m_allowedVertexFormats = { g_DefaultVertexFormats.begin(), g_DefaultVertexFormats.end() }; -} - -bool ScenePrimitivesFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const -{ - // Pre-opt - if (m_filterMask == ScenePrimitivesFilterEntry::Allow_None) - { - return false; - } - - if (m_filterMask == ScenePrimitivesFilterEntry::Allow_All) - { - return true; - } - - QModelIndex entryIndex = sourceModel()->index(sourceRow, 0, sourceParent); - - const auto value = sourceModel()->data(entryIndex, types::kChunkKindRole).value(); - - switch (value) - { - case gamelib::prm::PRMChunkRecognizedKind::CRK_ZERO_CHUNK: return m_filterMask & ScenePrimitivesFilterEntry::FilterAllow_Zero; - case gamelib::prm::PRMChunkRecognizedKind::CRK_INDEX_BUFFER: return m_filterMask & ScenePrimitivesFilterEntry::FilterAllow_Index; - case gamelib::prm::PRMChunkRecognizedKind::CRK_VERTEX_BUFFER: - { - if (m_filterMask & ScenePrimitivesFilterEntry::FilterAllow_Vertex) - { - if (g_DefaultVertexFormats.size() != m_allowedVertexFormats.size()) - { - auto format = sourceModel()->data(entryIndex, types::kChunkVertexFormatRole).value(); - if (m_allowedVertexFormats.contains(format)) - { - return true; - } - } - else - { - return true; - } - } - - return false; - } - case gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER: return m_filterMask & ScenePrimitivesFilterEntry::FilterAllow_Description; - case gamelib::prm::PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER: return m_filterMask & ScenePrimitivesFilterEntry::FilterAllow_Unknown; - } - - return false; -} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/ScenePrimitivesModel.cpp b/BMEdit/Editor/Source/Models/ScenePrimitivesModel.cpp deleted file mode 100644 index 8550bb0..0000000 --- a/BMEdit/Editor/Source/Models/ScenePrimitivesModel.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include -#include - - -using namespace models; - -ScenePrimitivesModel::ScenePrimitivesModel(QObject *parent) : QAbstractTableModel(parent) -{ -} - -int ScenePrimitivesModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - - if (!m_level) - return 0; - - return static_cast(m_level->getLevelGeometry()->chunks.size()); -} - -int ScenePrimitivesModel::columnCount(const QModelIndex &parent) const -{ - return ColumnID::CID_MAX_COLUMNS; -} - -QVariant ScenePrimitivesModel::data(const QModelIndex &index, int role) const -{ - auto getKindAsQString = [](gamelib::prm::PRMChunkRecognizedKind kind) -> QString - { - switch (kind) - { - case gamelib::prm::PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER: return "UNKNOWN BUFFER"; - case gamelib::prm::PRMChunkRecognizedKind::CRK_VERTEX_BUFFER: return "VERTEX BUFFER"; - case gamelib::prm::PRMChunkRecognizedKind::CRK_INDEX_BUFFER: return "INDEX BUFFER"; - case gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER: return "DESCRIPTION BUFFER"; - case gamelib::prm::PRMChunkRecognizedKind::CRK_ZERO_CHUNK: return ""; - } - - return "???"; - }; - - auto getVertexFormatAsQString = [](gamelib::prm::PRMVertexBufferFormat bufferFormat) -> QString - { - switch (bufferFormat) - { - case gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_10: return "Vertex Format 10"; - case gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_24: return "Vertex Format 24"; - case gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_28: return "Vertex Format 28"; - case gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_34: return "Vertex Format 34"; - case gamelib::prm::PRMVertexBufferFormat::VBF_UNKNOWN_VERTEX: return "UNKNOWN FORMAT"; - } - - return "???"; - }; - - if (!m_level) - { - return {}; - } - - auto& chk = m_level->getLevelGeometry()->chunks.at(index.row()); - - if (role == Qt::DisplayRole) - { - switch (static_cast(index.column())) - { - case ColumnID::CID_INDEX: return chk.getIndex(); - case ColumnID::CID_KIND: return getKindAsQString(chk.getKind()); - case ColumnID::CID_SIZE: return chk.getBuffer().size(); - case ColumnID::CID_VERTICES: - { - if (chk.getKind() == gamelib::prm::PRMChunkRecognizedKind::CRK_VERTEX_BUFFER) - { - return getVertexFormatAsQString(chk.getVertexBufferHeader()->vertexFormat); - } - break; - } - case ColumnID::CID_INDICES: - { - if (chk.getKind() == gamelib::prm::PRMChunkRecognizedKind::CRK_INDEX_BUFFER) - { - return chk.getIndexBufferHeader()->indicesCount; - } - break; - } - case ColumnID::CID_PTR_OBJECTS: - { - if (chk.getKind() == gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER) - { - return QString("%1 (0x%2)").arg(chk.getDescriptionBufferHeader()->ptrObjects).arg(chk.getDescriptionBufferHeader()->ptrObjects, 8, 16, QChar('0')); - } - break; - } - case ColumnID::CID_PTR_PARTS: - { - if (chk.getKind() == gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER) - { - return QString("%1 (0x%2)").arg(chk.getDescriptionBufferHeader()->ptrParts).arg(chk.getDescriptionBufferHeader()->ptrParts, 8, 16, QChar('0')); - } - break; - } - default: return {}; - } - } - else if (role == types::kChunkKindRole) - { - return QVariant::fromValue(chk.getKind()); - } - else if (role == types::kChunkIndexRole) - { - return chk.getIndex(); - } - else if (role == types::kChunkVertexFormatRole) - { - if (auto chkVertexBufferHeader = chk.getVertexBufferHeader(); chkVertexBufferHeader) - { - return QVariant::fromValue(chkVertexBufferHeader->vertexFormat); - } - } - - return {}; -} - -bool ScenePrimitivesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - Q_UNUSED(index); - Q_UNUSED(value); - Q_UNUSED(role); - // This model is read only - return false; -} - -QVariant ScenePrimitivesModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) - { - constexpr std::array g_ColNames = { - "Index", "Kind (chunk)", "Size (chunk)", "Indices", "Vertices", "Objects REF", "Parts REF" - }; - - return QString::fromStdString(g_ColNames.at(section).data()); - } - - return {}; -} - -Qt::ItemFlags ScenePrimitivesModel::flags(const QModelIndex &index) const -{ - return Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled; -} - -void ScenePrimitivesModel::setLevel(gamelib::Level* level) -{ - if (level) - { - beginResetModel(); - m_level = level; - endResetModel(); - } -} - -void ScenePrimitivesModel::resetLevel() -{ - beginResetModel(); - m_level = nullptr; - endResetModel(); -} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneTextureFilterModel.cpp b/BMEdit/Editor/Source/Models/SceneTextureFilterModel.cpp new file mode 100644 index 0000000..470f255 --- /dev/null +++ b/BMEdit/Editor/Source/Models/SceneTextureFilterModel.cpp @@ -0,0 +1,57 @@ +#include +#include + + +namespace models +{ + SceneTextureFilterModel::SceneTextureFilterModel(QObject* parent) : QSortFilterProxyModel(parent) + { + } + + void SceneTextureFilterModel::setTextureNameFilter(const QString& query) + { + beginResetModel(); + { + m_textureNameFilter = query; + m_filterResultsCache.clear(); + } + endResetModel(); + } + + const QString& SceneTextureFilterModel::getTextureNameFilter() const + { + return m_textureNameFilter; + } + + bool SceneTextureFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const // NOLINT(misc-no-recursion) + { + if (m_textureNameFilter.isEmpty()) + return true; // Allow all + + QAbstractItemModel *srcModel = sourceModel(); + + if (sourceParent.isValid() && sourceRow >= srcModel->rowCount(sourceParent)) + { + return false; + } + + QModelIndex index = srcModel->index(sourceRow, SceneTexturesModel::ColumnID::NAME, sourceParent); + if (m_filterResultsCache.contains(index)) + { + return m_filterResultsCache[index]; + } + + // Try build cache + QRegularExpression regex(m_textureNameFilter, QRegularExpression::CaseInsensitiveOption); + QString text = srcModel->data(index, Qt::DisplayRole).toString(); + if (regex.match(text).hasMatch()) + { + m_filterResultsCache[index] = true; + return true; + } + + // Or fill false + m_filterResultsCache[index] = false; + return false; + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneTexturesModel.cpp b/BMEdit/Editor/Source/Models/SceneTexturesModel.cpp new file mode 100644 index 0000000..0886b61 --- /dev/null +++ b/BMEdit/Editor/Source/Models/SceneTexturesModel.cpp @@ -0,0 +1,168 @@ +#include +#include +#include + +using namespace models; + + +SceneTexturesModel::SceneTexturesModel(QObject *parent) : QAbstractTableModel(parent) +{ +} + +int SceneTexturesModel::rowCount(const QModelIndex &parent) const +{ + if (!isReady()) return 0; + return static_cast(m_level->getSceneTextures()->entries.size()); +} + +int SceneTexturesModel::columnCount(const QModelIndex &parent) const +{ + if (!isReady()) return 0; + return ColumnID::MAX_COLUMNS; +} + +QVariant SceneTexturesModel::data(const QModelIndex &index, int role) const +{ + if (!isReady() || index.row() >= m_level->getSceneTextures()->entries.size()) + { + return {}; + } + + if (index.column() == ColumnID::INDEX) + { + if (role == Qt::DisplayRole) + { + // May be customized + return m_level->getSceneTextures()->entries.at(index.row()).m_index; + } + + if (role == Qt::UserRole) + { + // DO NOT CUSTOMIZE THIS + return m_level->getSceneTextures()->entries.at(index.row()).m_index; + } + } + + if (index.column() == ColumnID::OFFSET && role == Qt::DisplayRole) + { + return m_level->getSceneTextures()->entries.at(index.row()).m_offset; + } + + if (index.column() == ColumnID::NAME && role == Qt::DisplayRole) + { + if (auto& name = m_level->getSceneTextures()->entries.at(index.row()).m_fileName; name.has_value()) + { + return QString::fromStdString(name.value()); + } + + return QString(); + } +#ifndef NDEBUG + if (index.column() == ColumnID::CUBEMAP && role == Qt::DisplayRole) + { + return QString("%1").arg(m_level->getSceneTextures()->entries.at(index.row()).m_flags, 8, 16); + } +#endif + + if (index.column() == ColumnID::RESOLUTION && role == Qt::DisplayRole) + { + const auto& entry = m_level->getSceneTextures()->entries.at(index.row()); + return QString("%1x%2").arg(entry.m_width).arg(entry.m_height); + } + + if (index.column() == ColumnID::FORMAT && role == Qt::DisplayRole) + { + auto formatToQString = [](gamelib::tex::TEXEntryType type) -> QString { + switch (type) + { + case gamelib::tex::TEXEntryType::ET_BITMAP_I8: return "I8"; + case gamelib::tex::TEXEntryType::ET_BITMAP_EMBM: return "EMBM"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DOT3: return "DOT3"; + case gamelib::tex::TEXEntryType::ET_BITMAP_CUBE: return "CUBE"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DMAP: return "DMAP"; + case gamelib::tex::TEXEntryType::ET_BITMAP_PAL: return "PAL (Neg)"; + case gamelib::tex::TEXEntryType::ET_BITMAP_PAL_OPAC: return "PAL (Opac)"; + case gamelib::tex::TEXEntryType::ET_BITMAP_32: return "RGBA"; + case gamelib::tex::TEXEntryType::ET_BITMAP_U8V8: return "U8V8"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT1: return "DXT1"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT3: return "DXT3"; + default: + assert(false && "Unknown entry"); + return "Unknown"; + } + }; + + return formatToQString(m_level->getSceneTextures()->entries.at(index.row()).m_type1); + } + + if (role == SceneTexturesModel::Roles::R_TEXTURE_REF) + { + types::QTextureREF ref; + ref.ownerModel = this; + ref.textureIndex = m_level->getSceneTextures()->entries.at(index.row()).m_index; + return QVariant::fromValue(ref); + } + + return {}; +} + +bool SceneTexturesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + //TODO: Fixme later + return false; +} + +QVariant SceneTexturesModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + if (section == ColumnID::NAME) return QString("Name"); + if (section == ColumnID::OFFSET) return QString("Offset"); + if (section == ColumnID::INDEX) return QString("Index"); + if (section == ColumnID::FORMAT) return QString("Format"); + if (section == ColumnID::RESOLUTION) return QString("Resolution"); +#ifndef NDEBUG + if (section == ColumnID::CUBEMAP) return QString("CubeMap"); +#endif + } + + return QAbstractTableModel::headerData(section, orientation, role); +} + +Qt::ItemFlags SceneTexturesModel::flags(const QModelIndex &index) const +{ + return Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled; //TODO: Fixme later +} + +void SceneTexturesModel::setLevel(const gamelib::Level *level) +{ + if (m_level == level) return; + + beginResetModel(); + m_level = level; + endResetModel(); +} + +void SceneTexturesModel::resetLevel() +{ + if (!isReady()) return; + + beginResetModel(); + m_level = nullptr; + endResetModel(); +} + +const gamelib::Level *SceneTexturesModel::getLevel() const +{ + return m_level; +} + +gamelib::Level *SceneTexturesModel::getLevel() +{ + return const_cast(m_level); // Bruh. TODO: Refactor later +} + +bool SceneTexturesModel::isReady() const +{ + return m_level != nullptr; +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 2478566..4d53f67 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -6,11 +6,13 @@ #include #include #include +#include #include #include #include "LoadSceneProgressDialog.h" +#include "ViewTexturesDialog.h" namespace Ui { @@ -22,9 +24,8 @@ namespace models class SceneObjectPropertiesModel; class SceneObjectsTreeModel; class ScenePropertiesModel; - class ScenePrimitivesModel; - class ScenePrimitivesFilterModel; class SceneFilterModel; + class SceneTexturesModel; } namespace delegates @@ -58,9 +59,8 @@ class BMEditMainWindow : public QMainWindow void initProperties(); void initSceneProperties(); void initControllers(); - void initScenePrimitives(); void initSceneLoadingDialog(); - void resetPrimitivesFilter(); + void initViewTexturesDialog(); public slots: void onExit(); @@ -78,8 +78,8 @@ public slots: void onAssetExportFailed(const QString &reason); void onCloseLevel(); void onExportPRP(); + void onShowTexturesDialog(); void onContextMenuRequestedForSceneTreeNode(const QPoint& point); - void onContextMenuRequestedForPrimitivesTableHeader(const QPoint& point); private: // UI @@ -96,8 +96,7 @@ public slots: models::SceneFilterModel* m_sceneTreeFilterModel { nullptr }; models::SceneObjectPropertiesModel *m_sceneObjectPropertiesModel { nullptr }; models::ScenePropertiesModel *m_scenePropertiesModel { nullptr }; - models::ScenePrimitivesModel *m_scenePrimitivesModel { nullptr }; - models::ScenePrimitivesFilterModel *m_scenePrimitivesFilterModel { nullptr }; + QScopedPointer m_sceneTexturesModel { nullptr }; // Delegates delegates::TypePropertyItemDelegate *m_typePropertyItemDelegate { nullptr }; @@ -105,6 +104,7 @@ public slots: // Dialogs LoadSceneProgressDialog m_loadSceneDialog; + ViewTexturesDialog m_viewTexturesDialog; // Selection std::optional m_selectedPrimitiveToPreview; diff --git a/BMEdit/Editor/UI/Include/ImportTextureDialog.h b/BMEdit/Editor/UI/Include/ImportTextureDialog.h new file mode 100644 index 0000000..08879d9 --- /dev/null +++ b/BMEdit/Editor/UI/Include/ImportTextureDialog.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace Ui { +class ImportTextureDialog; +} + +class ImportTextureDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ImportTextureDialog(QWidget *parent = nullptr); + ~ImportTextureDialog(); + + // Setters + void resetState(); + void setSourcePath(const QString& sourcePath); + void setTextureName(const QString& textureName); + void setMIPLevelsCount(uint8_t count); + + // Getters + [[nodiscard]] const QString& getSourceFilePath() const; + [[nodiscard]] const QString& getTextureName() const; + [[nodiscard]] gamelib::tex::TEXEntryType getTargetFormat() const; + [[nodiscard]] uint8_t getTargetMIPLevels() const; + +private: + Ui::ImportTextureDialog *ui; + + // State + QString m_sourcePath {}; + QString m_textureName {}; + gamelib::tex::TEXEntryType m_entryType { gamelib::tex::TEXEntryType::ET_BITMAP_32 }; + uint8_t m_mipLevels { 1u }; +}; diff --git a/BMEdit/Editor/UI/Include/ViewTexturesDialog.h b/BMEdit/Editor/UI/Include/ViewTexturesDialog.h new file mode 100644 index 0000000..c400768 --- /dev/null +++ b/BMEdit/Editor/UI/Include/ViewTexturesDialog.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace Ui { +class ViewTexturesDialog; +} + +namespace models +{ +class SceneTexturesModel; +class SceneTextureFilterModel; +} + +class ImportTextureDialog; + + +class ViewTexturesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ViewTexturesDialog(QWidget *parent = nullptr); + ~ViewTexturesDialog(); + + void setTexturesSource(models::SceneTexturesModel *model); + +protected: + void showEvent(QShowEvent *event) override; + +private slots: + void onCurrentMipLevelChanged(int mipIndex); + void onExportTEXFile(); + void onExportCurrentTextureToFile(); + void onReplaceCurrentTexture(); + void onTextureToImportSpecified(); + +private: + void setPreview(uint32_t textureIndex, const std::optional& mipLevel = std::nullopt); + void setPreview(QPixmap &&image); + void resetPreview(); + void resetAvailableMIPs(uint32_t textureIndex); + void clearAvailableMIPs(); + +private: + [[nodiscard]] std::optional getActiveTexture() const; + [[nodiscard]] std::optional getActiveMIPLevel() const; + +private: + Ui::ViewTexturesDialog *ui; + QScopedPointer m_filterModel; + QScopedPointer m_texturePreviewWidgetContextMenu; + QScopedPointer m_importDialog; +}; diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index ff9ae6e..e19c64e 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -18,9 +18,8 @@ #include #include #include -#include -#include #include +#include #include #include @@ -46,7 +45,8 @@ enum OperationToProgress : int BMEditMainWindow::BMEditMainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::BMEditMainWindow), - m_loadSceneDialog(this) + m_loadSceneDialog(this), + m_viewTexturesDialog(this) { ui->setupUi(this); @@ -54,8 +54,8 @@ BMEditMainWindow::BMEditMainWindow(QWidget *parent) : initProperties(); initControllers(); initSceneProperties(); - initScenePrimitives(); initSceneLoadingDialog(); + initViewTexturesDialog(); initStatusBar(); initSearchInput(); connectActions(); @@ -71,7 +71,6 @@ BMEditMainWindow::~BMEditMainWindow() delete m_sceneTreeFilterModel; delete m_sceneTreeModel; delete m_sceneObjectPropertiesModel; - delete m_scenePrimitivesModel; delete m_operationProgress; delete m_operationLabel; @@ -99,12 +98,13 @@ void BMEditMainWindow::initSearchInput() void BMEditMainWindow::connectActions() { - connect(ui->actionExit, &QAction::triggered, [=]() { onExit(); }); - connect(ui->actionOpen_level, &QAction::triggered, [=]() { onOpenLevel(); }); - connect(ui->actionRestore_layout, &QAction::triggered, [=]() { onRestoreLayout(); }); - connect(ui->actionTypes_Viewer, &QAction::triggered, [=]() { onShowTypesViewer(); }); - connect(ui->actionSave_properties, &QAction::triggered, [=]() { onExportProperties(); }); - connect(ui->actionExport_PRP_properties, &QAction::triggered, [=]() { onExportPRP(); }); + connect(ui->actionExit, &QAction::triggered, this, &BMEditMainWindow::onExit); + connect(ui->actionOpen_level, &QAction::triggered, this, &BMEditMainWindow::onOpenLevel); + connect(ui->actionRestore_layout, &QAction::triggered, this, &BMEditMainWindow::onRestoreLayout); + connect(ui->actionTypes_Viewer, &QAction::triggered, this, &BMEditMainWindow::onShowTypesViewer); + connect(ui->actionSave_properties, &QAction::triggered, this, &BMEditMainWindow::onExportProperties); + connect(ui->actionExport_PRP_properties, &QAction::triggered, this, &BMEditMainWindow::onExportPRP); + connect(ui->actionTextures, &QAction::triggered, this, &BMEditMainWindow::onShowTexturesDialog); } void BMEditMainWindow::connectDockWidgetActions() @@ -219,6 +219,11 @@ void BMEditMainWindow::onLevelLoadSuccess() m_sceneTreeModel->setLevel(currentLevel); } + if (m_sceneTexturesModel) + { + m_sceneTexturesModel->setLevel(currentLevel); + } + if (m_sceneObjectPropertiesModel) { m_sceneObjectPropertiesModel->setLevel(currentLevel); @@ -229,24 +234,13 @@ void BMEditMainWindow::onLevelLoadSuccess() m_scenePropertiesModel->setLevel(const_cast(currentLevel)); } - if (m_scenePrimitivesModel) - { - m_scenePrimitivesModel->setLevel(const_cast(currentLevel)); - ui->primitivesCountLabel->setText(QString("%1").arg(currentLevel->getLevelGeometry()->header.countOfPrimitives)); - } - - // Reset primitive filters - resetPrimitivesFilter(); - - // Setup preview - ui->scenePrimitivePreview->setLevel(const_cast(currentLevel)); - // Load controllers index ui->geomControllers->switchToDefaults(); // Export action ui->menuExport->setEnabled(true); ui->actionExport_PRP_properties->setEnabled(true); + ui->actionTextures->setEnabled(true); //ui->actionSave_properties->setEnabled(true); //TODO: Uncomment when exporter to ZIP will be done ui->searchInputField->setEnabled(true); @@ -347,13 +341,7 @@ void BMEditMainWindow::onCloseLevel() if (m_sceneTreeModel) m_sceneTreeModel->resetLevel(); if (m_sceneObjectPropertiesModel) m_sceneObjectPropertiesModel->resetLevel(); if (m_scenePropertiesModel) m_scenePropertiesModel->resetLevel(); - if (m_scenePrimitivesModel) m_scenePrimitivesModel->resetLevel(); - - // Reset filters - resetPrimitivesFilter(); - - // Reset - ui->scenePrimitivePreview->resetLevel(); + if (m_sceneTexturesModel) m_sceneTexturesModel->resetLevel(); // Reset widget states ui->geomControllers->resetGeom(); @@ -361,9 +349,7 @@ void BMEditMainWindow::onCloseLevel() // Reset export menu ui->menuExport->setEnabled(false); ui->actionExport_PRP_properties->setEnabled(false); - - // Reset primitives counter - ui->primitivesCountLabel->setText("0"); + ui->actionTextures->setEnabled(false); // Disable filtering QSignalBlocker blocker { ui->searchInputField }; @@ -394,6 +380,11 @@ void BMEditMainWindow::onExportPRP() QMessageBox::information(this, "Export PRP", QString("PRP file exported successfully to %1").arg(saveAsPath)); } +void BMEditMainWindow::onShowTexturesDialog() +{ + m_viewTexturesDialog.show(); +} + void BMEditMainWindow::onContextMenuRequestedForSceneTreeNode(const QPoint& point) { if (!m_sceneTreeModel) @@ -443,26 +434,6 @@ void BMEditMainWindow::onContextMenuRequestedForSceneTreeNode(const QPoint& poin } } -void BMEditMainWindow::onContextMenuRequestedForPrimitivesTableHeader(const QPoint &point) -{ - QMenu contextMenu; - -#define BE_CONFIGURE_ACTION(actName, actFmt) \ - { \ - auto action = contextMenu.addAction(actName); \ - action->setCheckable(true); \ - action->setChecked(m_scenePrimitivesFilterModel->isVertexFormatAllowed(actFmt)); \ - connect(action, &QAction::toggled, [this](bool val) { m_scenePrimitivesFilterModel->setVertexFormatAllowed(actFmt, val); }); \ - } - - BE_CONFIGURE_ACTION("Vertex Format 10", gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_10); - BE_CONFIGURE_ACTION("Vertex Format 24", gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_24); - BE_CONFIGURE_ACTION("Vertex Format 28", gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_28); - BE_CONFIGURE_ACTION("Vertex Format 34", gamelib::prm::PRMVertexBufferFormat::VBF_VERTEX_34); - - contextMenu.exec(ui->scenePrimitivesTable->horizontalHeader()->viewport()->mapToGlobal(point)); -} - void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); @@ -629,60 +600,6 @@ void BMEditMainWindow::initControllers() //TODO: Init this } -void BMEditMainWindow::initScenePrimitives() -{ - m_scenePrimitivesModel = new models::ScenePrimitivesModel(this); - m_scenePrimitivesFilterModel = new models::ScenePrimitivesFilterModel(this); - - m_scenePrimitivesFilterModel->setSourceModel(m_scenePrimitivesModel); - ui->scenePrimitivesTable->setModel(m_scenePrimitivesFilterModel); - ui->scenePrimitivesTable->setSelectionBehavior(QAbstractItemView::SelectItems); - ui->scenePrimitivesTable->setSelectionMode(QAbstractItemView::SingleSelection); - ui->scenePrimitivesTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - ui->scenePrimitivesTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - - connect(ui->scenePrimitivesTable->selectionModel(), &QItemSelectionModel::selectionChanged, [=](const QItemSelection &selected, const QItemSelection &deselected) { - if ((selected.indexes().size() == 1 && selected.indexes().at(0).row() != 0) || (!selected.indexes().empty())) - { - m_selectedPrimitiveToPreview.emplace(static_cast(selected.indexes().first().data(types::kChunkIndexRole).value())); - ui->scenePrimitivePreview->setPrimitiveIndex(*m_selectedPrimitiveToPreview); - } - else if (!deselected.indexes().isEmpty()) - { - m_selectedPrimitiveToPreview = std::nullopt; - } - - ui->exportChunk->setEnabled(m_selectedPrimitiveToPreview.has_value()); - }); - - connect(ui->exportChunk, &QPushButton::clicked, [=]() { - if (m_selectedPrimitiveToPreview.has_value()) - { - //TODO: Impl me - } - }); - - connect(ui->scenePrimitivesTable->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, &BMEditMainWindow::onContextMenuRequestedForPrimitivesTableHeader); - - auto sendChangesToFilterModel = [=](models::ScenePrimitivesFilterEntry entry, int newState) - { - if (newState) - { - m_scenePrimitivesFilterModel->addFilterEntry(entry); - } - else - { - m_scenePrimitivesFilterModel->removeFilterEntry(entry); - } - }; - - connect(ui->primitivesFilter_UnknownPrimType, &QCheckBox::stateChanged, [=](int newState) { sendChangesToFilterModel(models::ScenePrimitivesFilterEntry::FilterAllow_Unknown, newState); }); - connect(ui->primitivesFilter_ZeroBufferPrimType, &QCheckBox::stateChanged, [=](int newState) { sendChangesToFilterModel(models::ScenePrimitivesFilterEntry::FilterAllow_Zero, newState); }); - connect(ui->primitivesFilter_DescriptionPrimType, &QCheckBox::stateChanged, [=](int newState) { sendChangesToFilterModel(models::ScenePrimitivesFilterEntry::FilterAllow_Description, newState); }); - connect(ui->primitivesFilter_IndexBufferPrimType, &QCheckBox::stateChanged, [=](int newState) { sendChangesToFilterModel(models::ScenePrimitivesFilterEntry::FilterAllow_Index, newState); }); - connect(ui->primitivesFilter_VertexBufferPrimType, &QCheckBox::stateChanged, [=](int newState) { sendChangesToFilterModel(models::ScenePrimitivesFilterEntry::FilterAllow_Vertex, newState); }); -} - void BMEditMainWindow::initSceneLoadingDialog() { m_loadSceneDialog.setFixedSize(m_loadSceneDialog.size()); @@ -690,24 +607,9 @@ void BMEditMainWindow::initSceneLoadingDialog() m_loadSceneDialog.setModal(true); } -void BMEditMainWindow::resetPrimitivesFilter() +void BMEditMainWindow::initViewTexturesDialog() { - if (m_scenePrimitivesFilterModel) - { - m_scenePrimitivesFilterModel->resetToDefaults(); - } - -#define BE_RESET_CHECK_BOX(x) \ - { \ - QSignalBlocker blk{x}; \ - x->setChecked(true); \ - } - - BE_RESET_CHECK_BOX(ui->primitivesFilter_UnknownPrimType); - BE_RESET_CHECK_BOX(ui->primitivesFilter_ZeroBufferPrimType); - BE_RESET_CHECK_BOX(ui->primitivesFilter_DescriptionPrimType); - BE_RESET_CHECK_BOX(ui->primitivesFilter_IndexBufferPrimType); - BE_RESET_CHECK_BOX(ui->primitivesFilter_VertexBufferPrimType); - -#undef BE_RESET_CHECK_BOX + m_sceneTexturesModel.reset(new models::SceneTexturesModel(this)); + m_viewTexturesDialog.setModal(true); + m_viewTexturesDialog.setTexturesSource(m_sceneTexturesModel.get()); } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/ImportTextureDialog.cpp b/BMEdit/Editor/UI/Source/ImportTextureDialog.cpp new file mode 100644 index 0000000..82c4645 --- /dev/null +++ b/BMEdit/Editor/UI/Source/ImportTextureDialog.cpp @@ -0,0 +1,161 @@ +#include "ImportTextureDialog.h" +#include "ui_ImportTextureDialog.h" +#include +#include +#include +#include + + +ImportTextureDialog::ImportTextureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ImportTextureDialog) +{ + ui->setupUi(this); + + resetState(); + + connect(ui->selectSourceTextureButton, &QPushButton::clicked, [this]() { + QFileDialog openTextureDialog(this, QString("Select Texture...")); + openTextureDialog.setViewMode(QFileDialog::ViewMode::Detail); + openTextureDialog.setFileMode(QFileDialog::FileMode::AnyFile); + openTextureDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptOpen); + openTextureDialog.setNameFilters({ "PNG Texture (*.png)", "JPG Texture (*.jpg)" }); + + if (!openTextureDialog.exec()) + { + return; + } + + if (openTextureDialog.selectedFiles().empty()) + { + return; + } + + QString textureToImport = openTextureDialog.selectedFiles().first(); + + // Try update preview + QImage image { textureToImport }; + if (image.isNull()) + { + // Bruh + QMessageBox::warning(this, "Import texture", QString("Failed to import texture '%1'. Unsupported format!").arg(textureToImport)); + return; + } + + ui->previewBox->setTitle(QString("Preview (%1;%2):").arg(image.width()).arg(image.height())); + ui->previewWidget->setPixmap(QPixmap::fromImage(std::move(image))); + + // And set up + setSourcePath(openTextureDialog.selectedFiles().first()); + }); + + connect(ui->importButton, &QPushButton::clicked, [this]() { + if (!m_sourcePath.isEmpty()) + { + accept(); + } + else + { + QMessageBox::warning(this, "Import texture", "You must specify source texture path before import!"); + } + }); + + connect(ui->destinationFormatCombo, &QComboBox::currentIndexChanged, [this](int index) { + m_entryType = static_cast(ui->destinationFormatCombo->itemData(index, Qt::UserRole).value()); + }); + + connect(ui->mipLevels, &QSpinBox::valueChanged, [this](int value) { + m_mipLevels = static_cast(value); + }); + + connect(ui->textureName, &QLineEdit::textChanged, [this](const QString& newName) { + m_textureName = newName; + }); +} + +ImportTextureDialog::~ImportTextureDialog() +{ + delete ui; +} + +void ImportTextureDialog::resetState() +{ + // Clear data + m_sourcePath = {}; + m_textureName = {}; + m_entryType = gamelib::tex::TEXEntryType::ET_BITMAP_32; + m_mipLevels = 1u; + + // Clear path + { + QSignalBlocker blocker { ui->sourcePathEdit }; + ui->sourcePathEdit->clear(); + } + + // Clear name + { + QSignalBlocker blocker { ui->textureName }; + ui->textureName->clear(); + } + + // Fill known and supported formats + { + QSignalBlocker blocker { ui->destinationFormatCombo }; + ui->destinationFormatCombo->clear(); + ui->destinationFormatCombo->addItem("RGBA", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_32)); + ui->destinationFormatCombo->addItem("I8", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_I8)); + ui->destinationFormatCombo->addItem("PALN", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_PAL)); + ui->destinationFormatCombo->addItem("U8V8", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_U8V8)); + ui->destinationFormatCombo->addItem("DXT1", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_DXT1)); + ui->destinationFormatCombo->addItem("DXT3", static_cast(gamelib::tex::TEXEntryType::ET_BITMAP_DXT3)); + } + + // Reset mip level + { + QSignalBlocker blocker { ui->mipLevels }; + ui->mipLevels->setValue(1); + } + + // Reset preview + ui->previewWidget->setPixmap(QPixmap()); + ui->previewBox->setTitle("Preview:"); +} + +void ImportTextureDialog::setSourcePath(const QString& sourcePath) +{ + m_sourcePath = sourcePath; + ui->sourcePathEdit->setText(m_sourcePath); +} + +void ImportTextureDialog::setTextureName(const QString& textureName) +{ + m_textureName = textureName; + ui->textureName->setText(textureName); +} + +void ImportTextureDialog::setMIPLevelsCount(uint8_t count) +{ + if (count > 0) + { + m_mipLevels = count; + ui->mipLevels->setValue(count); + } +} + +const QString& ImportTextureDialog::getSourceFilePath() const +{ + return m_sourcePath; +} + +const QString &ImportTextureDialog::getTextureName() const +{ + return m_textureName; +} + +gamelib::tex::TEXEntryType ImportTextureDialog::getTargetFormat() const +{ + return m_entryType; +} + +uint8_t ImportTextureDialog::getTargetMIPLevels() const +{ + return m_mipLevels; +} diff --git a/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp b/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp new file mode 100644 index 0000000..f0670cc --- /dev/null +++ b/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp @@ -0,0 +1,444 @@ +#include "ViewTexturesDialog.h" +#include +#include "ui_ViewTexturesDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static constexpr int kDefaultMIP = 0; +static constexpr const char* kPNGFilter = "PNG image (*.png)"; + + +static QString convertTextureTypeToQString(gamelib::tex::TEXEntryType entry) +{ + switch (entry) + { + case gamelib::tex::TEXEntryType::ET_BITMAP_I8: return "I8"; + case gamelib::tex::TEXEntryType::ET_BITMAP_EMBM: return "EMBM"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DOT3: return "DOT3"; + case gamelib::tex::TEXEntryType::ET_BITMAP_CUBE: return "CUBE"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DMAP: return "DMAP"; + case gamelib::tex::TEXEntryType::ET_BITMAP_PAL: return "PAL (Neg)"; + case gamelib::tex::TEXEntryType::ET_BITMAP_PAL_OPAC: return "PAL (Opac)"; + case gamelib::tex::TEXEntryType::ET_BITMAP_32: return "RGBA"; + case gamelib::tex::TEXEntryType::ET_BITMAP_U8V8: return "U8V8"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT1: return "DXT1"; + case gamelib::tex::TEXEntryType::ET_BITMAP_DXT3: return "DXT3"; + } + + return "Unknown"; +} + +ViewTexturesDialog::ViewTexturesDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ViewTexturesDialog) +{ + ui->setupUi(this); + ui->texturesTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); + ui->texturesTableView->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + ui->texturesTableView->verticalHeader()->hide(); + + m_importDialog.reset(new ImportTextureDialog(this)); + m_filterModel.reset(new models::SceneTextureFilterModel(this)); + ui->texturesTableView->setModel(m_filterModel.get()); + + // Connect common signals + connect(ui->mipLevelCombo, &QComboBox::currentIndexChanged, this, &ViewTexturesDialog::onCurrentMipLevelChanged); + connect(ui->exportTEXButton, &QPushButton::clicked, this, &ViewTexturesDialog::onExportTEXFile); + connect(ui->exportTextureButton, &QPushButton::clicked, this, &ViewTexturesDialog::onExportCurrentTextureToFile); + connect(ui->replaceTextureButton, &QPushButton::clicked, this, &ViewTexturesDialog::onReplaceCurrentTexture); + connect(ui->searchTextureByNameInput, &QLineEdit::textChanged, [this](const QString& query) { + m_filterModel->setTextureNameFilter(query); + }); + connect(m_importDialog.get(), &QDialog::accepted, this, &ViewTexturesDialog::onTextureToImportSpecified); +} + +ViewTexturesDialog::~ViewTexturesDialog() +{ + delete ui; +} + +void ViewTexturesDialog::setTexturesSource(models::SceneTexturesModel* model) +{ + { + QSignalBlocker blocker { ui->searchTextureByNameInput }; + ui->searchTextureByNameInput->clear(); + m_filterModel->setTextureNameFilter({}); + } + + m_filterModel->setSourceModel(model); + ui->mipLevelCombo->clear(); + ui->texturesTableView->resizeColumnsToContents(); + + connect(ui->texturesTableView->selectionModel(), &QItemSelectionModel::selectionChanged, [this](const QItemSelection &selected, const QItemSelection &deselected) { + const bool somethingSelected = !selected.indexes().isEmpty(); + + if (somethingSelected) + { + const QModelIndex mappedSelection = m_filterModel->mapToSource(selected.indexes().first()); + const auto textureRef = mappedSelection.data(models::SceneTexturesModel::Roles::R_TEXTURE_REF).value(); + + if (!textureRef.textureIndex) + return; + + const auto textureIndex = textureRef.textureIndex; + + // Show available MIP levels + resetAvailableMIPs(textureIndex); + setPreview(textureIndex, std::nullopt); + } + else if (!deselected.indexes().isEmpty()) + { + ui->exportTextureButton->setEnabled(false); + ui->replaceTextureButton->setEnabled(false); + clearAvailableMIPs(); + resetPreview(); + } + }); +} + +void ViewTexturesDialog::showEvent(QShowEvent *event) +{ + ui->mipLevelCombo->clear(); + ui->texturesTableView->selectionModel()->reset(); + ui->searchTextureByNameInput->setText(QString()); + resetPreview(); + + QDialog::showEvent(event); +} + +void ViewTexturesDialog::onCurrentMipLevelChanged(int mipIndex) +{ + auto textureRefOptional = getActiveTexture(); + if (!textureRefOptional.has_value()) + return; + + auto currentMIPOptional = getActiveMIPLevel(); + if (!currentMIPOptional.has_value()) + return; + + const auto& textureREF = textureRefOptional.value(); + const auto textureIndex = textureREF.textureIndex; + + setPreview(textureIndex, currentMIPOptional.value()); +} + +void ViewTexturesDialog::onExportTEXFile() +{ + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return; + + QFileDialog saveTEXDialog(this, QString("Save TEX"), QString(), QString("Glacier Texture pack (*.TEX)")); + saveTEXDialog.setViewMode(QFileDialog::ViewMode::Detail); + saveTEXDialog.setFileMode(QFileDialog::FileMode::AnyFile); + saveTEXDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); + saveTEXDialog.selectFile(QString("%1.TEX").arg(QString::fromStdString(model->getLevel()->getLevelName()))); + if (!saveTEXDialog.exec()) + { + return; + } + + if (saveTEXDialog.selectedFiles().empty()) + { + return; + } + + const auto saveAsPath = saveTEXDialog.selectedFiles().first(); + const auto sceneTextures = model->getLevel()->getSceneTextures(); + + // Build TEX into buffer + std::vector texBuffer; + gamelib::tex::TEXWriter::write(sceneTextures->header, sceneTextures->entries, texBuffer); + + QFile texFile(saveAsPath); + if (!texFile.open(QIODeviceBase::OpenModeFlag::WriteOnly | QIODeviceBase::OpenModeFlag::Truncate | QIODeviceBase::OpenModeFlag::Unbuffered)) + { + QMessageBox::warning(this, "Export TEX", QString("Failed to export TEX file. Unable to open file '%1' to write").arg(saveAsPath)); + return; + } + + texFile.write(QByteArray::fromRawData(reinterpret_cast(texBuffer.data()), static_cast(texBuffer.size()))); + QMessageBox::information(this, "Export TEX", QString("TEX file '%1' exported successfully!").arg(saveAsPath)); +} + +void ViewTexturesDialog::onExportCurrentTextureToFile() +{ + auto textureRefOptional = getActiveTexture(); + if (!textureRefOptional.has_value()) + return; + + const auto& textureREF = textureRefOptional.value(); + + const auto textureIndex = textureREF.textureIndex; + const auto& entries = textureREF.ownerModel->getLevel()->getSceneTextures()->entries; + auto foundIt = std::find_if(entries.begin(), entries.end(), [idx = textureIndex](const gamelib::tex::TEXEntry& entry) -> bool { + return entry.m_index == idx; + }); + + if (foundIt == entries.end()) + return; + + QString relatedFileName; + const gamelib::tex::TEXEntry& textureToSave = *foundIt; + + // Predict name + if (textureToSave.m_fileName.has_value()) + { + relatedFileName = QString::fromStdString(textureToSave.m_fileName.value()); + } + else + { + relatedFileName = QString("Texture_%1").arg(textureToSave.m_index); + } + + // Show dialog + QFileDialog saveTEXDialog(this, QString("Export texture")); + saveTEXDialog.setNameFilters({ kPNGFilter }); + saveTEXDialog.setViewMode(QFileDialog::ViewMode::Detail); + saveTEXDialog.setFileMode(QFileDialog::FileMode::AnyFile); + saveTEXDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); + saveTEXDialog.selectFile(relatedFileName); + if (!saveTEXDialog.exec()) + { + return; + } + + if (saveTEXDialog.selectedFiles().empty()) + { + return; + } + + const auto savePath = saveTEXDialog.selectedFiles().first(); + const auto saveExt = saveTEXDialog.selectedNameFilter(); + + bool exportResult = false; + if (saveExt == kPNGFilter) + { + exportResult = editor::TextureProcessor::exportTEXEntryAsPNG(textureToSave, savePath.toStdString()); + } + //TODO: Add support for other formats + + if (exportResult) + { + QMessageBox::information(this, "Export texture", QString("Texture exported to file %1 successfully!").arg(savePath)); + } + else + { + QMessageBox::warning(this, "Export texture", "Failed to export texture. Unsupported format or internal error"); + } +} + +void ViewTexturesDialog::onReplaceCurrentTexture() +{ + auto textureRefOptional = getActiveTexture(); + if (!textureRefOptional.has_value()) + return; + + const auto& textureREF = textureRefOptional.value(); + + const auto textureIndex = textureREF.textureIndex; + auto& entries = textureREF.ownerModel->getLevel()->getSceneTextures()->entries; + auto foundIt = std::find_if(entries.begin(), entries.end(), [idx = textureIndex](const gamelib::tex::TEXEntry& entry) -> bool { + return entry.m_index == idx; + }); + + // Cleanup + m_importDialog->resetState(); + + // Update state + if (foundIt->m_fileName.has_value()) + { + m_importDialog->setTextureName(QString::fromStdString(foundIt->m_fileName.value())); + } + m_importDialog->setMIPLevelsCount(foundIt->m_mipLevels.size()); + + // And open dialog again + m_importDialog->setModal(true); + m_importDialog->open(); +} + +void ViewTexturesDialog::onTextureToImportSpecified() +{ + if (ui->texturesTableView->selectionModel()->selectedIndexes().isEmpty()) + return; + + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return; + + // Extract data & select texture to replace + const auto& sourceFile = m_importDialog->getSourceFilePath(); + const auto& textureName = m_importDialog->getTextureName(); + const auto targetFormat = m_importDialog->getTargetFormat(); + const auto mipLevelsNr = m_importDialog->getTargetMIPLevels(); + + // Check that format supported + if (targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_PAL_OPAC || // Maybe will support in future + targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_EMBM || // DirectX 9 stuff, no support from us + targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_DOT3 || // DirectX 9 stuff, no support from us + targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_CUBE || // DirectX 9 stuff, no support from us + targetFormat == gamelib::tex::TEXEntryType::ET_BITMAP_DMAP) // DirectX 9 stuff, no support from us + { + QMessageBox::warning(this, "Import texture", "Required target format not supported yet"); + return; + } + + auto textureRefOptional = getActiveTexture(); + if (!textureRefOptional.has_value()) + return; + + const auto& textureREF = textureRefOptional.value(); + + const auto textureIndex = textureREF.textureIndex; + auto& entries = const_cast(textureREF.ownerModel->getLevel())->getSceneTextures()->entries; + auto foundIt = std::find_if(entries.begin(), entries.end(), [idx = textureIndex](const gamelib::tex::TEXEntry& entry) -> bool { + return entry.m_index == idx; + }); + + if (!editor::TextureProcessor::importTextureToEntry(*foundIt, sourceFile, textureName, targetFormat, mipLevelsNr)) + { + QMessageBox::warning(this, "Import texture", "Failed to import texture! Invalid or unsupported format"); + return; + } + + // And it's done! Update our preview! + resetPreview(); + setPreview(textureIndex, std::nullopt); + resetAvailableMIPs(textureIndex); +} + +void ViewTexturesDialog::setPreview(uint32_t textureIndex, const std::optional& mipLevel) +{ + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return; + + auto& entries = const_cast(model->getLevel())->getSceneTextures()->entries; + auto foundIt = std::find_if(entries.begin(), entries.end(), [textureIndex](const gamelib::tex::TEXEntry& entry) -> bool { + return entry.m_index == textureIndex; + }); + + const auto& textureEntry = *foundIt; + + // Select MIP level + int requiredMipLevel = mipLevel.has_value() ? mipLevel.value() : kDefaultMIP; + + if (requiredMipLevel < 0 || requiredMipLevel >= textureEntry.m_mipLevels.size()) + { + if (textureEntry.m_mipLevels.empty()) + { + qDebug() << "No mip levels in texture #" << textureIndex; + return; + } + + qDebug() << "Bad mip level. Use #0"; + requiredMipLevel = 0; + } + + uint16_t width = 0; + uint16_t height = 0; + std::unique_ptr decompressedMemBlk = editor::TextureProcessor::decompressRGBA(textureEntry, width, height, requiredMipLevel); + + // Save to QPixmap + if (decompressedMemBlk) + { + QImage image(decompressedMemBlk.get(), width, height, QImage::Format::Format_RGBA8888); + QPixmap pixmap = QPixmap::fromImage(std::move(image)); + setPreview(std::move(pixmap)); + + ui->formatLabel->setText(convertTextureTypeToQString(textureEntry.m_type1)); + + ui->exportTextureButton->setEnabled(true); + ui->replaceTextureButton->setEnabled(true); + } + else + { + qDebug() << "Unsupported format"; + resetPreview(); + } +} + +void ViewTexturesDialog::setPreview(QPixmap &&image) +{ + resetPreview(); + ui->textureViewWidget->setPixmap(image); +} + +void ViewTexturesDialog::resetPreview() +{ + ui->textureViewWidget->setPixmap(QPixmap()); + ui->formatLabel->setText("Unknown"); + ui->exportTextureButton->setEnabled(false); + ui->replaceTextureButton->setEnabled(false); +} + +void ViewTexturesDialog::resetAvailableMIPs(uint32_t textureIndex) +{ + QSignalBlocker blocker { ui->mipLevelCombo }; + ui->mipLevelCombo->clear(); + + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return; + + auto& entries = const_cast(model->getLevel())->getSceneTextures()->entries; + auto foundIt = std::find_if(entries.begin(), entries.end(), [idx = textureIndex](const gamelib::tex::TEXEntry& entry) -> bool { + return entry.m_index == idx; + }); + + const auto& textureEntry = *foundIt; + + for (uint32_t mip = 0; mip < textureEntry.m_numOfMipMaps; ++mip) + { + ui->mipLevelCombo->addItem(QString("MIP #%1").arg(mip), mip); + } + + ui->mipLevelCombo->setEnabled(textureEntry.m_numOfMipMaps > 1); +} + +void ViewTexturesDialog::clearAvailableMIPs() +{ + QSignalBlocker blocker { ui->mipLevelCombo }; + ui->mipLevelCombo->clear(); +} + +std::optional ViewTexturesDialog::getActiveTexture() const +{ + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return std::nullopt; + + if (ui->texturesTableView->selectionModel()->selectedIndexes().empty()) + return std::nullopt; + + QModelIndex mappedSelection = m_filterModel->mapToSource(ui->texturesTableView->selectionModel()->selectedIndexes().first()); + + auto textureREF = mappedSelection.data(models::SceneTexturesModel::Roles::R_TEXTURE_REF).value(); + if (!textureREF.textureIndex) + return std::nullopt; + + return textureREF; +} + +std::optional ViewTexturesDialog::getActiveMIPLevel() const +{ + auto model = dynamic_cast(m_filterModel->sourceModel()); + if (!model) + return std::nullopt; + + if (ui->texturesTableView->selectionModel()->selectedIndexes().empty()) + return std::nullopt; + + if (!ui->mipLevelCombo->isEnabled()) + return kDefaultMIP; + + return static_cast(ui->mipLevelCombo->itemData(ui->mipLevelCombo->currentIndex(), Qt::UserRole).value()); +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index a925327..599bab0 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -30,7 +30,7 @@ 0 0 1548 - 21 + 22 @@ -52,6 +52,8 @@ Edit + + @@ -81,7 +83,7 @@ 120 - 35 + 40 @@ -102,7 +104,7 @@ 260 - 189 + 191 @@ -288,147 +290,6 @@ - - - QDockWidget::DockWidgetFeatureMask - - - Primitives - - - 8 - - - - - - - Qt::Horizontal - - - - - - - - - - - Primitives count: - - - - - - - 0 - - - - - - - - - false - - - Export chunk - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - Filter by: - - - - - - - Unknown - - - - - - - Zero buffer - - - - - - - Description Buffer - - - - - - - Index Buffer - - - - - - - Vertex Buffer - - - - - - - - - - - - - - - Primitive Preview: - - - - - - - 100 - 100 - - - - - - - - - - - Open level @@ -494,6 +355,22 @@ Export PRP (properties) + + + false + + + Textures + + + + + false + + + Localization + + @@ -507,11 +384,6 @@ QTreeView
Widgets/SceneTreeView.h
- - widgets::PrimitivePreviewWidget - QOpenGLWidget -
Widgets/PrimitivePreviewWidget.h
-
diff --git a/BMEdit/Editor/UI/UI/ImportTextureDialog.ui b/BMEdit/Editor/UI/UI/ImportTextureDialog.ui new file mode 100644 index 0000000..9e334fb --- /dev/null +++ b/BMEdit/Editor/UI/UI/ImportTextureDialog.ui @@ -0,0 +1,132 @@ + + + ImportTextureDialog + + + + 0 + 0 + 874 + 473 + + + + Import Texture + + + + + + Qt::Horizontal + + + + + + + + + Source file: + + + + + + + + + false + + + + + + + ... + + + + + + + + + Name: + + + + + + + 512 + + + + + + + Destination format: + + + + + + + + + + MIP Levels: + + + + + + + 1 + + + 8 + + + + + + + + + Import + + + + + + + + Preview: + + + + + + + 256 + 256 + + + + + + + Qt::AlignCenter + + + + + + + + + + + + diff --git a/BMEdit/Editor/UI/UI/ViewTexturesDialog.ui b/BMEdit/Editor/UI/UI/ViewTexturesDialog.ui new file mode 100644 index 0000000..c17de3d --- /dev/null +++ b/BMEdit/Editor/UI/UI/ViewTexturesDialog.ui @@ -0,0 +1,196 @@ + + + ViewTexturesDialog + + + + 0 + 0 + 1188 + 791 + + + + + 900 + 400 + + + + Texture Viewer + + + + + + + + + Qt::Horizontal + + + + + + + + + Search: + + + + + + + Texture name... + + + + + + + + + + 0 + 0 + + + + + + + + + + New texture... + + + + + + + Export TEX + + + + + + + + + + Texture: + + + + + + + + + + LOD: + + + + + + + + + + + + + + Format: + + + + + + + Unkown + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + true + + + 0 + + + + Texture + + + + + + + 512 + 512 + + + + true + + + + + + + + + Qt::AlignCenter + + + + + + + + + + + + + false + + + REPLACE + + + + + + + false + + + EXPORT + + + + + + + + + + + + + + diff --git a/BMEdit/GameLib/CMakeLists.txt b/BMEdit/GameLib/CMakeLists.txt index 37d8307..3516a71 100644 --- a/BMEdit/GameLib/CMakeLists.txt +++ b/BMEdit/GameLib/CMakeLists.txt @@ -19,7 +19,8 @@ target_include_directories(GameLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Include) # --- Dependencies target_link_libraries(GameLib PRIVATE ZBinaryReader) # Private libs target_link_libraries(GameLib PUBLIC nlohmann_json::nlohmann_json fmt::fmt-header-only) # Public library to work with json -target_link_libraries(GameLib PUBLIC zlib) # Public library to work with compressed streams +target_link_libraries(GameLib PUBLIC ${ZLIB_LIBRARIES}) # Public library to work with compressed streams +target_include_directories(GameLib PRIVATE ${ZLIB_INCLUDE_DIRS}) # --- Tests (temporary disabled) #add_subdirectory(ThirdParty/gtest) diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index eb8460b..4745ad0 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,15 @@ namespace gamelib std::vector chunks; }; + struct LevelTextures + { + tex::TEXHeader header; + std::vector entries; + tex::OffsetsPool table1Offsets { 0u }; + tex::OffsetsPool table2Offsets { 0u }; + uint32_t countOfEmptyOffsets { 0u }; + }; + struct SceneProperties { gms::GMSHeader header; @@ -46,6 +56,8 @@ namespace gamelib [[nodiscard]] const SceneProperties *getSceneProperties() const; [[nodiscard]] const LevelGeometry* getLevelGeometry() const; [[nodiscard]] LevelGeometry* getLevelGeometry(); + [[nodiscard]] const LevelTextures* getSceneTextures() const; + [[nodiscard]] LevelTextures* getSceneTextures(); [[nodiscard]] const std::vector &getSceneObjects() const; @@ -55,6 +67,7 @@ namespace gamelib bool loadLevelProperties(); bool loadLevelScene(); bool loadLevelPrimitives(); + bool loadLevelTextures(); private: // Core @@ -65,6 +78,7 @@ namespace gamelib LevelProperties m_levelProperties; SceneProperties m_sceneProperties; LevelGeometry m_levelGeometry; + LevelTextures m_levelTextures; // Managed objects std::vector m_sceneObjects {}; diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEX.h b/BMEdit/GameLib/Include/GameLib/TEX/TEX.h new file mode 100644 index 0000000..82205a9 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEX.h @@ -0,0 +1,8 @@ +#pragma once + + +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXEntry.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXEntry.h new file mode 100644 index 0000000..d57370e --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXEntry.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + + +namespace ZBio::ZBinaryWriter +{ + class BinaryWriter; +} + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::tex +{ + struct PALPalette + { + uint32_t m_size { 0 }; + std::unique_ptr m_data { nullptr }; + + public: + static void deserialize(PALPalette &palette, ZBio::ZBinaryReader::BinaryReader *binaryReader); + static void serialize(const PALPalette &palette, ZBio::ZBinaryWriter::BinaryWriter *writerStream); + }; + + struct TEXMipLevel + { + uint32_t m_mipLevelSize { 0u }; + std::unique_ptr m_buffer { nullptr }; + + public: + static void deserialize(TEXMipLevel &mipLevel, ZBio::ZBinaryReader::BinaryReader *binaryReader); + static void serialize(const TEXMipLevel &mipLevel, ZBio::ZBinaryWriter::BinaryWriter *writerStream); + }; + + struct TEXCubeMaps + { + uint32_t m_count { 0u }; + std::vector m_textureIndices {}; + + public: + static void deserialize(TEXCubeMaps &cubeMaps, ZBio::ZBinaryReader::BinaryReader *binaryReader); + static void serialize(const TEXCubeMaps &cubeMaps, ZBio::ZBinaryWriter::BinaryWriter *writerStream); + }; + + using TEXEntryFlag = uint32_t; + + enum TEXEntryFlags : TEXEntryFlag + { + TEX_EF_IsGUI = 0x2 + , TEX_EF_Unk40 = 0x40 + , TEX_EF_IsCubeMap = 0x400 + , TEX_EF_Unk1000 = 0x1000 + }; + + struct TEXEntry + { + TEXEntry() = default; + + static void deserialize(TEXEntry &header, ZBio::ZBinaryReader::BinaryReader *binaryReader); + static void serialize(const TEXEntry &header, ZBio::ZBinaryWriter::BinaryWriter *writerStream, uint32_t& entryOffset, uint32_t& cubeMapsDescOffset); + + [[nodiscard]] uint32_t calculateSize() const; + + public: + uint32_t m_offset { 0u }; + uint32_t m_fileSize { 0u }; + TEXEntryType m_type1 {}; + TEXEntryType m_type2 {}; + uint32_t m_index { 0u }; + uint16_t m_width { 0u }; + uint16_t m_height { 0u }; + uint32_t m_numOfMipMaps { 0u }; + TEXEntryFlag m_flags { 0u }; + uint32_t m_unk2 { 0u }; + uint32_t m_unk3 { 0u }; + std::optional m_fileName {}; + std::vector m_mipLevels {}; + std::optional m_palPalette; + TEXCubeMaps m_cubeMaps; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXEntryType.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXEntryType.h new file mode 100644 index 0000000..dc37efb --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXEntryType.h @@ -0,0 +1,28 @@ +#pragma once + +#include + + +namespace gamelib::tex +{ + /** + * @enum TEXEntryType + * @brief Describes all supported by Glacier texture formats + * @note BMEdit supports I8, U8V8, DXT1, DXT3, RGBA (32) and PAL formats. PAL_OPAC not used by Glacier but supported in missions (not in Loader_Sequence!). + * EMBM, DOT3, CUBE and DMAP referred, supported (in theory) but we can operate it only via DirectX9 SDK, so not our case. + */ + enum class TEXEntryType : uint32_t + { + ET_BITMAP_I8 = 0x49382020u, //I8 + ET_BITMAP_EMBM = 0x454D424Du, //EMBM + ET_BITMAP_DOT3 = 0x444F5433u, //DOT3 + ET_BITMAP_CUBE = 0x43554245u, //CUBE + ET_BITMAP_DMAP = 0x444D4150u, //DMAP + ET_BITMAP_PAL = 0x50414C4Eu, //PALN + ET_BITMAP_PAL_OPAC = 0x50414C4Fu, //PALO + ET_BITMAP_32 = 0x52474241u, //RGBA + ET_BITMAP_U8V8 = 0x55385638u, //V8U8 + ET_BITMAP_DXT1 = 0x44585431u, //DXT1 + ET_BITMAP_DXT3 = 0x44585433u, //DXT3 + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h new file mode 100644 index 0000000..91e6526 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h @@ -0,0 +1,34 @@ +#pragma once + +#include + + +namespace ZBio::ZBinaryWriter +{ + class BinaryWriter; +} + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::tex +{ + struct TEXHeader + { + TEXHeader() = default; + TEXHeader(uint32_t table1Offset, uint32_t table2Offset, uint32_t unk1, uint32_t unk2); + + [[nodiscard]] explicit operator bool() const noexcept; + + static void deserialize(TEXHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader); + static void serialize(const TEXHeader &header, ZBio::ZBinaryWriter::BinaryWriter *writerStream); + + public: + uint32_t m_texturesPoolOffset { 0u }; + uint32_t m_cubeMapsPoolOffset { 0u }; + uint32_t m_unk1 { 0u }; + uint32_t m_unk2 { 0u }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXReader.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXReader.h new file mode 100644 index 0000000..ef47985 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXReader.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::tex +{ + class TEXReader + { + public: + TEXReader() = default; + + bool parse(const uint8_t *texFileBuffer, int64_t texFileSize); + + public: + TEXHeader m_header; + std::vector m_entries; + OffsetsPool m_texturesPool { 0u }; + OffsetsPool m_cubeMapsPool { 0u }; + uint32_t m_countOfEmptyOffsets { 0u }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXTypes.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXTypes.h new file mode 100644 index 0000000..43f60eb --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXTypes.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + + +namespace gamelib::tex +{ + using OffsetsPool = std::array; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXWriter.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXWriter.h new file mode 100644 index 0000000..f7412bf --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXWriter.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace gamelib::tex +{ + struct TEXWriter + { + static void write(const TEXHeader &header, const std::vector &entries, std::vector &outBuffer); + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 1199940..9ac74dd 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -40,6 +40,11 @@ namespace gamelib return false; } + if (!loadLevelTextures()) + { + return false; + } + // TODO: Load things (it's time to combine GMS, PRP & BUF files) m_isLevelLoaded = true; return true; @@ -86,6 +91,16 @@ namespace gamelib return &m_levelGeometry; } + const LevelTextures* Level::getSceneTextures() const + { + return &m_levelTextures; + } + + LevelTextures* Level::getSceneTextures() + { + return &m_levelTextures; + } + const std::vector &Level::getSceneObjects() const { return m_sceneObjects; @@ -230,4 +245,31 @@ namespace gamelib return true; } + + bool Level::loadLevelTextures() + { + // Read TEX file + int64_t texFileSize = 0; + auto texFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::TEXTURES, texFileSize); + + if (!texFileSize || !texFileBuffer) + { + return false; + } + + tex::TEXReader reader; + const bool parseResult = reader.parse(texFileBuffer.get(), texFileSize); + if (!parseResult) + { + return false; + } + + m_levelTextures.header = reader.m_header; + m_levelTextures.entries = std::move(reader.m_entries); + m_levelTextures.table1Offsets = reader.m_texturesPool; + m_levelTextures.table2Offsets = reader.m_cubeMapsPool; + m_levelTextures.countOfEmptyOffsets = reader.m_countOfEmptyOffsets; + + return true; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXEntry.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXEntry.cpp new file mode 100644 index 0000000..52d1a6f --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXEntry.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include + + +namespace gamelib::tex +{ + template + static void alignStream(ZBio::ZBinaryWriter::BinaryWriter *writerStream) + { + const size_t remainder = writerStream->tell() % Align; + const size_t paddingSize = remainder > 0 ? Align - remainder : 0; + + for (int i = 0; i < paddingSize; i++) + { + writerStream->write(Pad); + } + } + + void PALPalette::deserialize(PALPalette &palette, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + palette.m_size = binaryReader->read(); + palette.m_data = std::make_unique(palette.m_size * 4); + binaryReader->read(palette.m_data.get(), static_cast(palette.m_size) * 4); + } + + void PALPalette::serialize(const PALPalette &palette, ZBio::ZBinaryWriter::BinaryWriter *writerStream) + { + writerStream->write(palette.m_size); + writerStream->write(palette.m_data.get(), static_cast(palette.m_size) * 4); + } + + void TEXMipLevel::deserialize(TEXMipLevel &mipLevel, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + mipLevel.m_mipLevelSize = binaryReader->read(); + mipLevel.m_buffer = std::make_unique(mipLevel.m_mipLevelSize); + binaryReader->read(mipLevel.m_buffer.get(), static_cast(mipLevel.m_mipLevelSize)); + } + + void TEXMipLevel::serialize(const TEXMipLevel &mipLevel, ZBio::ZBinaryWriter::BinaryWriter *writerStream) + { + writerStream->write(mipLevel.m_mipLevelSize); + writerStream->write(mipLevel.m_buffer.get(), static_cast(mipLevel.m_mipLevelSize)); + } + + void TEXCubeMaps::deserialize(TEXCubeMaps &cubeMaps, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + cubeMaps.m_count = binaryReader->read(); + cubeMaps.m_textureIndices.resize(cubeMaps.m_count); + binaryReader->read(cubeMaps.m_textureIndices.data(), cubeMaps.m_count); + } + + void TEXCubeMaps::serialize(const TEXCubeMaps &cubeMaps, ZBio::ZBinaryWriter::BinaryWriter *writerStream) + { + writerStream->write(static_cast(cubeMaps.m_textureIndices.size())); + writerStream->write(cubeMaps.m_textureIndices.data(), static_cast(cubeMaps.m_textureIndices.size())); + } + + void TEXEntry::deserialize(TEXEntry &entry, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + entry.m_fileSize = binaryReader->read(); + auto checkFormat = [](uint32_t format) -> bool { + if (auto type = static_cast(format); + type != TEXEntryType::ET_BITMAP_I8 && type != TEXEntryType::ET_BITMAP_EMBM && + type != TEXEntryType::ET_BITMAP_DOT3 && type != TEXEntryType::ET_BITMAP_CUBE && type != TEXEntryType::ET_BITMAP_DMAP && + type != TEXEntryType::ET_BITMAP_PAL && type != TEXEntryType::ET_BITMAP_PAL_OPAC && + type != TEXEntryType::ET_BITMAP_32 && type != TEXEntryType::ET_BITMAP_U8V8 && + type != TEXEntryType::ET_BITMAP_DXT1 && type != TEXEntryType::ET_BITMAP_DXT3 + ) { + return false; + } + + return true; + }; + + { + if (auto type = binaryReader->read(); !checkFormat(type)) + { + assert(false && "Bad format!"); + return; + } + else + { + entry.m_type1 = static_cast(type); + } + } + + { + if (auto type = binaryReader->read(); !checkFormat(type)) + { + assert(false && "Bad format [2]!"); + return; + } + else + { + entry.m_type2 = static_cast(type); + } + } + + entry.m_index = binaryReader->read(); + entry.m_height = binaryReader->read(); + entry.m_width = binaryReader->read(); + entry.m_numOfMipMaps = binaryReader->read(); + + // https://github.com/pavledev/GlacierTEXEditor/blob/master/GlacierTEXEditor/Form1.cs#L296 + + entry.m_flags = binaryReader->read(); + entry.m_unk2 = binaryReader->read(); + entry.m_unk3 = binaryReader->read(); + + // Allocate mip levels + entry.m_mipLevels.resize(entry.m_numOfMipMaps); + + // Read filename + if (auto fileName = binaryReader->readCString(); !fileName.empty()) + { + entry.m_fileName = std::move(fileName); + } + else + { + entry.m_fileName = std::nullopt; + } + + // Read mip levels + for (auto& mipEntry : entry.m_mipLevels) + { + TEXMipLevel::deserialize(mipEntry, binaryReader); + } + + // If PALN - need to save palette. TODO: Maybe for PALO need save this too? + if (entry.m_type1 == TEXEntryType::ET_BITMAP_PAL) + { + PALPalette& palette = entry.m_palPalette.emplace(); + PALPalette::deserialize(palette, binaryReader); + } + } + + void TEXEntry::serialize(const TEXEntry &entry, ZBio::ZBinaryWriter::BinaryWriter *writerStream, uint32_t& entryOffset, uint32_t& cubeMapsDescOffset) + { + // Save stream pos + entryOffset = writerStream->tell(); + + // Write data + writerStream->write(entry.m_fileSize); + writerStream->write(static_cast(entry.m_type1)); + writerStream->write(static_cast(entry.m_type2)); + writerStream->write(entry.m_index); + writerStream->write(entry.m_height); + writerStream->write(entry.m_width); + writerStream->write(static_cast(entry.m_mipLevels.size())); + writerStream->write(entry.m_flags); + writerStream->write(entry.m_unk2); + writerStream->write(entry.m_unk3); + if (entry.m_fileName.has_value() && !entry.m_fileName.value().empty()) + { + writerStream->writeCString(entry.m_fileName.value()); + } + else + { + writerStream->write(0u); + } + + // Write MIP levels + for (const auto& mipEntry : entry.m_mipLevels) + { + TEXMipLevel::serialize(mipEntry, writerStream); + } + + if (entry.m_type1 == TEXEntryType::ET_BITMAP_PAL) //NOTE: Check for PALO (Opac) here, or remove support of that texture type + { + // Write palette + assert(entry.m_palPalette.has_value()); + PALPalette::serialize(entry.m_palPalette.value(), writerStream); + } + + alignStream<0x10, 0x00>(writerStream); + + if (!entry.m_cubeMaps.m_textureIndices.empty()) + { + cubeMapsDescOffset = writerStream->tell(); + + TEXCubeMaps::serialize(entry.m_cubeMaps, writerStream); + alignStream<0x10, 0x00>(writerStream); + } + else + { + cubeMapsDescOffset = 0; + } + } + + uint32_t TEXEntry::calculateSize() const + { + /** + * @source https://github.com/pavledev/GlacierTEXEditor/blob/master/GlacierTEXEditor/Form1.cs#L852 + */ + const std::string kEmptyStr {}; + uint32_t fileSize = sizeof(m_type1) + sizeof(m_type1) + + sizeof(uint32_t) + + sizeof(uint16_t) + sizeof(uint16_t) + + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t) + + (m_fileName.value_or(kEmptyStr).length()) + 1; + + for (const auto& mipLevel : m_mipLevels) + { + fileSize += mipLevel.m_mipLevelSize; + } + + if (m_type1 == TEXEntryType::ET_BITMAP_PAL) + { + fileSize += m_palPalette.value().m_size * 4; + } + + if (!m_cubeMaps.m_textureIndices.empty()) + { + fileSize += sizeof(uint32_t) + m_cubeMaps.m_textureIndices.size(); + } + + return fileSize; + } +} diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp new file mode 100644 index 0000000..04fd72b --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp @@ -0,0 +1,36 @@ +#include +#include +#include + + +namespace gamelib::tex +{ + TEXHeader::TEXHeader(uint32_t table1Offset, uint32_t table2Offset, uint32_t unk1, uint32_t unk2) + : m_texturesPoolOffset(table1Offset) + , m_cubeMapsPoolOffset(table2Offset) + , m_unk1(unk1) + , m_unk2(unk2) + { + } + + TEXHeader::operator bool() const noexcept + { + return true; //idk how to validate it now + } + + void TEXHeader::deserialize(TEXHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + header.m_texturesPoolOffset = binaryReader->read(); + header.m_cubeMapsPoolOffset = binaryReader->read(); + header.m_unk1 = binaryReader->read(); + header.m_unk2 = binaryReader->read(); + } + + void TEXHeader::serialize(const TEXHeader &header, ZBio::ZBinaryWriter::BinaryWriter *writerStream) + { + writerStream->write(header.m_texturesPoolOffset); + writerStream->write(header.m_cubeMapsPoolOffset); + writerStream->write(header.m_unk1); + writerStream->write(header.m_unk2); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXReader.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXReader.cpp new file mode 100644 index 0000000..304b5e2 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXReader.cpp @@ -0,0 +1,91 @@ +#include +#include + + +namespace gamelib::tex +{ + bool TEXReader::parse(const uint8_t *texFileBuffer, int64_t texFileSize) + { + if (!texFileBuffer || !texFileSize) + return false; + + { + ZBio::ZBinaryReader::BinaryReader reader{ + reinterpret_cast(texFileBuffer), + static_cast(texFileSize)}; + + // Read header + TEXHeader::deserialize(m_header, &reader); + } + + if (m_header.m_texturesPoolOffset >= texFileSize || m_header.m_cubeMapsPoolOffset >= texFileSize) + return false; // Invalid file + + // Read textures pool + { + ZBio::ZBinaryReader::BinaryReader reader{ + reinterpret_cast(&texFileBuffer[m_header.m_texturesPoolOffset]), + static_cast(texFileSize) + }; + + reader.read(m_texturesPool.data(), static_cast(m_texturesPool.size())); + } + + ///NOTE: For PS4 support see https://github.com/pavledev/GlacierTEXEditor/blob/master/GlacierTEXEditor/Form1.cs#L261 + // Now read entries + m_entries.clear(); + + for (const uint32_t& entryOffset : m_texturesPool) + { + if (entryOffset != 0) + { + // Do read + ZBio::ZBinaryReader::BinaryReader reader{ + reinterpret_cast(&texFileBuffer[entryOffset]), + static_cast(texFileSize - entryOffset) // Technically we must set some constants, but who cares? + }; + + TEXEntry& entry = m_entries.emplace_back(); + entry.m_offset = entryOffset; + TEXEntry::deserialize(entry, &reader); + } + else + { + if (m_entries.empty()) + { + ++m_countOfEmptyOffsets; + } + } + } + + // Read cube maps pool #2 + { + ZBio::ZBinaryReader::BinaryReader reader{ + reinterpret_cast(&texFileBuffer[m_header.m_cubeMapsPoolOffset]), + static_cast(texFileSize) + }; + + reader.read(m_cubeMapsPool.data(), static_cast(m_cubeMapsPool.size())); + } + + // Read cubemaps + { + for (auto& entry : m_entries) + { + // A right way to get cube maps working in game + if (!(entry.m_flags & TEXEntryFlags::TEX_EF_IsCubeMap)) + continue; + + const uint32_t offset = m_cubeMapsPool[entry.m_index]; + + ZBio::ZBinaryReader::BinaryReader reader{ + reinterpret_cast(&texFileBuffer[offset]), static_cast(texFileSize) + }; + + TEXCubeMaps::deserialize(entry.m_cubeMaps, &reader); + } + } + + return true; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp new file mode 100644 index 0000000..52faecc --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp @@ -0,0 +1,46 @@ +#include +#include + + +namespace gamelib::tex +{ + void TEXWriter::write(const TEXHeader &header, const std::vector &entries, std::vector &outBuffer) + { + auto writerSink = std::make_unique(); + auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink)); + + // Offsets & pools + uint32_t texturesPoolOffset = 0u; + uint32_t cubeMapsPoolOffset = 0u; + + OffsetsPool textureOffsetsPool { 0u }; + OffsetsPool cubeMapsOffsetsPool { 0u }; + + // Write header (will modify it later) + TEXHeader::serialize(header, &binaryWriter); + + // Write entry by entry + for (size_t entryIndex = 0; entryIndex < entries.size(); ++entryIndex) // NOLINT(modernize-loop-convert) + { + const auto& entry = entries[entryIndex]; + TEXEntry::serialize(entry, &binaryWriter, textureOffsetsPool[entry.m_index], cubeMapsOffsetsPool[entry.m_index]); + } + + // Next is going offsets table + texturesPoolOffset = binaryWriter.tell(); + binaryWriter.write(textureOffsetsPool.data(), static_cast(textureOffsetsPool.size())); + + // And write cube map indices pool + cubeMapsPoolOffset = binaryWriter.tell(); + binaryWriter.write(cubeMapsOffsetsPool.data(), static_cast(cubeMapsOffsetsPool.size())); + + // Finally, copy data to final buffer + auto raw = binaryWriter.release().value(); + std::copy(raw.begin(), raw.end(), std::back_inserter(outBuffer)); + + // And replace offsets (classic) + auto* finalHeader = reinterpret_cast(outBuffer.data()); + finalHeader->m_texturesPoolOffset = texturesPoolOffset; + finalHeader->m_cubeMapsPoolOffset = cubeMapsPoolOffset; + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 498a559..98ad95c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,13 +3,10 @@ project(BMEdit) set(CMAKE_CXX_STANDARD 20) -# --- Conan deps -if(EXISTS ${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) - include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) - conan_basic_setup() -else() - message(FATAL_ERROR "The file conanbuildinfo.cmake doesn't exist (lookup ${CMAKE_BINARY_DIR}), please, follow README.md \"Build\" for more details!") -endif() +# --- Deps +find_package(ZLIB REQUIRED HINTS ${CMAKE_BINARY_DIR}) +find_package(libzip REQUIRED HINTS ${CMAKE_BINARY_DIR}) +find_package(libsquish REQUIRED HINTS ${CMAKE_BINARY_DIR}) # --- Global dependencies add_subdirectory(ThirdParty/fmt) @@ -41,12 +38,7 @@ if (WIN32) add_custom_command(TARGET BMEdit POST_BUILD COMMAND "${CMAKE_COMMAND}" -E env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}" - "$" -3dcore -3drenderer -3dinput -3danimation -3dextras -network + "$" --no-translations -network COMMENT "Running windeployqt...") - - # Copy binary dependencies - add_custom_command(TARGET BMEdit POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy "${CONAN_BIN_DIRS_LIBZIP}/zip.dll" "$" - COMMENT "Copy conan dependencies to bin") endif() # TODO: Support other OS later \ No newline at end of file diff --git a/README.md b/README.md index 7b2731a..c7a42a0 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,17 @@ Build First of all you need to install [conan](https://conan.io) dependencies manager on your system. -Then download (or git clone) this repository and do -``` -conan install . -s build_type=Debug --install-folder=cmake-build-debug -``` -or +**Note** Currently supported only Conan 2 (2.0.9 in my env) + +Download (or git clone) this repository and do ``` -conan install . -s build_type=Release --install-folder=cmake-build-release +conan profile detect --force +conan install . --output-folder=cmake-build-debug --build=missing -s build_type=Debug ``` -Reload cmake project in your IDE or +(replace `cmake-build-debug` to your ; replace Debug to Release for release build) + +Then reload cmake project in your IDE or ``` cd cmake --build . diff --git a/conanfile.txt b/conanfile.txt index 64362d5..7e2382c 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,8 +1,7 @@ [requires] +zlib/1.2.13 libzip/1.8.0 +libsquish/1.15 [generators] -cmake - -[options] -libzip:shared=True \ No newline at end of file +CMakeDeps \ No newline at end of file From 52a041892bcedd3cc0d8281ebb887b4be3197ffc Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 12:22:32 +0300 Subject: [PATCH 02/80] Trying to fix conan build script in GitHub workflows --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 175fe7a..43bb4d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,8 @@ jobs: - name: Make build folder and install Conan dependencies run: | mkdir build - conan install . -s build_type=Release --install-folder=build + conan profile detect --force + conan install . --output-folder=build --build=missing -s build_type=Release - name: Generate projects run: | From 505cd3055a61ebdbabcffb645dfd6051a0cb8c4a Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 14:25:46 +0300 Subject: [PATCH 03/80] Trying to fix build (added deps into cmake) --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98ad95c..5a06246 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ set(CMAKE_CXX_STANDARD 20) # --- Deps find_package(ZLIB REQUIRED HINTS ${CMAKE_BINARY_DIR}) +find_package(BZip2 REQUIRED HINTS ${CMAKE_BINARY_DIR}) +find_package(LibLZMA REQUIRED HINTS ${CMAKE_BINARY_DIR}) +find_package(zstd REQUIRED HINTS ${CMAKE_BINARY_DIR}) find_package(libzip REQUIRED HINTS ${CMAKE_BINARY_DIR}) find_package(libsquish REQUIRED HINTS ${CMAKE_BINARY_DIR}) From 7a07f532d0948f52e81518e89afc99549450c0fb Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 14:39:39 +0300 Subject: [PATCH 04/80] Trying to fix deploy script --- .github/workflows/build.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43bb4d9..b000d98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,24 +62,24 @@ jobs: - name: Move assets to distribution folder run: | mkdir dist - mv build/bin/BMEdit.exe dist - mv build/bin/d3dcompiler_47.dll dist - mv build/bin/opengl32sw.dll dist - mv build/bin/Qt6Core.dll dist - mv build/bin/Qt6Gui.dll dist - mv build/bin/Qt6OpenGL.dll dist - mv build/bin/Qt6OpenGLWidgets.dll dist - mv build/bin/Qt6Svg.dll dist - mv build/bin/Qt6Widgets.dll dist - mv build/bin/Qt6Network.dll dist - mv build/bin/Qt6Concurrent.dll dist - mv build/bin/translations dist/translations - mv build/bin/styles dist/styles - mv build/bin/platforms dist/platforms - mv build/bin/networkinformation dist/networkinformation - mv build/bin/tls dist/tls - mv build/bin/imageformats dist/imageformats - mv build/bin/iconengines dist/iconengines + mv build/Release/BMEdit.exe dist + mv build/Release/d3dcompiler_47.dll dist + mv build/Release/opengl32sw.dll dist + mv build/Release/Qt6Core.dll dist + mv build/Release/Qt6Gui.dll dist + mv build/Release/Qt6OpenGL.dll dist + mv build/Release/Qt6OpenGLWidgets.dll dist + mv build/Release/Qt6Svg.dll dist + mv build/Release/Qt6Widgets.dll dist + mv build/Release/Qt6Network.dll dist + mv build/Release/Qt6Concurrent.dll dist + mv build/Release/translations dist/translations + mv build/Release/styles dist/styles + mv build/Release/platforms dist/platforms + mv build/Release/networkinformation dist/networkinformation + mv build/Release/tls dist/tls + mv build/Release/imageformats dist/imageformats + mv build/Release/iconengines dist/iconengines mv Assets/g1 dist/g1 mv Assets/TypesRegistry.json dist mv README.md dist From 5c28ce2766dd33d54ebebb2291460dd6984f8dd9 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 14:53:34 +0300 Subject: [PATCH 05/80] Another try to build it --- .github/workflows/build.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b000d98..1d068a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,17 +63,7 @@ jobs: run: | mkdir dist mv build/Release/BMEdit.exe dist - mv build/Release/d3dcompiler_47.dll dist - mv build/Release/opengl32sw.dll dist - mv build/Release/Qt6Core.dll dist - mv build/Release/Qt6Gui.dll dist - mv build/Release/Qt6OpenGL.dll dist - mv build/Release/Qt6OpenGLWidgets.dll dist - mv build/Release/Qt6Svg.dll dist - mv build/Release/Qt6Widgets.dll dist - mv build/Release/Qt6Network.dll dist - mv build/Release/Qt6Concurrent.dll dist - mv build/Release/translations dist/translations + mv build/Release/*.dll dist mv build/Release/styles dist/styles mv build/Release/platforms dist/platforms mv build/Release/networkinformation dist/networkinformation From 8d6bf07ed12a4afe43765e08d9a337b5a16f992d Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 30 Jul 2023 18:35:43 +0300 Subject: [PATCH 06/80] Fixed TEXWriter::write issue --- .../GameLib/Include/GameLib/TEX/TEXHeader.h | 2 -- .../GameLib/Source/GameLib/TEX/TEXHeader.cpp | 5 ----- .../GameLib/Source/GameLib/TEX/TEXWriter.cpp | 22 ++++++++----------- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h b/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h index 91e6526..9d9ea66 100644 --- a/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h +++ b/BMEdit/GameLib/Include/GameLib/TEX/TEXHeader.h @@ -20,8 +20,6 @@ namespace gamelib::tex TEXHeader() = default; TEXHeader(uint32_t table1Offset, uint32_t table2Offset, uint32_t unk1, uint32_t unk2); - [[nodiscard]] explicit operator bool() const noexcept; - static void deserialize(TEXHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader); static void serialize(const TEXHeader &header, ZBio::ZBinaryWriter::BinaryWriter *writerStream); diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp index 04fd72b..c2d72bb 100644 --- a/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXHeader.cpp @@ -13,11 +13,6 @@ namespace gamelib::tex { } - TEXHeader::operator bool() const noexcept - { - return true; //idk how to validate it now - } - void TEXHeader::deserialize(TEXHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader) { header.m_texturesPoolOffset = binaryReader->read(); diff --git a/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp b/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp index 52faecc..cddeeac 100644 --- a/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp +++ b/BMEdit/GameLib/Source/GameLib/TEX/TEXWriter.cpp @@ -9,15 +9,12 @@ namespace gamelib::tex auto writerSink = std::make_unique(); auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink)); - // Offsets & pools - uint32_t texturesPoolOffset = 0u; - uint32_t cubeMapsPoolOffset = 0u; - + TEXHeader localHeader = header; OffsetsPool textureOffsetsPool { 0u }; OffsetsPool cubeMapsOffsetsPool { 0u }; - // Write header (will modify it later) - TEXHeader::serialize(header, &binaryWriter); + // Allocate space for header (will be serialized later) + binaryWriter.seek(sizeof(TEXHeader)); // Write entry by entry for (size_t entryIndex = 0; entryIndex < entries.size(); ++entryIndex) // NOLINT(modernize-loop-convert) @@ -27,20 +24,19 @@ namespace gamelib::tex } // Next is going offsets table - texturesPoolOffset = binaryWriter.tell(); + localHeader.m_texturesPoolOffset = binaryWriter.tell(); binaryWriter.write(textureOffsetsPool.data(), static_cast(textureOffsetsPool.size())); // And write cube map indices pool - cubeMapsPoolOffset = binaryWriter.tell(); + localHeader.m_cubeMapsPoolOffset = binaryWriter.tell(); binaryWriter.write(cubeMapsOffsetsPool.data(), static_cast(cubeMapsOffsetsPool.size())); + // Serialize header + binaryWriter.seek(0); + TEXHeader::serialize(localHeader, &binaryWriter); + // Finally, copy data to final buffer auto raw = binaryWriter.release().value(); std::copy(raw.begin(), raw.end(), std::back_inserter(outBuffer)); - - // And replace offsets (classic) - auto* finalHeader = reinterpret_cast(outBuffer.data()); - finalHeader->m_texturesPoolOffset = texturesPoolOffset; - finalHeader->m_cubeMapsPoolOffset = cubeMapsPoolOffset; } } \ No newline at end of file From 7e6fb9980a048b753b16d48ff29c7ad0caf6ca96 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 7 Oct 2023 15:14:55 +0300 Subject: [PATCH 07/80] PRM reader by 2kpr ported & added to GameLib --- .gitmodules | 3 + .../Source/Widgets/PrimitivePreviewWidget.cpp | 20 +- BMEdit/GameLib/CMakeLists.txt | 2 +- BMEdit/GameLib/Include/GameLib/Level.h | 20 +- BMEdit/GameLib/Include/GameLib/PRM/PRM.h | 5 +- .../GameLib/PRM/PRMBadChunkException.h | 19 -- .../GameLib/Include/GameLib/PRM/PRMBadFile.h | 17 -- BMEdit/GameLib/Include/GameLib/PRM/PRMChunk.h | 45 ---- .../Include/GameLib/PRM/PRMChunkDescriptor.h | 24 -- .../GameLib/PRM/PRMChunkRecognizedKind.h | 14 -- .../PRM/PRMDescriptionChunkBaseHeader.h | 37 --- .../GameLib/Include/GameLib/PRM/PRMEntries.h | 89 +++++++ .../Include/GameLib/PRM/PRMException.h | 13 - .../GameLib/Include/GameLib/PRM/PRMHeader.h | 23 -- .../Include/GameLib/PRM/PRMIndexChunkHeader.h | 20 -- .../GameLib/Include/GameLib/PRM/PRMReader.h | 27 +- .../GameLib/PRM/PRMVertexBufferHeader.h | 12 - .../Include/GameLib/PRM/PRMVertexFormat.h | 18 -- BMEdit/GameLib/Include/GameLib/ZBioHelpers.h | 34 +++ BMEdit/GameLib/Source/GameLib/Level.cpp | 24 +- .../GameLib/PRM/PRMBadChunkException.cpp | 16 -- .../GameLib/Source/GameLib/PRM/PRMBadFile.cpp | 15 -- .../GameLib/Source/GameLib/PRM/PRMChunk.cpp | 165 ------------- .../Source/GameLib/PRM/PRMChunkDescriptor.cpp | 16 -- .../PRM/PRMDescriptionChunkBaseHeader.cpp | 31 --- .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 231 ++++++++++++++++++ .../GameLib/Source/GameLib/PRM/PRMHeader.cpp | 14 -- .../GameLib/PRM/PRMIndexChunkHeader.cpp | 12 - .../GameLib/Source/GameLib/PRM/PRMReader.cpp | 150 ++++++------ CMakeLists.txt | 1 + ThirdParty/glm | 1 + 31 files changed, 481 insertions(+), 637 deletions(-) delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMBadChunkException.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMBadFile.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMChunk.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMChunkDescriptor.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMChunkRecognizedKind.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMDescriptionChunkBaseHeader.h create mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMException.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMHeader.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMIndexChunkHeader.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMVertexBufferHeader.h delete mode 100644 BMEdit/GameLib/Include/GameLib/PRM/PRMVertexFormat.h create mode 100644 BMEdit/GameLib/Include/GameLib/ZBioHelpers.h delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMBadChunkException.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMBadFile.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMChunk.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMChunkDescriptor.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMDescriptionChunkBaseHeader.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMHeader.cpp delete mode 100644 BMEdit/GameLib/Source/GameLib/PRM/PRMIndexChunkHeader.cpp create mode 160000 ThirdParty/glm diff --git a/.gitmodules b/.gitmodules index 63856cc..764928f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "ThirdParty/fmt"] path = ThirdParty/fmt url = https://github.com/fmtlib/fmt +[submodule "ThirdParty/glm"] + path = ThirdParty/glm + url = https://github.com/g-truc/glm diff --git a/BMEdit/Editor/Source/Widgets/PrimitivePreviewWidget.cpp b/BMEdit/Editor/Source/Widgets/PrimitivePreviewWidget.cpp index 2964f11..6a13db4 100644 --- a/BMEdit/Editor/Source/Widgets/PrimitivePreviewWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/PrimitivePreviewWidget.cpp @@ -75,15 +75,17 @@ void PrimitivePreviewWidget::resizeGL(int w, int h) void PrimitivePreviewWidget::doPreloadNewPrimitive() { - auto& chk = m_level->getLevelGeometry()->chunks.at(m_primitiveIndex); - if (chk.getKind() != gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER) - { - // Invalid case: we've unable to draw model by non-descriptor index - m_primitiveIndex = 0u; - return; - } - - const auto descriptionHeader = chk.getDescriptionBufferHeader(); + m_primitiveIndex = 0u; // invalid case, do nothing + +// auto& chk = m_level->getLevelGeometry()->chunks.at(m_primitiveIndex); +// if (chk.getKind() != gamelib::prm::PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER) +// { +// // Invalid case: we've unable to draw model by non-descriptor index +// m_primitiveIndex = 0u; +// return; +// } +// +// const auto descriptionHeader = chk.getDescriptionBufferHeader(); //TODO: Extract index buffer, extract vertex buffer, validate index buffer, validate vertex buffer. } diff --git a/BMEdit/GameLib/CMakeLists.txt b/BMEdit/GameLib/CMakeLists.txt index 3516a71..9b8d0b6 100644 --- a/BMEdit/GameLib/CMakeLists.txt +++ b/BMEdit/GameLib/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(GameLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Include) # --- Dependencies target_link_libraries(GameLib PRIVATE ZBinaryReader) # Private libs -target_link_libraries(GameLib PUBLIC nlohmann_json::nlohmann_json fmt::fmt-header-only) # Public library to work with json +target_link_libraries(GameLib PUBLIC nlohmann_json::nlohmann_json fmt::fmt-header-only glm::glm) target_link_libraries(GameLib PUBLIC ${ZLIB_LIBRARIES}) # Public library to work with compressed streams target_include_directories(GameLib PRIVATE ${ZLIB_INCLUDE_DIRS}) diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 4745ad0..6c6b994 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -2,10 +2,10 @@ #include #include -#include #include #include #include +#include #include #include @@ -22,13 +22,6 @@ namespace gamelib uint32_t objectsCount; }; - struct LevelGeometry - { - prm::PRMHeader header; - std::vector chunkDescriptors; - std::vector chunks; - }; - struct LevelTextures { tex::TEXHeader header; @@ -43,6 +36,11 @@ namespace gamelib gms::GMSHeader header; }; + struct LevelGeometry + { + prm::PrmFile primitives; + }; + class Level { public: @@ -54,10 +52,10 @@ namespace gamelib [[nodiscard]] const LevelProperties *getLevelProperties() const; [[nodiscard]] LevelProperties *getLevelProperties(); [[nodiscard]] const SceneProperties *getSceneProperties() const; - [[nodiscard]] const LevelGeometry* getLevelGeometry() const; - [[nodiscard]] LevelGeometry* getLevelGeometry(); [[nodiscard]] const LevelTextures* getSceneTextures() const; [[nodiscard]] LevelTextures* getSceneTextures(); + [[nodiscard]] const LevelGeometry* getLevelGeometry() const; + [[nodiscard]] LevelGeometry* getLevelGeometry(); [[nodiscard]] const std::vector &getSceneObjects() const; @@ -77,8 +75,8 @@ namespace gamelib // Raw data LevelProperties m_levelProperties; SceneProperties m_sceneProperties; - LevelGeometry m_levelGeometry; LevelTextures m_levelTextures; + LevelGeometry m_levelGeometry; // Managed objects std::vector m_sceneObjects {}; diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRM.h b/BMEdit/GameLib/Include/GameLib/PRM/PRM.h index 58015fe..d616e69 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRM.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRM.h @@ -1,5 +1,4 @@ #pragma once -#include -#include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMBadChunkException.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMBadChunkException.h deleted file mode 100644 index 6a38566..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMBadChunkException.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include - - -namespace gamelib::prm -{ - class PRMBadChunkException : public PRMException - { - public: - explicit PRMBadChunkException(std::uint32_t chunkIndex); - - [[nodiscard]] char const* what() const override; - - private: - std::string m_errorMessage; - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMBadFile.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMBadFile.h deleted file mode 100644 index 4a27782..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMBadFile.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - - -namespace gamelib::prm -{ - class PRMBadFile : public PRMException - { - public: - explicit PRMBadFile(const std::string& reason); - - [[nodiscard]] char const* what() const override; - private: - std::string m_message; - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunk.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMChunk.h deleted file mode 100644 index 42d0e38..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunk.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - - -namespace gamelib::prm -{ - class PRMChunk - { - private: - struct NullData {}; - - std::uint32_t m_chunkIndex { 0u }; - std::unique_ptr m_buffer { nullptr }; - std::size_t m_bufferSize { 0 }; - PRMChunkRecognizedKind m_recognizedKind { PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER }; - std::variant m_data; - - public: - PRMChunk(); - PRMChunk(std::uint32_t chunkIndex, int totalChunksNr, std::unique_ptr &&buffer, std::size_t size); - - [[nodiscard]] std::uint32_t getIndex() const; - [[nodiscard]] Span getBuffer(); - [[nodiscard]] PRMChunkRecognizedKind getKind() const; - - [[nodiscard]] const PRMDescriptionChunkBaseHeader* getDescriptionBufferHeader() const; - [[nodiscard]] PRMDescriptionChunkBaseHeader* getDescriptionBufferHeader(); - - [[nodiscard]] const PRMIndexChunkHeader* getIndexBufferHeader() const; - [[nodiscard]] PRMIndexChunkHeader* getIndexBufferHeader(); - - [[nodiscard]] const PRMVertexBufferHeader* getVertexBufferHeader() const; - [[nodiscard]] PRMVertexBufferHeader* getVertexBufferHeader(); - - private: - void recognizeChunkKindAndSaveData(Span chunk, int totalChunksNr); - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkDescriptor.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkDescriptor.h deleted file mode 100644 index b9685f5..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkDescriptor.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - - -namespace ZBio::ZBinaryReader -{ - class BinaryReader; -} - -namespace gamelib::prm -{ - struct PRMChunkDescriptor - { - uint32_t declarationOffset; // +0x0 - uint32_t declarationSize; // +0x4 (need to be confirmed! Source: https://github.com/HHCHunter/Hitman-BloodMoney/blob/master/TOOLS/PRMConverter/Source/PRMConvert.cpp#L23) - uint32_t declarationKind; // +0x8 - uint32_t unkC; // +0xC - - static constexpr int kDescriptorSize = 0x10; - - static void deserialize(PRMChunkDescriptor &descriptor, ZBio::ZBinaryReader::BinaryReader *binaryReader); - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkRecognizedKind.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkRecognizedKind.h deleted file mode 100644 index 86954d6..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMChunkRecognizedKind.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - - -namespace gamelib::prm -{ - enum class PRMChunkRecognizedKind - { - CRK_ZERO_CHUNK, ///< Only for chunk #0 - CRK_INDEX_BUFFER, ///< For chunk with indices data - CRK_VERTEX_BUFFER, ///< For chunk with vertices data - CRK_DESCRIPTION_BUFFER, ///< For chunk with description - CRK_UNKNOWN_BUFFER ///< For unrecognized chunk, it may contains any kind of data - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMDescriptionChunkBaseHeader.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMDescriptionChunkBaseHeader.h deleted file mode 100644 index 1c12853..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMDescriptionChunkBaseHeader.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - - -namespace ZBio::ZBinaryReader -{ - class BinaryReader; -} - -namespace gamelib::prm -{ - struct PRMDescriptionChunkBaseHeader - { - std::uint8_t boneDeclOffset { 0 }; - std::uint8_t primPackType { 0 }; - std::uint16_t kind { 0 }; - std::uint16_t textureId { 0 }; - std::uint16_t unk6 { 0 }; - std::uint32_t nextVariation { 0 }; - std::uint8_t unkC { 0 }; - std::uint8_t unkD { 0 }; - std::uint8_t unkE { 0 }; - std::uint8_t currentVariation { 0 }; - std::uint16_t ptrParts { 0 }; - std::uint16_t materialIdx { 0 }; - std::uint32_t totalVariations { 0 }; - std::uint16_t ptrObjects { 0 }; - std::uint16_t ptrObjects_HI { 0 }; - std::uint32_t unk3 { 0 }; - BoundingBox boundingBox {}; - - static void deserialize(PRMDescriptionChunkBaseHeader& header, ZBio::ZBinaryReader::BinaryReader *binaryReader); - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h new file mode 100644 index 0000000..fdef7ee --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -0,0 +1,89 @@ +/** + * Credits: + * * 2kpr - https://github.com/glacier-modding/io_scene_blood_money/blob/libraries/BMExport/src/Prm.hpp + */ +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::prm +{ +#pragma pack(push, 1) // TODO: Need to use some sort of macro to make this place cross-compiler supportable + struct PrmFile; + + struct Index + { + uint16_t a = 0; + uint16_t b = 0; + uint16_t c = 0; + + static void deserialize(Index& index, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct Mesh + { + uint8_t lod = 0; + uint16_t material_id = 0; + int32_t diffuse_id = 0; + int32_t normal_id = 0; + int32_t specular_id = 0; + std::vector vertices {}; + std::vector indices {}; + std::vector uvs {}; + + static void deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile); + }; + + struct Model + { + uint32_t chunk = 0; + std::vector meshes {}; + }; + + struct Chunk + { + std::unique_ptr data { nullptr }; + bool is_model = false; + uint32_t model = 0; + }; + + struct Entry + { + uint32_t offset = 0; + uint32_t size = 0; + uint32_t type = 0; + uint32_t pad = 0; + + static void deserialize(Entry& entry, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct Header + { + uint32_t table_offset = 0; + uint32_t table_count = 0; + uint32_t table_offset2 = 0; + uint32_t zeroed = 0; + + static void deserialize(Header& header, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct PrmFile + { + Header header; + std::vector entries; + std::vector chunks; + std::vector models; + }; +#pragma pack(pop) +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMException.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMException.h deleted file mode 100644 index 114e018..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMException.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - - -namespace gamelib::prm -{ - class PRMException : public std::exception - { - public: - using std::exception::exception; - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMHeader.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMHeader.h deleted file mode 100644 index d724bc9..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMHeader.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - - - -namespace ZBio::ZBinaryReader -{ - class BinaryReader; -} - -namespace gamelib::prm -{ - struct PRMHeader - { - uint32_t chunkOffset { 0 }; // Header + 0x0: Offset of main chunk - uint32_t countOfPrimitives { 0 }; // Header + 0x4: Total geoms count on scene (max about 0x21000 things) - uint32_t chunkOffset2 {0}; // Duplicate of chunkOffset, maybe second chunk? Or ... LODs? - uint32_t zeroed {0}; // always zero (maybe used as 'checkpoint') - - static void deserialize(PRMHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader); - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMIndexChunkHeader.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMIndexChunkHeader.h deleted file mode 100644 index 2617131..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMIndexChunkHeader.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - - -namespace ZBio::ZBinaryReader -{ - class BinaryReader; -} - -namespace gamelib::prm -{ - struct PRMIndexChunkHeader - { - std::uint16_t unk0; - std::uint16_t indicesCount; - - static void deserialize(PRMIndexChunkHeader& header, ZBio::ZBinaryReader::BinaryReader *binaryReader); - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMReader.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMReader.h index 0e57933..8d762b6 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMReader.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMReader.h @@ -1,31 +1,24 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include -namespace gamelib::prm +namespace gamelib { + /** + * @note It's not full implementation of reader (it's doing partial read, but enough to visualize level geometry in editor) + * @todo Need to complete reverse engineering of this stuff and make writer + */ class PRMReader { public: - PRMReader() = delete; - PRMReader(PRMHeader &header, std::vector &chunkDescriptors, std::vector &chunks); + PRMReader(); - bool read(Span buffer); + bool parse(const std::uint8_t* pBuffer, std::size_t iBufferSize); - [[nodiscard]] const PRMHeader &getHeader() const; - [[nodiscard]] const std::vector &getChunkDescriptors() const; - [[nodiscard]] PRMChunk* getChunkAt(size_t chunkIndex); - [[nodiscard]] const PRMChunk* getChunkAt(size_t chunkIndex) const; + prm::PrmFile&& takePrimitives(); private: - PRMHeader& m_header; - std::vector& m_chunks; - std::vector& m_chunkDescriptors; + prm::PrmFile m_file {}; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexBufferHeader.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexBufferHeader.h deleted file mode 100644 index 0c804da..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexBufferHeader.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - - -namespace gamelib::prm -{ - struct PRMVertexBufferHeader - { - PRMVertexBufferFormat vertexFormat { PRMVertexBufferFormat::VBF_UNKNOWN_VERTEX }; - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexFormat.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexFormat.h deleted file mode 100644 index b3ebb82..0000000 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMVertexFormat.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - - -namespace gamelib::prm -{ - enum class PRMVertexBufferFormat : std::uint8_t - { - VBF_VERTEX_10 = 0x10, - VBF_VERTEX_24 = 0x24, - VBF_VERTEX_28 = 0x28, - VBF_VERTEX_34 = 0x34, - - VBF_SIMPLE_VERTEX = VBF_VERTEX_10, - VBF_UNKNOWN_VERTEX = 0x0 - }; -} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h new file mode 100644 index 0000000..9d29389 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +// Just a few helpers for BinaryIO library +namespace gamelib +{ + struct ZBioHelpers + { + /** + * @brief Move position by offset from current position (pos += offset) + * @param reader + * @param offset + */ + static void seekBy(ZBio::ZBinaryReader::BinaryReader* reader, int64_t offset) + { + if (!reader) + { + assert(reader != nullptr); + return; + } + + const int64_t finalPos = offset + reader->tell(); + if (finalPos < 0 || finalPos > reader->size()) + { + assert(false && "Out of bounds!"); + return; + } + + reader->seek(finalPos); + } + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 9ac74dd..1324aab 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -8,7 +8,6 @@ #include #include -#include namespace gamelib @@ -81,24 +80,25 @@ namespace gamelib return nullptr; } - const LevelGeometry *Level::getLevelGeometry() const + const LevelTextures* Level::getSceneTextures() const { - return &m_levelGeometry; + return &m_levelTextures; } - LevelGeometry *Level::getLevelGeometry() + LevelTextures* Level::getSceneTextures() { - return &m_levelGeometry; + return &m_levelTextures; } - const LevelTextures* Level::getSceneTextures() const + + const LevelGeometry* Level::getLevelGeometry() const { - return &m_levelTextures; + return &m_levelGeometry; } - LevelTextures* Level::getSceneTextures() + LevelGeometry* Level::getLevelGeometry() { - return &m_levelTextures; + return &m_levelGeometry; } const std::vector &Level::getSceneObjects() const @@ -237,12 +237,14 @@ namespace gamelib return false; } - prm::PRMReader reader { m_levelGeometry.header, m_levelGeometry.chunkDescriptors, m_levelGeometry.chunks }; - if (!reader.read(Span(prmFileBuffer.get(), prmFileSize))) + PRMReader reader; + if (!reader.parse(prmFileBuffer.get(), prmFileSize)) { return false; } + m_levelGeometry.primitives = std::move(reader.takePrimitives()); + return true; } diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMBadChunkException.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMBadChunkException.cpp deleted file mode 100644 index 4ca8145..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMBadChunkException.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - - -namespace gamelib::prm -{ - PRMBadChunkException::PRMBadChunkException(std::uint32_t chunkIndex) : PRMException() - { - m_errorMessage = "Bad chunk #" + std::to_string(chunkIndex); - } - - const char *PRMBadChunkException::what() const - { - return m_errorMessage.data(); - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMBadFile.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMBadFile.cpp deleted file mode 100644 index 842069c..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMBadFile.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - - -namespace gamelib::prm -{ - PRMBadFile::PRMBadFile(const std::string& reason) : PRMException() - { - m_message = "Bad file: " + reason; - } - - const char *PRMBadFile::what() const - { - return m_message.data(); - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMChunk.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMChunk.cpp deleted file mode 100644 index 054105b..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMChunk.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include - - -namespace gamelib::prm -{ - PRMChunk::PRMChunk() = default; - - PRMChunk::PRMChunk(std::uint32_t chunkIndex, int totalChunksNr, std::unique_ptr &&buffer, std::size_t size) - : m_chunkIndex(chunkIndex) - , m_buffer(std::move(buffer)) - , m_bufferSize(size) - { - // Recognize type & save data - if (chunkIndex == 0u) - { - m_recognizedKind = PRMChunkRecognizedKind::CRK_ZERO_CHUNK; - } - else - { - recognizeChunkKindAndSaveData(getBuffer(), totalChunksNr); - } - } - - std::uint32_t PRMChunk::getIndex() const - { - return m_chunkIndex; - } - - Span PRMChunk::getBuffer() - { - return { m_buffer.get(), static_cast(m_bufferSize) }; - } - - PRMChunkRecognizedKind PRMChunk::getKind() const - { - return m_recognizedKind; - } - - const PRMDescriptionChunkBaseHeader* PRMChunk::getDescriptionBufferHeader() const - { - return std::get_if(&m_data); - } - - PRMDescriptionChunkBaseHeader* PRMChunk::getDescriptionBufferHeader() - { - return std::get_if(&m_data); - } - - const PRMIndexChunkHeader* PRMChunk::getIndexBufferHeader() const - { - return std::get_if(&m_data); - } - - PRMIndexChunkHeader* PRMChunk::getIndexBufferHeader() - { - return std::get_if(&m_data); - } - - const PRMVertexBufferHeader* PRMChunk::getVertexBufferHeader() const - { - return std::get_if(&m_data); - } - - PRMVertexBufferHeader* PRMChunk::getVertexBufferHeader() - { - return std::get_if(&m_data); - } - - void PRMChunk::recognizeChunkKindAndSaveData(Span chunk, int totalChunksNr) - { - // Description buffer - if (chunk.size() >= sizeof(PRMDescriptionChunkBaseHeader)) - { - auto binaryReader = ZBio::ZBinaryReader::BinaryReader(reinterpret_cast(&chunk[0]), chunk.size()); - - PRMDescriptionChunkBaseHeader chunkHdr; - PRMDescriptionChunkBaseHeader::deserialize(chunkHdr, &binaryReader); - - // Helpers - // PRM_IS_VALID_KIND - check for all known values - // PRM_IS_VALID_PACK_TYPE - check for all known pack types -#define PRM_IS_VALID_KIND(k) (k) == 0 || (k) == 1 || (k) == 4 || (k) == 6 || (k) == 7 || (k) == 8 || (k) == 10 || (k) == 11 || (k) == 12 -#define PRM_IS_VALID_PACK_TYPE(p) (p) == 0 - - if (chunkHdr.ptrObjects <= totalChunksNr && PRM_IS_VALID_KIND(chunkHdr.kind) && PRM_IS_VALID_PACK_TYPE(chunkHdr.primPackType) && chunkHdr.ptrParts < totalChunksNr && chunkHdr.ptrObjects < totalChunksNr) - { - m_recognizedKind = PRMChunkRecognizedKind::CRK_DESCRIPTION_BUFFER; - m_data.emplace(chunkHdr); // Copy data - return; - } - } - - // Index buffer - if (chunk.size() > 4 && (chunk.size() % 0x10) == 0) - { - // So, we need to check second two bytes - auto binaryReader = ZBio::ZBinaryReader::BinaryReader(reinterpret_cast(&chunk[0]), chunk.size()); - - PRMIndexChunkHeader chunkHdr {}; - PRMIndexChunkHeader::deserialize(chunkHdr, &binaryReader); - - if (chunkHdr.indicesCount <= ((chunk.size() - 4) / 2)) - { - m_recognizedKind = PRMChunkRecognizedKind::CRK_INDEX_BUFFER; - m_data.emplace(chunkHdr); - return; - } - } - - // Vertex buffer - if (auto chunkSize = chunk.size(); (chunkSize % 0x10) == 0 || (chunkSize % 0x24) == 0 || (chunkSize % 0x28) == 0 || (chunkSize % 0x34) == 0) - { - PRMVertexBufferHeader vertexBufferHeader; - vertexBufferHeader.vertexFormat = PRMVertexBufferFormat::VBF_UNKNOWN_VERTEX; - - if ((chunkSize % 0x28) == 0) - { - auto binaryReader = ZBio::ZBinaryReader::BinaryReader(reinterpret_cast(&chunk[0x24]), chunk.size()); - const auto b28 = binaryReader.read(); - const bool is28k = (b28 == 0xCDCDCDCDu); - if (!is28k) - { - m_recognizedKind = PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER; - } - else - { - vertexBufferHeader.vertexFormat = PRMVertexBufferFormat::VBF_VERTEX_28; - m_recognizedKind = PRMChunkRecognizedKind::CRK_VERTEX_BUFFER; - } - } - - if (m_recognizedKind == PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER) - { - if ((chunkSize % 0x24) == 0) - { - vertexBufferHeader.vertexFormat = PRMVertexBufferFormat::VBF_VERTEX_24; - } - else if ((chunkSize % 0x34) == 0) - { - vertexBufferHeader.vertexFormat = PRMVertexBufferFormat::VBF_VERTEX_34; - } - else if ((chunkSize % 0x10) == 0) - { - vertexBufferHeader.vertexFormat = PRMVertexBufferFormat::VBF_SIMPLE_VERTEX; - } - } - - m_recognizedKind = PRMChunkRecognizedKind::CRK_VERTEX_BUFFER; - m_data.emplace(vertexBufferHeader); - return; - } - - // Bone description - if (auto chunkSize = chunk.size(); (chunkSize % 0x40) == 0) - { - assert(false && "Unsupported thing"); - m_recognizedKind = PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER; - m_data.emplace(); - return; - } -#undef PRM_IS_VALID_KIND -#undef PRM_IS_VALID_PACK_TYPE - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMChunkDescriptor.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMChunkDescriptor.cpp deleted file mode 100644 index e6b09fb..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMChunkDescriptor.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - - -namespace gamelib::prm -{ - void PRMChunkDescriptor::deserialize(PRMChunkDescriptor &descriptor, ZBio::ZBinaryReader::BinaryReader *binaryReader) - { - auto &[offset, size, kind, unkC] = descriptor; - - offset = binaryReader->read(); - size = binaryReader->read(); - kind = binaryReader->read(); - unkC = binaryReader->read(); - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMDescriptionChunkBaseHeader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMDescriptionChunkBaseHeader.cpp deleted file mode 100644 index cbe2fd5..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMDescriptionChunkBaseHeader.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - - -using namespace gamelib::prm; - -void PRMDescriptionChunkBaseHeader::deserialize(gamelib::prm::PRMDescriptionChunkBaseHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader) -{ - header.boneDeclOffset = binaryReader->read(); - header.primPackType = binaryReader->read(); - header.kind = binaryReader->read(); - header.textureId = binaryReader->read(); - header.unk6 = binaryReader->read(); - header.nextVariation = binaryReader->read(); - header.unkC = binaryReader->read(); - header.unkD = binaryReader->read(); - header.unkE = binaryReader->read(); - header.currentVariation = binaryReader->read(); - header.ptrParts = binaryReader->read(); - header.materialIdx = binaryReader->read(); - header.totalVariations = binaryReader->read(); - header.ptrObjects = binaryReader->read(); - header.ptrObjects_HI = binaryReader->read(); - header.unk3 = binaryReader->read(); - header.boundingBox.min.x = binaryReader->read(); - header.boundingBox.min.y = binaryReader->read(); - header.boundingBox.min.z = binaryReader->read(); - header.boundingBox.max.x = binaryReader->read(); - header.boundingBox.max.y = binaryReader->read(); - header.boundingBox.max.z = binaryReader->read(); -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp new file mode 100644 index 0000000..b7d0a3f --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -0,0 +1,231 @@ +#include +#include +#include + + +namespace gamelib::prm +{ + void Index::deserialize(Index& index, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + index.a = binaryReader->read(); + index.b = binaryReader->read(); + index.c = binaryReader->read(); + } + + void Mesh::deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile) + { + // Skip header? + binaryReader->seek(0xE); + + mesh.lod = binaryReader->read(); + + if ((mesh.lod & static_cast(1)) == static_cast(1)) + { + // Seed another 3 bytes? + ZBioHelpers::seekBy(binaryReader, 0x3); + + // Read material id + mesh.material_id = binaryReader->read(); + + // Another seek + ZBioHelpers::seekBy(binaryReader, 0x14); + + uint32_t meshDescriptionChunk = 0; + + { + uint32_t meshDescriptionPointerChunk = 0; + + // Read description chunk index + meshDescriptionPointerChunk = binaryReader->read(); + + if (meshDescriptionPointerChunk >= prmFile.chunks.size()) + { + // Invalid chunk? Or not? + // TODO: Weird case, need investigate it later + return; + } + + // Read description + ZBio::ZBinaryReader::BinaryReader modelDescriptionReader { + reinterpret_cast(prmFile.chunks[meshDescriptionPointerChunk].data.get()), + static_cast(prmFile.entries[meshDescriptionPointerChunk].size) + }; + + // Instead of meshDescriptionPointerChunk this value pointed by meshDescriptionPointerChunk (another IOI shit code, who cares?) + meshDescriptionChunk = modelDescriptionReader.read(); + + if (meshDescriptionChunk >= prmFile.chunks.size()) + { + // Invalid chunk? Invalid wtf? IOI!!111111 + return; + } + } + + // Now we almost ready to read model description (rly?) + ZBio::ZBinaryReader::BinaryReader meshDescriptionReader { + reinterpret_cast(prmFile.chunks[meshDescriptionChunk].data.get()), + static_cast(prmFile.entries[meshDescriptionChunk].size) + }; + + uint32_t vertexCount = 0, vertexChunk = 0, trianglesChunk = 0; + + vertexCount = meshDescriptionReader.read(); + vertexChunk = meshDescriptionReader.read(); + + // Another seek (rly?) + ZBioHelpers::seekBy(&meshDescriptionReader, 0x4); + + trianglesChunk = meshDescriptionReader.read(); + + // Check that we have something valid here + if (vertexCount != 0 && vertexChunk != 0 && trianglesChunk != 0) + { + // And another one reader + ZBio::ZBinaryReader::BinaryReader trianglesReader { + reinterpret_cast(prmFile.chunks[trianglesChunk].data.get()), + static_cast(prmFile.entries[trianglesChunk].size) + }; + + // Skip first 2 bytes + ZBioHelpers::seekBy(&trianglesReader, 0x2); + + uint16_t trianglesCount = 0; + trianglesCount = trianglesReader.read(); + + // Magic (rly?) + uint32_t vertexSize = 0; + vertexSize = static_cast(prmFile.entries[vertexChunk].size / vertexCount); + vertexSize -= vertexSize % 4; + + if (vertexSize != 0x28 && vertexSize % 0x28 == 0) + { + vertexCount *= vertexSize / 0x28; + vertexSize = 0x28; + } + + // And vertex reader + ZBio::ZBinaryReader::BinaryReader vertexReader { + reinterpret_cast(prmFile.chunks[vertexChunk].data.get()), + static_cast(prmFile.entries[vertexChunk].size) + }; + + enum VertexFormat : uint32_t { + VF_10 = 0x10, + VF_24 = 0x24, + VF_28 = 0x28, + VF_34 = 0x34 + }; + + assert(vertexSize == 0x10 || vertexSize == 0x24 || vertexSize == 0x28 || vertexSize == 0x34); + if (vertexSize == 0x10 || vertexSize == 0x24 || vertexSize == 0x28 || vertexSize == 0x34) + { + VertexFormat format = static_cast(vertexSize); // NOLINT(*-use-auto) + + switch (format) + { + case VF_10: + { + for (uint32_t j = 0; j < vertexCount; j++) + { + glm::vec3& vertex = mesh.vertices.emplace_back(); + vertex.x = vertexReader.read(); + vertex.y = vertexReader.read(); + vertex.z = vertexReader.read(); + + // Skip 4 bytes (it's some sort of data, but ignored by us) + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0x4); + } + } + break; + case VF_24: + { + for (uint32_t j = 0; j < vertexCount; j++) + { + glm::vec3& vertex = mesh.vertices.emplace_back(); + vertex.x = vertexReader.read(); + vertex.y = vertexReader.read(); + vertex.z = vertexReader.read(); + + // Skip another 0x10 useful info + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0x10); + + // Read UVs + glm::vec2& uv = mesh.uvs.emplace_back(); + uv.x = vertexReader.read(); + uv.y = 1.f - vertexReader.read(); + } + } + break; + case VF_28: + { + for (uint32_t j = 0; j < vertexCount; j++) + { + glm::vec3& vertex = mesh.vertices.emplace_back(); + vertex.x = vertexReader.read(); + vertex.y = vertexReader.read(); + vertex.z = vertexReader.read(); + + // Skip another 0x10 useful info + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0x8); + + // Read UVs + glm::vec2& uv = mesh.uvs.emplace_back(); + uv.x = vertexReader.read(); + uv.y = 1.f - vertexReader.read(); + + // Another seek + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0xC); + } + } + break; + case VF_34: + { + glm::vec3& vertex = mesh.vertices.emplace_back(); + vertex.x = vertexReader.read(); + vertex.y = vertexReader.read(); + vertex.z = vertexReader.read(); + + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0x18); + + glm::vec2& uv = mesh.uvs.emplace_back(); + uv.x = vertexReader.read(); + uv.y = 1.f - vertexReader.read(); + + // TODO: Fix this + ZBioHelpers::seekBy(&vertexReader, 0x8); + } + break; + } + + // Store triangle indices + for (uint32_t j = 0; j < trianglesCount / 3; j++) + { + prm::Index& index = mesh.indices.emplace_back(); + prm::Index::deserialize(index, &trianglesReader); + } + } + } + } + } + + void Entry::deserialize(Entry& entry, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + entry.offset = binaryReader->read(); + entry.size = binaryReader->read(); + entry.type = binaryReader->read(); + entry.pad = binaryReader->read(); + } + + void Header::deserialize(Header& header, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + header.table_offset = binaryReader->read(); + header.table_count = binaryReader->read(); + header.table_offset2 = binaryReader->read(); + header.zeroed = binaryReader->read(); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMHeader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMHeader.cpp deleted file mode 100644 index ebc5ea7..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMHeader.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include - - -namespace gamelib::prm -{ - void PRMHeader::deserialize(gamelib::prm::PRMHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader) - { - header.chunkOffset = binaryReader->read(); - header.countOfPrimitives = binaryReader->read(); - header.chunkOffset2 = binaryReader->read(); - header.zeroed = binaryReader->read(); - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMIndexChunkHeader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMIndexChunkHeader.cpp deleted file mode 100644 index 08ce83b..0000000 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMIndexChunkHeader.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - - -namespace gamelib::prm -{ - void PRMIndexChunkHeader::deserialize(gamelib::prm::PRMIndexChunkHeader &header, ZBio::ZBinaryReader::BinaryReader *binaryReader) - { - header.unk0 = binaryReader->read(); - header.indicesCount = binaryReader->read(); - } -} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp index 0269e5e..089fc13 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp @@ -1,109 +1,111 @@ -#include -#include -#include -#include #include -#include -#include - +#include #include -namespace gamelib::prm +namespace gamelib { - constexpr std::size_t kMaxChunksPerFile = 40960; // There are 40960 geoms max - - PRMReader::PRMReader(gamelib::prm::PRMHeader &header, std::vector &chunkDescriptors, std::vector &chunks) - : m_header(header) - , m_chunkDescriptors(chunkDescriptors) - , m_chunks(chunks) - { - } + PRMReader::PRMReader() = default; - bool PRMReader::read(Span buffer) + bool PRMReader::parse(const std::uint8_t *pBuffer, std::size_t iBufferSize) { - if (!buffer) - { + if (!pBuffer || !iBufferSize) return false; - } - ZBio::ZBinaryReader::BinaryReader binaryReader(reinterpret_cast(buffer.data()), buffer.size()); + // Initialize global reader + ZBio::ZBinaryReader::BinaryReader reader { reinterpret_cast(pBuffer), static_cast(iBufferSize) }; // Read header - PRMHeader::deserialize(m_header, &binaryReader); - if (m_header.zeroed != 0x0) - { - throw PRMBadFile("Zeroed field must be zeroed!"); - } + prm::Header::deserialize(m_file.header, &reader); - if (m_header.countOfPrimitives >= kMaxChunksPerFile) + // Seek & read entries { - throw PRMBadFile("Possibly invalid PRM file. Game supports max 40959 unique primitives per level"); - } + ZBio::ZBinaryReader::BinaryReader entriesReader { reinterpret_cast(pBuffer), static_cast(iBufferSize) }; + entriesReader.seek(m_file.header.table_offset); + m_file.entries.reserve(m_file.header.table_count); // actually, it's not a table count, it's amount of chunks. - m_chunks.reserve(m_header.countOfPrimitives); - m_chunkDescriptors.reserve(m_header.countOfPrimitives); + for (int i = 0; i < m_file.header.table_count; i++) + { + prm::Entry& entry = m_file.entries.emplace_back(); + prm::Entry::deserialize(entry, &entriesReader); + } - for (std::uint32_t chunkIndex = 0u; chunkIndex < m_header.countOfPrimitives; ++chunkIndex) + m_file.chunks.reserve(m_file.header.table_count); + } + + // Prepare chunks { - if (m_header.chunkOffset + (chunkIndex * PRMChunkDescriptor::kDescriptorSize) >= buffer.size()) + for (int i = 0; i < m_file.header.table_count; i++) { - throw PRMBadChunkException(chunkIndex); - } + ZBio::ZBinaryReader::BinaryReader chunksReader { reinterpret_cast(pBuffer), static_cast(iBufferSize) }; + chunksReader.seek(m_file.entries[i].offset); - // Read chunk descriptor - binaryReader.seek(m_header.chunkOffset + (chunkIndex * PRMChunkDescriptor::kDescriptorSize)); - auto &descriptor = m_chunkDescriptors.emplace_back(); - PRMChunkDescriptor::deserialize(descriptor, &binaryReader); + prm::Chunk& chunk = m_file.chunks.emplace_back(); + chunk.data = std::make_unique(m_file.entries[i].size); - // Read chunk - auto chunkBufferSize = descriptor.declarationSize; - auto chunkBuffer = std::make_unique(chunkBufferSize); + // TODO: Need to check that data is valid (malloc is ok) + chunksReader.read(&chunk.data[0], m_file.entries[i].size); - { - BinaryReaderSeekScope scope { &binaryReader }; + // TODO: Need to refactor and use former header for chunk buffer instead of cropping few bytes (will fix later) + // TODO: Need to use proper way to read bytes (endianness) + if (m_file.entries[i].size == 0x40 && *reinterpret_cast(chunk.data.get()) == 0x70100) + { + chunk.is_model = true; + chunk.model = static_cast(m_file.models.size()); - binaryReader.seek(descriptor.declarationOffset); - binaryReader.read(&chunkBuffer[0], static_cast(chunkBufferSize)); + prm::Model& newMdl = m_file.models.emplace_back(); + newMdl.chunk = i; + } } - - m_chunks.emplace_back(chunkIndex, m_header.countOfPrimitives, std::move(chunkBuffer), chunkBufferSize); } - int unrecognizedChunks = 0; - for (const auto& chk: m_chunks) + // Read models { - if (chk.getKind() == PRMChunkRecognizedKind::CRK_UNKNOWN_BUFFER) + for (prm::Model& model : m_file.models) { - unrecognizedChunks++; + ZBio::ZBinaryReader::BinaryReader modelReader { + reinterpret_cast(m_file.chunks[model.chunk].data.get()), + static_cast(m_file.entries[model.chunk].size) + }; + + modelReader.seek(0x14); + uint32_t meshCount = 0, meshTable = 0; + + meshCount = modelReader.read(); + meshTable = modelReader.read(); + + // Read mesh table + ZBio::ZBinaryReader::BinaryReader meshTableReader { + reinterpret_cast(m_file.chunks[meshTable].data.get()), + static_cast(m_file.entries[meshTable].size) + }; + + for (uint32_t i = 0; i < meshCount; i++) + { + // Read mesh chunk index + uint32_t currentMeshChunkIdx = meshTableReader.read(); // NOLINT(*-use-auto) + + // And read mesh itself + ZBio::ZBinaryReader::BinaryReader meshEntryReader { + reinterpret_cast(m_file.chunks[currentMeshChunkIdx].data.get()), + static_cast(m_file.entries[currentMeshChunkIdx].size) + }; + + // Read mesh + prm::Mesh currentMesh {}; + prm::Mesh::deserialize(currentMesh, &meshEntryReader, m_file); + + // Save mesh + model.meshes.emplace_back(std::move(currentMesh)); + } } } - if (unrecognizedChunks > 0) - { - throw PRMBadFile("Found at least 1 unrecognized chunk. Need to check level by devs"); - } - return true; } - const PRMHeader &PRMReader::getHeader() const - { - return m_header; - } - - const std::vector &PRMReader::getChunkDescriptors() const - { - return m_chunkDescriptors; - } - - PRMChunk* PRMReader::getChunkAt(size_t chunkIndex) - { - return chunkIndex >= m_chunks.size() ? nullptr : &m_chunks[chunkIndex]; - } - - const PRMChunk* PRMReader::getChunkAt(size_t chunkIndex) const + prm::PrmFile&& PRMReader::takePrimitives() { - return chunkIndex >= m_chunks.size() ? nullptr : &m_chunks[chunkIndex]; + return std::move(m_file); } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a06246..3a33180 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ find_package(libsquish REQUIRED HINTS ${CMAKE_BINARY_DIR}) # --- Global dependencies add_subdirectory(ThirdParty/fmt) +add_subdirectory(ThirdParty/glm) # --- Project modules add_subdirectory(BMEdit/Editor) diff --git a/ThirdParty/glm b/ThirdParty/glm new file mode 160000 index 0000000..bf71a83 --- /dev/null +++ b/ThirdParty/glm @@ -0,0 +1 @@ +Subproject commit bf71a834948186f4097caa076cd2663c69a10e1e From 4afa017f59ec4c58b3ac33127cbc8bb95f472dde Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 7 Oct 2023 20:32:16 +0300 Subject: [PATCH 08/80] Trying to draw something... Need to complete MAT support to draw with correct textures --- .../Include/Widgets/SceneRenderWidget.h | 223 +++++ .../Source/Widgets/SceneRenderWidget.cpp | 923 ++++++++++++++++++ BMEdit/Editor/UI/Include/BMEditMainWindow.h | 2 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 37 +- BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 11 +- .../GameLib/Include/GameLib/PRM/PRMEntries.h | 10 + .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 21 +- 7 files changed, 1224 insertions(+), 3 deletions(-) create mode 100644 BMEdit/Editor/Include/Widgets/SceneRenderWidget.h create mode 100644 BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h new file mode 100644 index 0000000..960e835 --- /dev/null +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -0,0 +1,223 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace renderer +{ + // Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods + enum Camera_Movement { + FORWARD, + BACKWARD, + LEFT, + RIGHT + }; + + // Default camera values + const float YAW = -90.0f; + const float PITCH = 0.0f; + const float SPEED = 2.5f; + const float SENSITIVITY = 0.1f; + const float ZOOM = 45.0f; + + /** + * @credits https://learnopengl.com/Getting-started/Camera + */ + class Camera + { + public: + // camera Attributes + glm::vec3 Position; + glm::vec3 Front; + glm::vec3 Up; + glm::vec3 Right; + glm::vec3 WorldUp; + // euler Angles + float Yaw; + float Pitch; + // camera options + float MovementSpeed; + float MouseSensitivity; + float Zoom; + + // constructor with vectors + Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + { + Position = position; + WorldUp = up; + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); + } + + // constructor with scalar values + Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + { + Position = glm::vec3(posX, posY, posZ); + WorldUp = glm::vec3(upX, upY, upZ); + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); + } + + void setPosition(const glm::vec3& position) + { + Position = position; + updateCameraVectors(); + } + + // returns the view matrix calculated using Euler Angles and the LookAt Matrix + glm::mat4 getViewMatrix() + { + return glm::lookAt(Position, Position + Front, Up); + } + + // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) + void processKeyboard(Camera_Movement direction, float deltaTime, float moveScale = 1.f) + { + float velocity = MovementSpeed * deltaTime * moveScale; + if (direction == FORWARD) + Position += Front * velocity; + if (direction == BACKWARD) + Position -= Front * velocity; + if (direction == LEFT) + Position -= Right * velocity; + if (direction == RIGHT) + Position += Right * velocity; + } + + // processes input received from a mouse input system. Expects the offset value in both the x and y direction. + void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) + { + xoffset *= MouseSensitivity; + yoffset *= MouseSensitivity; + + Yaw += xoffset; + Pitch += yoffset; + + // make sure that when pitch is out of bounds, screen doesn't get flipped + if (constrainPitch) + { + if (Pitch > 89.0f) + Pitch = 89.0f; + if (Pitch < -89.0f) + Pitch = -89.0f; + } + + // update Front, Right and Up Vectors using the updated Euler angles + updateCameraVectors(); + } + + // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis + void processMouseScroll(float yoffset) + { + Zoom -= (float)yoffset; + if (Zoom < 1.0f) + Zoom = 1.0f; + if (Zoom > 45.0f) + Zoom = 45.0f; + } + + private: + // calculates the front vector from the Camera's (updated) Euler Angles + void updateCameraVectors() + { + // calculate the new Front vector + glm::vec3 front; + front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + front.y = sin(glm::radians(Pitch)); + front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + Front = glm::normalize(front); + // also re-calculate the Right and Up vector + Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. + Up = glm::normalize(glm::cross(Right, Front)); + } + }; +} + +class QOpenGLFunctions_3_3_Core; + +namespace widgets +{ + class SceneRenderWidget : public QOpenGLWidget + { + Q_OBJECT + public: + SceneRenderWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + ~SceneRenderWidget() noexcept override; + + void setLevel(gamelib::Level* pLevel); + void resetLevel(); + + [[nodiscard]] renderer::Camera& getCamera() { return m_camera; } + [[nodiscard]] const renderer::Camera& getCamera() const { return m_camera; } + + [[nodiscard]] float getFOV() const { return m_fFOV; } + void setFOV(float fov) { m_fFOV = fov; m_bDirtyProj = true; } + + void moveCameraTo(const glm::vec3& position); + + signals: + void resourcesReady(); + void resourceLoadFailed(const QString& reason); + + protected: + void initializeGL() override; + void paintGL() override; + void resizeGL(int w, int h) override; + + void keyPressEvent(QKeyEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + + private: + void updateProjectionMatrix(int w, int h); + void doRenderScene(QOpenGLFunctions_3_3_Core* glFunctions); + void doLoadTextures(QOpenGLFunctions_3_3_Core* glFunctions); + void doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions); + void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); + void doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions); + void doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject::Ptr& geom); + void discardResources(QOpenGLFunctions_3_3_Core* glFunctions); + void acquireMouseCapture(); + void releaseMouseCapture(); + + private: + gamelib::Level* m_pLevel { nullptr }; + renderer::Camera m_camera {}; + glm::mat4 m_matProjection {}; + float m_fFOV { 67.664f }; + float m_fZNear { .1f }; + float m_fZFar { 1000.f }; + bool m_bDirtyProj { true }; + + enum class ELevelState : uint8_t + { + LS_NONE = 0, + LS_LOAD_TEXTURES = 1, + LS_LOAD_GEOMETRY = 2, + LS_COMPILE_SHADERS = 3, + LS_RESET_CAMERA_STATE = 4, + LS_READY + }; + + ELevelState m_eState { ELevelState::LS_NONE }; + QPoint m_mouseLastPosition {}; + bool m_bFirstMouseQuery { true }; + + struct GLResources; + std::unique_ptr m_resources; + }; +} diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp new file mode 100644 index 0000000..b76bda5 --- /dev/null +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -0,0 +1,923 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace widgets +{ + struct SceneRenderWidget::GLResources + { + static constexpr GLuint kInvalidResource = 0xFDEADC0D; + + struct Mesh + { + GLuint vao { kInvalidResource }; + GLuint vbo { kInvalidResource }; + GLuint ibo { kInvalidResource }; + size_t textureId { 0 }; + int trianglesCount { 0 }; + + void discard(QOpenGLFunctions_3_3_Core* gapi) + { + if (vao != kInvalidResource) + { + gapi->glDeleteVertexArrays(1, &vao); + vao = kInvalidResource; + } + + if (vbo != kInvalidResource) + { + gapi->glDeleteBuffers(1, &vbo); + vbo = kInvalidResource; + } + + if (ibo != kInvalidResource) + { + gapi->glDeleteBuffers(1, &ibo); + ibo = kInvalidResource; + } + } + }; + + struct Model + { + std::vector meshes {}; + std::uint32_t chunkId { 0 }; + + void discardAll(QOpenGLFunctions_3_3_Core* gapi) + { + for (auto& mesh : meshes) + { + mesh.discard(gapi); + } + + meshes.clear(); + } + }; + + struct Texture + { + uint16_t width { 0 }; + uint16_t height { 0 }; + GLuint texture { kInvalidResource }; + + void discard(QOpenGLFunctions_3_3_Core* gapi) + { + width = height = 0; + + if (texture != kInvalidResource) + { + gapi->glDeleteTextures(1, &texture); + } + } + }; + + struct Shader + { + GLuint vertexProgramId { kInvalidResource }; + GLuint fragmentProgramId { kInvalidResource }; + GLuint programId { kInvalidResource }; + + Shader() = default; + + void discard(QOpenGLFunctions_3_3_Core* gapi) + { + if (vertexProgramId != kInvalidResource) + { + gapi->glDeleteShader(vertexProgramId); + vertexProgramId = kInvalidResource; + } + + if (fragmentProgramId != kInvalidResource) + { + gapi->glDeleteShader(fragmentProgramId); + fragmentProgramId = kInvalidResource; + } + + if (programId != kInvalidResource) + { + gapi->glDeleteShader(programId); + programId = kInvalidResource; + } + } + + void bind(QOpenGLFunctions_3_3_Core* gapi) + { + if (programId != kInvalidResource) + { + gapi->glUseProgram(programId); + } + } + + void unbind(QOpenGLFunctions_3_3_Core* gapi) + { + gapi->glUseProgram(0); + } + + bool compile(QOpenGLFunctions_3_3_Core* gapi, const std::string& vertexProgram, const std::string& fragmentProgram, std::string& error) + { + // Allocate root program + programId = gapi->glCreateProgram(); + vertexProgramId = gapi->glCreateShader(GL_VERTEX_SHADER); + fragmentProgramId = gapi->glCreateShader(GL_FRAGMENT_SHADER); + + // Compile vertex program + if (!compileUnit(gapi, vertexProgramId, GL_VERTEX_SHADER, vertexProgram, error)) + { + qDebug() << "Failed to compile vertex shader program"; + assert(false && "Failed to compile vertex shader program"); + return false; + } + + gapi->glAttachShader(programId, vertexProgramId); + + // Compile fragment program + if (!compileUnit(gapi, fragmentProgramId, GL_FRAGMENT_SHADER, fragmentProgram, error)) + { + qDebug() << "Failed to compile vertex shader program"; + assert(false && "Failed to compile fragment shader program"); + return false; + } + + gapi->glAttachShader(programId, fragmentProgramId); + + // Linking + gapi->glLinkProgram(programId); + + // Check linking status + GLint linkingIsOK = GL_FALSE; + + gapi->glGetProgramiv(programId, GL_LINK_STATUS, &linkingIsOK); + if (linkingIsOK == GL_FALSE) + { + constexpr int kCompileLogSize = 512; + + char linkLog[kCompileLogSize] = { 0 }; + GLint length { 0 }; + + gapi->glGetProgramInfoLog(programId, kCompileLogSize, &length, &linkLog[0]); + error = std::string(&linkLog[0], length); + qDebug() << "Failed to link shader program: " << std::string_view(&linkLog[0], length); + + assert(false); + return false; + } + + // Done + return true; + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, float s) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform1f(location, s); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, std::int32_t s) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform1i(location, s); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec2& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform2fv(location, 1, glm::value_ptr(v)); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec3& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform3fv(location, 1, glm::value_ptr(v)); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec4& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform4fv(location, 1, glm::value_ptr(v)); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat3& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniformMatrix3fv(location, 1, GL_FALSE, glm::value_ptr(v)); + } + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat4& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(v)); + } + + GLint resolveLocation(QOpenGLFunctions_3_3_Core* gapi, const std::string& id) const + { + if (auto it = m_locationsCache.find(id); it == m_locationsCache.end()) + { + GLint result = gapi->glGetUniformLocation(programId, id.c_str()); + m_locationsCache[id] = result; + return result; + } + else + { + return it->second; + } + } + + private: + bool compileUnit(QOpenGLFunctions_3_3_Core* gapi, GLuint unitId, GLenum unitType, const std::string& unitSource, std::string& error) + { + const GLchar* glSrc = reinterpret_cast(unitSource.c_str()); + const GLint glLen = static_cast(unitSource.length()); + + gapi->glShaderSource(unitId, 1, &glSrc, &glLen); + gapi->glCompileShader(unitId); + + GLint isCompiled = 0; + gapi->glGetShaderiv(unitId, GL_COMPILE_STATUS, &isCompiled); + + if (isCompiled == GL_FALSE) + { + constexpr int kCompileLogSize = 512; + + char compileLog[kCompileLogSize] = { 0 }; + GLint length { 0 }; + + gapi->glGetShaderInfoLog(unitId, kCompileLogSize, &length, &compileLog[0]); + qDebug() << "Failed to compile shader program: " << std::string_view(&compileLog[0], length); + + error = std::string(&compileLog[0], length); + assert(false && "Failed to compile unit!"); + return false; + } + + return true; + } + + private: + mutable std::map m_locationsCache; + }; + + std::vector m_textures {}; + std::vector m_shaders {}; + std::vector m_models {}; + + GLResources() {} + ~GLResources() {} + + void discard(QOpenGLFunctions_3_3_Core* gapi) + { + { + for (auto& texture : m_textures) + { + texture.discard(gapi); + } + + m_textures.clear(); + } + + { + for (auto& shader : m_shaders) + { + shader.discard(gapi); + } + + m_shaders.clear(); + } + + { + for (auto& model : m_models) + { + model.discardAll(gapi); + } + + m_models.clear(); + } + } + + bool hasResources() const + { + return !m_textures.empty() || !m_shaders.empty() || !m_models.empty(); + } + }; + + struct GlacierVertex + { + glm::vec3 vPos {}; + glm::vec2 vUV {}; + }; + + SceneRenderWidget::SceneRenderWidget(QWidget *parent, Qt::WindowFlags f) : QOpenGLWidget(parent, f) + { + } + + SceneRenderWidget::~SceneRenderWidget() noexcept = default; + + void SceneRenderWidget::initializeGL() + { + QSurfaceFormat format; + format.setDepthBufferSize(24); + format.setStencilBufferSize(8); + format.setVersion(3, 3); + format.setProfile(QSurfaceFormat::CoreProfile); + setFormat(format); + + // Create resource holder + m_resources = std::make_unique(); + } + + void SceneRenderWidget::paintGL() + { + auto funcs = QOpenGLVersionFunctionsFactory::get(QOpenGLContext::currentContext()); + if (!funcs) { + qFatal("Could not obtain required OpenGL context version"); + return; + } + + funcs->glClear(GL_COLOR_BUFFER_BIT); + funcs->glClearColor(0.15f, 0.2f, 0.45f, 1.0f); + funcs->glEnable(GL_CULL_FACE); + funcs->glCullFace(GL_BACK); + + // Update projection + if (m_bDirtyProj) + { + updateProjectionMatrix(QWidget::width(), QWidget::height()); + } + + switch (m_eState) + { + case ELevelState::LS_NONE: + { + if (m_pLevel) { + m_eState = ELevelState::LS_LOAD_TEXTURES; + } else if (m_resources && m_resources->hasResources()) { + m_resources->discard(funcs); + } + } + break; + case ELevelState::LS_LOAD_TEXTURES: + { + doLoadTextures(funcs); + } + break; + case ELevelState::LS_LOAD_GEOMETRY: + { + doLoadGeometry(funcs); + } + break; + case ELevelState::LS_COMPILE_SHADERS: + { + doCompileShaders(funcs); + } + break; + case ELevelState::LS_RESET_CAMERA_STATE: + { + doResetCameraState(funcs); + } + break; + case ELevelState::LS_READY: + { + doRenderScene(funcs); + } + break; + } + } + + void SceneRenderWidget::resizeGL(int w, int h) + { + Q_UNUSED(w); + Q_UNUSED(h); + + // Will recalc on next frame + m_bDirtyProj = true; + } + + void SceneRenderWidget::keyPressEvent(QKeyEvent* event) + { + if (m_pLevel) + { + bool bMoved = false; + constexpr float kBaseDt = 1.f / 60.f; + constexpr float kSpeedUp = 100.f; + + if (event->key() == Qt::Key_W) + { + m_camera.processKeyboard(renderer::Camera_Movement::FORWARD, kBaseDt, kSpeedUp); + bMoved = true; + } + else if (event->key() == Qt::Key_S) + { + m_camera.processKeyboard(renderer::Camera_Movement::BACKWARD, kBaseDt, kSpeedUp); + bMoved = true; + } + else if (event->key() == Qt::Key_A) + { + m_camera.processKeyboard(renderer::Camera_Movement::LEFT, kBaseDt, kSpeedUp); + bMoved = true; + } + else if (event->key() == Qt::Key_D) + { + m_camera.processKeyboard(renderer::Camera_Movement::RIGHT, kBaseDt, kSpeedUp); + bMoved = true; + } + + if (bMoved) + repaint(); + } + + QOpenGLWidget::keyPressEvent(event); + } + + void SceneRenderWidget::mouseDoubleClickEvent(QMouseEvent* event) + { + QOpenGLWidget::mouseDoubleClickEvent(event); + } + + void SceneRenderWidget::mouseMoveEvent(QMouseEvent* event) + { + if (m_pLevel) + { + float xpos = event->pos().x(); + float ypos = event->pos().y(); + + if (m_bFirstMouseQuery) + { + m_mouseLastPosition = event->pos(); + m_bFirstMouseQuery = false; + return; + } + + float xoffset = xpos - m_mouseLastPosition.x(); + float yoffset = m_mouseLastPosition.y() - ypos; + + m_mouseLastPosition = event->pos(); + + // Update camera + qDebug() << "Mouse moved (" << xoffset << ";" << yoffset << ")"; + m_camera.processMouseMovement(xoffset, yoffset); + } + + repaint(); + QOpenGLWidget::mouseMoveEvent(event); + } + + void SceneRenderWidget::mousePressEvent(QMouseEvent* event) + { + QOpenGLWidget::mousePressEvent(event); + } + + void SceneRenderWidget::mouseReleaseEvent(QMouseEvent* event) + { + QOpenGLWidget::mouseReleaseEvent(event); + } + + void SceneRenderWidget::updateProjectionMatrix(int w, int h) + { + m_matProjection = glm::perspectiveFov(glm::radians(m_fFOV), static_cast(w), static_cast(h), m_fZNear, m_fZFar); + m_bDirtyProj = false; + } + + void SceneRenderWidget::setLevel(gamelib::Level *pLevel) + { + if (m_pLevel != pLevel) + { + m_eState = ELevelState::LS_NONE; + m_pLevel = pLevel; + m_bFirstMouseQuery = true; + } + } + + void SceneRenderWidget::resetLevel() + { + if (m_pLevel != nullptr) + { + m_pLevel = nullptr; + m_bFirstMouseQuery = true; + } + } + + void SceneRenderWidget::moveCameraTo(const glm::vec3& position) + { + if (!m_pLevel) + return; + + m_camera.setPosition(position); + repaint(); + } + +#define LEVEL_SAFE_CHECK() \ + if (!m_pLevel) \ + { \ + m_eState = ELevelState::LS_NONE; \ + if (m_resources) \ + { \ + m_resources->discard(glFunctions); \ + } \ + return; \ + } + + void SceneRenderWidget::doLoadTextures(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + // Do it at once + // TODO: Optimize and load "chunk by chunk" + for (const auto& texture : m_pLevel->getSceneTextures()->entries) + { + // TODO: Support mip levels here? + if (texture.m_mipLevels.empty()) + { + // create null texture + m_resources->m_textures.emplace_back(); + qWarning() << "Failed to load texture #" << texture.m_index << ". Reason: no mip levels (empty texture)"; + continue; + } + + // Ok, texture is ok - load it + GLResources::Texture newTexture; + + std::unique_ptr decompressedMemBlk = editor::TextureProcessor::decompressRGBA(texture, newTexture.width, newTexture.height, 0); // + if (!decompressedMemBlk) + { + m_resources->m_textures.emplace_back(); + qWarning() << "Failed to decompress texture #" << texture.m_index << " to RGBA sequence"; + continue; + } + + glFunctions->glGenTextures(1, &newTexture.texture); + glFunctions->glBindTexture(GL_TEXTURE_2D, newTexture.texture); + glFunctions->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTexture.width, newTexture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, decompressedMemBlk.get()); + + glFunctions->glGenerateMipmap(GL_TEXTURE_2D); + glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glFunctions->glBindTexture(GL_TEXTURE_2D, 0); + + m_resources->m_textures.emplace_back(newTexture); + } + + qDebug() << "All textures (" << m_pLevel->getSceneTextures()->entries.size() << ") are loaded and ready to be used"; + m_eState = ELevelState::LS_LOAD_GEOMETRY; + } + + void SceneRenderWidget::doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + int kDebugTextureTextureIndex = 0; + + // Select debug texture + static constexpr const char* kGlacierMissingTex = "_Glacier/Missing_01"; + static constexpr const char* kWorldColiTex = "_TEST/Worldcoli"; + + for (int i = 0; i < m_pLevel->getSceneTextures()->entries.size(); i++) + { + const gamelib::tex::TEXEntry& texture = m_pLevel->getSceneTextures()->entries[i]; + + if (texture.m_fileName.has_value() && (texture.m_fileName.value() == kGlacierMissingTex || texture.m_fileName.value() == kWorldColiTex)) + { + kDebugTextureTextureIndex = i; + break; + } + } + + // TODO: Optimize and load "chunk by chunk" + for (const auto& model : m_pLevel->getLevelGeometry()->primitives.models) + { + if (model.meshes.empty()) + { + // create null model + m_resources->m_models.emplace_back(); + qWarning() << "Failed to load model of chunk " << model.chunk << ". Reason: no meshes (empty model)"; + continue; + } + + GLResources::Model& glModel = m_resources->m_models.emplace_back(); + glModel.chunkId = model.chunk; + + int meshIdx = 0; + for (const auto& mesh : model.meshes) + { + if (mesh.vertices.empty()) + { + // create empty mesh + glModel.meshes.emplace_back(); + qWarning() << "Failed to load mesh #" << meshIdx << " of model at chunk " << model.chunk << ". Reason: no meshes (empty model)"; + ++meshIdx; + continue; + } + + // Convert vertices & indices to single memory chunk + std::vector vertices; + std::vector indices; + + vertices.resize(mesh.vertices.size()); + indices.reserve(mesh.indices.size() * 3); // each 'index' subject contains three values + + for (int i = 0; i < mesh.vertices.size(); i++) + { + vertices[i].vPos = mesh.vertices[i]; + + if (mesh.uvs.empty()) + { + vertices[i].vUV = glm::vec2(.0f); // TODO: Idk what I should do here... + } + else + { + vertices[i].vUV = mesh.uvs[i]; + } + } + + for (const auto& index : mesh.indices) + { + indices.emplace_back(index.a); + indices.emplace_back(index.b); + indices.emplace_back(index.c); + } + + // And upload it to GPU + GLResources::Mesh& glMesh = glModel.meshes.emplace_back(); + glMesh.trianglesCount = static_cast(mesh.indices.size() / 3); + + // Allocate VAO, VBO & IBO stuff + glFunctions->glGenVertexArrays(1, &glMesh.vao); + glFunctions->glGenBuffers(1, &glMesh.vbo); + if (!indices.empty()) + { + glFunctions->glGenBuffers(1, &glMesh.ibo); + } + + // Attach VAO + glFunctions->glBindVertexArray(glMesh.vao); + glFunctions->glBindBuffer(GL_ARRAY_BUFFER, glMesh.vbo); + + // Upload vertices + glFunctions->glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GlacierVertex), &vertices[0], GL_STATIC_DRAW); + + // Upload indices + if (!indices.empty()) + { + glFunctions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glMesh.ibo); + glFunctions->glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(std::uint16_t), &indices[0], GL_STATIC_DRAW); + } + + // Setup vertex format + glFunctions->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glFunctions->glEnableVertexAttribArray(0); + + glFunctions->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glFunctions->glEnableVertexAttribArray(1); + + // TODO: Precache texture id (color channel) + glMesh.textureId = kDebugTextureTextureIndex; // mesh.textureId != 0 ? mesh.textureId : 133; + + // Next mesh + ++meshIdx; + } + } + + qDebug() << "All models (" << m_pLevel->getLevelGeometry()->primitives.models.size() << ") are loaded & ready to use!"; + m_eState = ELevelState::LS_COMPILE_SHADERS; + } + + void SceneRenderWidget::doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + // TODO: In future we will use shaders from FS, but now I need to debug this stuff easier + static constexpr const char* kVertexShader = R"( +#version 330 core + +// Layout +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aUV; + +// Common +struct Camera +{ + mat4 projView; +}; + +struct Transform +{ + mat4 model; +}; + +// Uniforms +uniform Camera i_uCamera; +uniform Transform i_uTransform; + +// Out +out vec2 g_TexCoord; + +void main() +{ + gl_Position = i_uCamera.projView * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); + g_TexCoord = aUV; +} +)"; + + static constexpr const char* kFragmentShader = R"( +#version 330 core + +uniform sampler2D iActiveTexture; +in vec2 g_TexCoord; + +// Out +out vec4 o_FragColor; + +void main() +{ + o_FragColor = texture(iActiveTexture, g_TexCoord); +} +)"; + + // TODO: Compile shader + std::string compileError; + GLResources::Shader defaultShader; + if (!defaultShader.compile(glFunctions, kVertexShader, kFragmentShader, compileError)) + { + m_pLevel = nullptr; + m_eState = ELevelState::LS_NONE; + + emit resourceLoadFailed(QString("Failed to compile shaders: %1").arg(QString::fromStdString(compileError))); + return; + } + + m_resources->m_shaders.emplace_back(defaultShader); + + qDebug() << "Shaders (" << m_resources->m_shaders.size() << ") compiled and ready to use!"; + m_eState = ELevelState::LS_RESET_CAMERA_STATE; + } + + void SceneRenderWidget::doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + // Take scene and find first camera (ZCAMERA instance I guess) + glm::vec3 cameraPosition { .0f }; + + for (const auto& sceneObject : m_pLevel->getSceneObjects()) + { + if (sceneObject->getType()->getName() == "ZCAMERA") + { + // Nice, camera found! + cameraPosition.x = sceneObject->getProperties()["Position"][1].getOperand().get(); + cameraPosition.y = sceneObject->getProperties()["Position"][2].getOperand().get(); + cameraPosition.z = sceneObject->getProperties()["Position"][3].getOperand().get(); + break; + } + } + + m_camera.setPosition(cameraPosition); // TODO: Need teleport camera to player and put under player's head + + emit resourcesReady(); + + m_eState = ELevelState::LS_READY; // Done! + } + + void SceneRenderWidget::doRenderScene(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + // Out ROOT is always first object. Start tree hierarchy visit from ROOT + const gamelib::scene::SceneObject::Ptr& root = m_pLevel->getSceneObjects()[0]; + + doRenderGeom(glFunctions, root); + } + + void SceneRenderWidget::discardResources(QOpenGLFunctions_3_3_Core* glFunctions) + { + if (m_resources) + { + m_resources->discard(glFunctions); + } + + m_eState = ELevelState::LS_NONE; // Switch to none, everything is gone + } + + void propertyMatrixToGlmMatrix(const gamelib::Span& matrix, glm::mat4& result) + { + result = glm::mat4(1.f); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + // +1 cuz #0 - begin array opcode + result[i][j] = matrix[(1 + (j * 3 + i))].getOperand().get(); + } + } + } + + void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject::Ptr& geom) // NOLINT(*-no-recursion) + { + // First of all we need to take primitive id and visibillity flag + const std::int32_t primId = geom->getProperties()["PrimId"][0].getOperand().get(); + const bool bIsVisible = !geom->getProperties().hasProperty("Invisible") || geom->getProperties()["Invisible"][0].getOperand().get(); + const glm::vec3 vPosition = glm::vec3 { + geom->getProperties()["Position"][1].getOperand().get(), + geom->getProperties()["Position"][2].getOperand().get(), + geom->getProperties()["Position"][3].getOperand().get() + }; + + if (primId != 0) + { + // Do render? + if (!bIsVisible) + return; // Don't draw children of invisible ZSTDOBJ + + // Extract matrix from properties + glm::mat4 modelMatrix = glm::mat4(1.f); + modelMatrix = glm::translate(modelMatrix, vPosition); + //propertyMatrixToGlmMatrix(geom->getProperties()["Matrix"], modelMatrix); + + // And bind required resources + // TODO: Optimize me + const auto& models = m_resources->m_models; + auto modelIt = std::find_if(models.begin(), models.end(), + [&primId](const GLResources::Model& model) -> bool { + return model.chunkId == (primId - 1); + }); + + if (modelIt != models.end()) + { + const GLResources::Model& model = *modelIt; + + // Render all meshes + for (const auto& mesh : model.meshes) + { + // Render single mesh + // 1. Activate default shader + m_resources->m_shaders[0].bind(glFunctions); + + // 2. Submit uniforms + m_resources->m_shaders[0].setUniform(glFunctions, "i_uTransform.model", modelMatrix); + m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.projView", m_matProjection * m_camera.getViewMatrix()); + + // 3. Bind texture + glFunctions->glBindTexture(GL_TEXTURE_2D, m_resources->m_textures[mesh.textureId].texture); + + // 3. Activate VAO + glFunctions->glBindVertexArray(mesh.vao); + + // 4. Submit + if (mesh.ibo != GLResources::kInvalidResource) + { + // Draw indexed + glFunctions->glDrawElements(GL_TRIANGLES, mesh.trianglesCount, GL_UNSIGNED_SHORT, nullptr); + } + else + { + // Draw elements + glFunctions->glDrawArrays(GL_TRIANGLES, 0, mesh.trianglesCount); + } + + // ... End + glFunctions->glBindTexture(GL_TEXTURE_2D, 0); + m_resources->m_shaders[0].unbind(glFunctions); + } + } + // otherwise draw red bbox! + } + + // Render others + for (const auto& childRef : geom->getChildren()) + { + if (auto child = childRef.lock()) + { + doRenderGeom(glFunctions, child); + } + } + } +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 4d53f67..0445825 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -80,6 +80,8 @@ public slots: void onExportPRP(); void onShowTexturesDialog(); void onContextMenuRequestedForSceneTreeNode(const QPoint& point); + void onLevelAssetsLoaded(); + void onLevelAssetsLoadFailed(const QString& reason); private: // UI diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index e19c64e..8c65184 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -207,7 +207,7 @@ void BMEditMainWindow::onShowTypesViewer() void BMEditMainWindow::onLevelLoadSuccess() { auto currentLevel = editor::EditorInstance::getInstance().getActiveLevel(); - setWindowTitle(QString("BMEdit - %1").arg(QString::fromStdString(currentLevel->getLevelName()))); + setWindowTitle(QString("BMEdit - %1 [loading view...]").arg(QString::fromStdString(currentLevel->getLevelName()))); resetStatusToDefault(); @@ -234,6 +234,9 @@ void BMEditMainWindow::onLevelLoadSuccess() m_scenePropertiesModel->setLevel(const_cast(currentLevel)); } + // Show game scene (start loading process) + ui->sceneGLView->setLevel(const_cast(currentLevel)); + // Load controllers index ui->geomControllers->switchToDefaults(); @@ -343,6 +346,9 @@ void BMEditMainWindow::onCloseLevel() if (m_scenePropertiesModel) m_scenePropertiesModel->resetLevel(); if (m_sceneTexturesModel) m_sceneTexturesModel->resetLevel(); + // Unload resources + ui->sceneGLView->resetLevel(); + // Reset widget states ui->geomControllers->resetGeom(); @@ -424,16 +430,42 @@ void BMEditMainWindow::onContextMenuRequestedForSceneTreeNode(const QPoint& poin QGuiApplication::clipboard()->setText(finalPath); }; + auto implMoveCameraToGeom = [this](gamelib::scene::SceneObject* sceneObject) + { + const glm::vec3 vPosition { + sceneObject->getProperties()["Position"][1].getOperand().get(), + sceneObject->getProperties()["Position"][2].getOperand().get(), + sceneObject->getProperties()["Position"][3].getOperand().get() + }; + + ui->sceneGLView->moveCameraTo(vPosition); + }; + contextMenu.addAction(QString("Object: '%1'").arg(QString::fromStdString(selectedGeom->getName())))->setDisabled(true); contextMenu.addAction(QString("Type: '%1'").arg(QString::fromStdString(selectedGeom->getType()->getName())))->setDisabled(true); contextMenu.addSeparator(); contextMenu.addAction("Copy path", [implCopyPathToGeom, selectedGeom] { implCopyPathToGeom(selectedGeom, false); }); contextMenu.addAction("Copy path (ignore ROOT)", [implCopyPathToGeom, selectedGeom] { implCopyPathToGeom(selectedGeom, true); }); + contextMenu.addAction("Move camera to this object", [implMoveCameraToGeom, selectedGeom] { implMoveCameraToGeom(const_cast(selectedGeom)); }); contextMenu.exec(ui->sceneTreeView->viewport()->mapToGlobal(point)); } } +void BMEditMainWindow::onLevelAssetsLoaded() +{ + auto currentLevel = editor::EditorInstance::getInstance().getActiveLevel(); + setWindowTitle(QString("BMEdit - %1 [DONE]").arg(QString::fromStdString(currentLevel->getLevelName()))); +} + +void BMEditMainWindow::onLevelAssetsLoadFailed(const QString& reason) +{ + auto currentLevel = editor::EditorInstance::getInstance().getActiveLevel(); + setWindowTitle(QString("BMEdit - %1 [!!!ERROR!!!]").arg(QString::fromStdString(currentLevel->getLevelName()))); + + QMessageBox::critical(this, QString("Scene render failed :("), QString("An error occurred while loading scene assets:\n%1").arg(reason)); +} + void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); @@ -570,6 +602,9 @@ void BMEditMainWindow::initSceneTree() }); connect(ui->sceneTreeView, &QTreeView::customContextMenuRequested, this, &BMEditMainWindow::onContextMenuRequestedForSceneTreeNode); + + connect(ui->sceneGLView, &widgets::SceneRenderWidget::resourcesReady, this, &BMEditMainWindow::onLevelAssetsLoaded); + connect(ui->sceneGLView, &widgets::SceneRenderWidget::resourceLoadFailed, this, &BMEditMainWindow::onLevelAssetsLoadFailed); } void BMEditMainWindow::initProperties() diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index 599bab0..9e0ad4c 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -18,7 +18,11 @@ - + + + Qt::ClickFocus + + @@ -384,6 +388,11 @@ QTreeView
Widgets/SceneTreeView.h
+ + widgets::SceneRenderWidget + QOpenGLWidget +
Widgets/SceneRenderWidget.h
+
diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h index fdef7ee..2aa085a 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -31,8 +31,17 @@ namespace gamelib::prm static void deserialize(Index& index, ZBio::ZBinaryReader::BinaryReader* binaryReader); }; + struct BoundingBox + { + glm::vec3 vMin; + glm::vec3 vMax; + + static void deserialize(BoundingBox& boundingBox, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + struct Mesh { + uint16_t textureId = 0; uint8_t lod = 0; uint16_t material_id = 0; int32_t diffuse_id = 0; @@ -41,6 +50,7 @@ namespace gamelib::prm std::vector vertices {}; std::vector indices {}; std::vector uvs {}; + //BoundingBox boundingBox {}; static void deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile); }; diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index b7d0a3f..e65da1a 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -12,12 +12,31 @@ namespace gamelib::prm index.c = binaryReader->read(); } + void BoundingBox::deserialize(BoundingBox& boundingBox, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + boundingBox.vMin.x = binaryReader->read(); + boundingBox.vMin.y = binaryReader->read(); + boundingBox.vMin.z = binaryReader->read(); + boundingBox.vMax.x = binaryReader->read(); + boundingBox.vMax.y = binaryReader->read(); + boundingBox.vMax.z = binaryReader->read(); + } + void Mesh::deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile) { +#ifdef TRY_HARD // Skip header? + binaryReader->seek(0x4); + mesh.textureId = binaryReader->read(); + + binaryReader->seek(0xB); + //binaryReader->seek(0xE); + mesh.lod = binaryReader->read(); +#else binaryReader->seek(0xE); mesh.lod = binaryReader->read(); +#endif if ((mesh.lod & static_cast(1)) == static_cast(1)) { @@ -27,7 +46,7 @@ namespace gamelib::prm // Read material id mesh.material_id = binaryReader->read(); - // Another seek + // Jump next ZBioHelpers::seekBy(binaryReader, 0x14); uint32_t meshDescriptionChunk = 0; From 405550baab07a4664cbe276fa30de24351f25f41 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 7 Oct 2023 20:45:12 +0300 Subject: [PATCH 09/80] Try fix build --- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index b76bda5..0cc8143 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -163,7 +163,7 @@ namespace widgets gapi->glGetProgramInfoLog(programId, kCompileLogSize, &length, &linkLog[0]); error = std::string(&linkLog[0], length); - qDebug() << "Failed to link shader program: " << std::string_view(&linkLog[0], length); + qDebug() << "Failed to link shader program: " << std::string(&linkLog[0], length); assert(false); return false; @@ -270,7 +270,7 @@ namespace widgets GLint length { 0 }; gapi->glGetShaderInfoLog(unitId, kCompileLogSize, &length, &compileLog[0]); - qDebug() << "Failed to compile shader program: " << std::string_view(&compileLog[0], length); + qDebug() << "Failed to compile shader program: " << std::string(&compileLog[0], length); error = std::string(&compileLog[0], length); assert(false && "Failed to compile unit!"); From 482e5e88d5e0eae4ce8bb34ffbe36513bc367be7 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 7 Oct 2023 20:58:38 +0300 Subject: [PATCH 10/80] Another fix --- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0cc8143..3bbc4ca 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -162,8 +162,9 @@ namespace widgets GLint length { 0 }; gapi->glGetProgramInfoLog(programId, kCompileLogSize, &length, &linkLog[0]); + error = std::string(&linkLog[0], length); - qDebug() << "Failed to link shader program: " << std::string(&linkLog[0], length); + qDebug() << "Failed to link shader program: " << QString::fromStdString(error); assert(false); return false; @@ -270,9 +271,10 @@ namespace widgets GLint length { 0 }; gapi->glGetShaderInfoLog(unitId, kCompileLogSize, &length, &compileLog[0]); - qDebug() << "Failed to compile shader program: " << std::string(&compileLog[0], length); error = std::string(&compileLog[0], length); + qDebug() << "Failed to compile shader program: " << QString::fromStdString(error); + assert(false && "Failed to compile unit!"); return false; } From 2657c6513cb619c3451deaea34d767dc56d80534 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 8 Oct 2023 21:40:16 +0300 Subject: [PATCH 11/80] Fix MAT file reader implementation. Not tested properly! --- .../Source/Widgets/SceneRenderWidget.cpp | 197 ++++++++++----- BMEdit/GameLib/Include/GameLib/Level.h | 12 + BMEdit/GameLib/Include/GameLib/MAT/MAT.h | 9 + BMEdit/GameLib/Include/GameLib/MAT/MATBind.h | 34 +++ .../Include/GameLib/MAT/MATBlendMode.h | 19 ++ BMEdit/GameLib/Include/GameLib/MAT/MATClass.h | 26 ++ .../Include/GameLib/MAT/MATColorChannel.h | 36 +++ .../GameLib/Include/GameLib/MAT/MATCullMode.h | 14 ++ .../GameLib/Include/GameLib/MAT/MATEntries.h | 176 +++++++++++++ .../GameLib/Include/GameLib/MAT/MATInstance.h | 26 ++ BMEdit/GameLib/Include/GameLib/MAT/MATLayer.h | 36 +++ .../GameLib/Include/GameLib/MAT/MATOption.h | 31 +++ .../GameLib/Include/GameLib/MAT/MATReader.h | 41 +++ .../Include/GameLib/MAT/MATRenderState.h | 58 +++++ .../GameLib/Include/GameLib/MAT/MATSprite.h | 29 +++ .../GameLib/Include/GameLib/MAT/MATSubClass.h | 36 +++ .../GameLib/Include/GameLib/MAT/MATTexture.h | 36 +++ .../Include/GameLib/MAT/MATTilingMode.h | 11 + .../GameLib/Include/GameLib/PRM/PRMEntries.h | 7 + .../Include/GameLib/PRP/PRPMathTypes.h | 184 ++++++++++++++ .../Include/GameLib/PRP/PRPObjectExtractor.h | 22 ++ BMEdit/GameLib/Include/GameLib/Value.h | 16 ++ BMEdit/GameLib/Include/GameLib/ZBioHelpers.h | 32 +++ BMEdit/GameLib/Source/GameLib/Level.cpp | 40 +++ BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp | 59 +++++ .../GameLib/Source/GameLib/MAT/MATClass.cpp | 26 ++ .../Source/GameLib/MAT/MATColorChannel.cpp | 105 ++++++++ .../GameLib/Source/GameLib/MAT/MATEntries.cpp | 84 +++++++ .../Source/GameLib/MAT/MATInstance.cpp | 25 ++ .../GameLib/Source/GameLib/MAT/MATLayer.cpp | 75 ++++++ .../GameLib/Source/GameLib/MAT/MATOption.cpp | 59 +++++ .../GameLib/Source/GameLib/MAT/MATReader.cpp | 238 ++++++++++++++++++ .../Source/GameLib/MAT/MATRenderState.cpp | 126 ++++++++++ .../GameLib/Source/GameLib/MAT/MATSprite.cpp | 60 +++++ .../Source/GameLib/MAT/MATSubClass.cpp | 53 ++++ .../GameLib/Source/GameLib/MAT/MATTexture.cpp | 91 +++++++ .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 18 +- 37 files changed, 2067 insertions(+), 80 deletions(-) create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MAT.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATBind.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATBlendMode.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATClass.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATCullMode.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATInstance.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATLayer.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATOption.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATReader.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATSprite.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATSubClass.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h create mode 100644 BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h create mode 100644 BMEdit/GameLib/Include/GameLib/PRP/PRPObjectExtractor.h create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATClass.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATEntries.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATInstance.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 3bbc4ca..0cdfb47 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace widgets @@ -66,6 +68,7 @@ namespace widgets uint16_t width { 0 }; uint16_t height { 0 }; GLuint texture { kInvalidResource }; + std::optional index {}; /// Index of texture from TEX container void discard(QOpenGLFunctions_3_3_Core* gapi) { @@ -201,6 +204,15 @@ namespace widgets gapi->glUniform2fv(location, 1, glm::value_ptr(v)); } + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::ivec2& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform2iv(location, 1, glm::value_ptr(v)); + } + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec3& v) { GLint location = resolveLocation(gapi, id); @@ -289,6 +301,7 @@ namespace widgets std::vector m_textures {}; std::vector m_shaders {}; std::vector m_models {}; + std::uint32_t m_iDebugTextureIndex { 0 }; GLResources() {} ~GLResources() {} @@ -321,9 +334,11 @@ namespace widgets m_models.clear(); } + + m_iDebugTextureIndex = 0u; } - bool hasResources() const + [[nodiscard]] bool hasResources() const { return !m_textures.empty() || !m_shaders.empty() || !m_models.empty(); } @@ -362,16 +377,11 @@ namespace widgets return; } - funcs->glClear(GL_COLOR_BUFFER_BIT); + // Begin frame + funcs->glEnable(GL_DEPTH_TEST); + funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); funcs->glClearColor(0.15f, 0.2f, 0.45f, 1.0f); - funcs->glEnable(GL_CULL_FACE); - funcs->glCullFace(GL_BACK); - - // Update projection - if (m_bDirtyProj) - { - updateProjectionMatrix(QWidget::width(), QWidget::height()); - } + funcs->glDepthFunc(GL_ALWAYS); switch (m_eState) { @@ -573,6 +583,10 @@ namespace widgets continue; } + // Store texture index from TEX container + newTexture.index = std::make_optional(texture.m_index); + + // Create GL resource glFunctions->glGenTextures(1, &newTexture.texture); glFunctions->glBindTexture(GL_TEXTURE_2D, newTexture.texture); glFunctions->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTexture.width, newTexture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, decompressedMemBlk.get()); @@ -586,6 +600,15 @@ namespace widgets glFunctions->glBindTexture(GL_TEXTURE_2D, 0); m_resources->m_textures.emplace_back(newTexture); + + // Precache debug texture if it's not precached yet + static constexpr const char* kGlacierMissingTex = "_Glacier/Missing_01"; + static constexpr const char* kWorldColiTex = "_TEST/Worldcoli"; + + if (m_resources->m_iDebugTextureIndex == 0 && texture.m_fileName.has_value() && (texture.m_fileName.value() == kGlacierMissingTex || texture.m_fileName.value() == kWorldColiTex)) + { + m_resources->m_iDebugTextureIndex = static_cast(m_resources->m_textures.size() - 1); + } } qDebug() << "All textures (" << m_pLevel->getSceneTextures()->entries.size() << ") are loaded and ready to be used"; @@ -596,23 +619,6 @@ namespace widgets { LEVEL_SAFE_CHECK() - int kDebugTextureTextureIndex = 0; - - // Select debug texture - static constexpr const char* kGlacierMissingTex = "_Glacier/Missing_01"; - static constexpr const char* kWorldColiTex = "_TEST/Worldcoli"; - - for (int i = 0; i < m_pLevel->getSceneTextures()->entries.size(); i++) - { - const gamelib::tex::TEXEntry& texture = m_pLevel->getSceneTextures()->entries[i]; - - if (texture.m_fileName.has_value() && (texture.m_fileName.value() == kGlacierMissingTex || texture.m_fileName.value() == kWorldColiTex)) - { - kDebugTextureTextureIndex = i; - break; - } - } - // TODO: Optimize and load "chunk by chunk" for (const auto& model : m_pLevel->getLevelGeometry()->primitives.models) { @@ -700,8 +706,8 @@ namespace widgets glFunctions->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); glFunctions->glEnableVertexAttribArray(1); - // TODO: Precache texture id (color channel) - glMesh.textureId = kDebugTextureTextureIndex; // mesh.textureId != 0 ? mesh.textureId : 133; + // Precache color texture + glMesh.textureId = mesh.textureId != 0 ? mesh.textureId : m_resources->m_iDebugTextureIndex; //TODO: here we need to ask material first! // Next mesh ++meshIdx; @@ -727,7 +733,9 @@ layout (location = 1) in vec2 aUV; // Common struct Camera { - mat4 projView; + mat4 proj; + mat4 view; + ivec2 resolution; }; struct Transform @@ -744,7 +752,7 @@ out vec2 g_TexCoord; void main() { - gl_Position = i_uCamera.projView * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); + gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); g_TexCoord = aUV; } )"; @@ -752,7 +760,18 @@ void main() static constexpr const char* kFragmentShader = R"( #version 330 core -uniform sampler2D iActiveTexture; +vec4 decodeDebugId(int objectId) +{ + float r = float((objectId >> 24) & 0xFF) / 255.0; + float g = float((objectId >> 16) & 0xFF) / 255.0; + float b = float((objectId >> 8) & 0xFF) / 255.0; + float a = float(objectId & 0xFF) / 255.0; + + return vec4(r, g, b, a); +} + +uniform sampler2D i_ActiveTexture; +uniform int i_DebugObjectID; in vec2 g_TexCoord; // Out @@ -760,7 +779,8 @@ out vec4 o_FragColor; void main() { - o_FragColor = texture(iActiveTexture, g_TexCoord); + //o_FragColor = decodeDebugId(i_DebugObjectID); + o_FragColor = texture(i_ActiveTexture, g_TexCoord); } )"; @@ -791,12 +811,12 @@ void main() for (const auto& sceneObject : m_pLevel->getSceneObjects()) { + // Need to fix this code. Most 'ZCAMERA' objects refs to 2D scene view, need to find another better way to find 'start' camera. + // At least we can try to find ZPlayer/ZHitman3 object to put camera near to player if (sceneObject->getType()->getName() == "ZCAMERA") { // Nice, camera found! - cameraPosition.x = sceneObject->getProperties()["Position"][1].getOperand().get(); - cameraPosition.y = sceneObject->getProperties()["Position"][2].getOperand().get(); - cameraPosition.z = sceneObject->getProperties()["Position"][3].getOperand().get(); + cameraPosition = sceneObject->getProperties().getObject("Position", glm::vec3(.0f)); break; } } @@ -812,6 +832,12 @@ void main() { LEVEL_SAFE_CHECK() + // Update projection + if (m_bDirtyProj) + { + updateProjectionMatrix(QWidget::width(), QWidget::height()); + } + // Out ROOT is always first object. Start tree hierarchy visit from ROOT const gamelib::scene::SceneObject::Ptr& root = m_pLevel->getSceneObjects()[0]; @@ -828,41 +854,29 @@ void main() m_eState = ELevelState::LS_NONE; // Switch to none, everything is gone } - void propertyMatrixToGlmMatrix(const gamelib::Span& matrix, glm::mat4& result) - { - result = glm::mat4(1.f); - - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) - { - // +1 cuz #0 - begin array opcode - result[i][j] = matrix[(1 + (j * 3 + i))].getOperand().get(); - } - } - } - void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject::Ptr& geom) // NOLINT(*-no-recursion) { - // First of all we need to take primitive id and visibillity flag - const std::int32_t primId = geom->getProperties()["PrimId"][0].getOperand().get(); - const bool bIsVisible = !geom->getProperties().hasProperty("Invisible") || geom->getProperties()["Invisible"][0].getOperand().get(); - const glm::vec3 vPosition = glm::vec3 { - geom->getProperties()["Position"][1].getOperand().get(), - geom->getProperties()["Position"][2].getOperand().get(), - geom->getProperties()["Position"][3].getOperand().get() - }; + // Take params + const auto primId = geom->getProperties().getObject("PrimId", 0); + const bool bInvisible = geom->getProperties().getObject("Invisible", false); + const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); + const auto mMatrix = geom->getProperties().getObject("Matrix", glm::mat3(1.f)); + + // Don't draw invisible things + if (bInvisible) + return; + + // TODO: Check for culling here (object visible or not) + // Check that object could be rendered by any way if (primId != 0) { - // Do render? - if (!bIsVisible) - return; // Don't draw children of invisible ZSTDOBJ - // Extract matrix from properties - glm::mat4 modelMatrix = glm::mat4(1.f); - modelMatrix = glm::translate(modelMatrix, vPosition); - //propertyMatrixToGlmMatrix(geom->getProperties()["Matrix"], modelMatrix); + const glm::mat4 transformMatrix = glm::mat4(mMatrix); + const glm::mat4 translateMatrix = glm::translate(glm::mat4(1.f), vPosition); + + // Am I right here? + const glm::mat4 modelMatrix = translateMatrix * transformMatrix; // And bind required resources // TODO: Optimize me @@ -885,10 +899,55 @@ void main() // 2. Submit uniforms m_resources->m_shaders[0].setUniform(glFunctions, "i_uTransform.model", modelMatrix); - m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.projView", m_matProjection * m_camera.getViewMatrix()); + m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.proj", m_matProjection); + m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.view", m_camera.getViewMatrix()); + m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.resolution", glm::ivec2(QWidget::width(), QWidget::height())); + + // Encode debug object marker + { + // Here we have about 31 bits of data + int32_t debugMarker = 0; + + union UEncoder + { + int32_t i32_1{0}; + int16_t i16_2[2]; + } encoder; + + auto djb2Hash16 = [](const std::string& str) -> std::int16_t { + int16_t hash = 5381; // Initial hash value + + for (char c : str) { + hash = ((hash << static_cast(5)) + hash) ^ static_cast(c); + } + + return hash; + }; + + encoder.i16_2[0] = static_cast(model.chunkId); + encoder.i16_2[1] = djb2Hash16(geom->getName()); + + m_resources->m_shaders[0].setUniform(glFunctions, "i_DebugObjectID", encoder.i32_1); + } // 3. Bind texture - glFunctions->glBindTexture(GL_TEXTURE_2D, m_resources->m_textures[mesh.textureId].texture); + // TODO: Optimize me + { + const auto& allTextures = m_resources->m_textures; + auto textureIt = std::find_if(allTextures.begin(), allTextures.end(), [index = mesh.textureId](const GLResources::Texture& tex) -> bool { + return tex.index.has_value() && tex.index.value() == index; + }); + + if (textureIt != allTextures.end()) + { + glFunctions->glBindTexture(GL_TEXTURE_2D, textureIt->texture); + } + else + { + // Need bind 'error' texture + glFunctions->glBindTexture(GL_TEXTURE_2D, m_resources->m_textures[m_resources->m_iDebugTextureIndex].texture); + } + } // 3. Activate VAO glFunctions->glBindVertexArray(mesh.vao); @@ -897,7 +956,7 @@ void main() if (mesh.ibo != GLResources::kInvalidResource) { // Draw indexed - glFunctions->glDrawElements(GL_TRIANGLES, mesh.trianglesCount, GL_UNSIGNED_SHORT, nullptr); + glFunctions->glDrawElements(GL_TRIANGLES, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); } else { @@ -913,7 +972,7 @@ void main() // otherwise draw red bbox! } - // Render others + // Draw children for (const auto& childRef : geom->getChildren()) { if (auto child = childRef.lock()) diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 6c6b994..154d2e4 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,13 @@ namespace gamelib prm::PrmFile primitives; }; + struct LevelMaterials + { + mat::MATHeader header; + std::vector materialClasses; + std::vector materialInstances; + }; + class Level { public: @@ -56,6 +64,8 @@ namespace gamelib [[nodiscard]] LevelTextures* getSceneTextures(); [[nodiscard]] const LevelGeometry* getLevelGeometry() const; [[nodiscard]] LevelGeometry* getLevelGeometry(); + [[nodiscard]] const LevelMaterials* getLevelMaterials() const; + [[nodiscard]] LevelMaterials* getLevelMaterials(); [[nodiscard]] const std::vector &getSceneObjects() const; @@ -66,6 +76,7 @@ namespace gamelib bool loadLevelScene(); bool loadLevelPrimitives(); bool loadLevelTextures(); + bool loadLevelMaterials(); private: // Core @@ -77,6 +88,7 @@ namespace gamelib SceneProperties m_sceneProperties; LevelTextures m_levelTextures; LevelGeometry m_levelGeometry; + LevelMaterials m_levelMaterials; // Managed objects std::vector m_sceneObjects {}; diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MAT.h b/BMEdit/GameLib/Include/GameLib/MAT/MAT.h new file mode 100644 index 0000000..ff5ac77 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MAT.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +namespace gamelib::mat +{ +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h b/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h new file mode 100644 index 0000000..5321ed7 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + struct MATBind + { + MATBind(); + + static MATBind makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + // Collected things + std::string name {}; /// I assume, that we able to have no name or only 1 name... + std::vector renderStates; + std::vector textures; + std::vector colorChannels; + std::vector sprites; + /// ... another fields? + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATBlendMode.h b/BMEdit/GameLib/Include/GameLib/MAT/MATBlendMode.h new file mode 100644 index 0000000..5689c05 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATBlendMode.h @@ -0,0 +1,19 @@ +#pragma once + +#include + + +namespace gamelib::mat +{ + enum class MATBlendMode : uint8_t + { + BM_TRANS, + BM_TRANS_ON_OPAQUE, + BM_TRANSADD_ON_OPAQUE, + BM_ADD_BEFORE_TRANS, + BM_ADD_ON_OPAQUE, + BM_ADD, + BM_SHADOW, + BM_STATICSHADOW + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATClass.h b/BMEdit/GameLib/Include/GameLib/MAT/MATClass.h new file mode 100644 index 0000000..34dd8e0 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATClass.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + + +namespace gamelib::mat +{ + class MATClass + { + public: + MATClass(std::string clasName, std::string parentClass, std::vector&& properties, std::vector&& subClasses); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] const std::string& getParentClassName() const; + [[nodiscard]] const std::vector& getSubClasses() const; + + private: + std::string m_name; + std::string m_parentClass; + std::vector m_properties; + std::vector m_subClasses; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h b/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h new file mode 100644 index 0000000..bed0c89 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + using MATColorRGBA = std::variant; + + /** + * @brief Present option for color channel (able to pass rgba as float & int values) + */ + class MATColorChannel + { + public: + MATColorChannel(std::string name, bool bEnabled, MATColorRGBA&& color); + + static MATColorChannel makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] bool isEnabled() const; + [[nodiscard]] const MATColorRGBA& getColor() const; + + private: + std::string m_name {}; + bool m_bEnabled { false }; + MATColorRGBA m_color {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATCullMode.h b/BMEdit/GameLib/Include/GameLib/MAT/MATCullMode.h new file mode 100644 index 0000000..c52c951 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATCullMode.h @@ -0,0 +1,14 @@ +#pragma once + +#include + + +namespace gamelib::mat +{ + enum class MATCullMode : uint8_t + { + CM_DontCare, + CM_OneSided, + CM_TwoSided + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h b/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h new file mode 100644 index 0000000..7b56c38 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h @@ -0,0 +1,176 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + /** + * Describe type of operand + * @note See sub_471820 for details + */ + enum class MATValueType + { + PT_FLOAT = 0, + PT_CHAR = 1, + PT_UINT32 = 2, + PT_LIST = 3, + PT_UNKNOWN + }; + + /** + * @brief All (almost) possible material system properties from Glacier 1. There are divided by few groups: + * 1) STRREF - reference to string + * 2) TRIVIAL - trivial value (int, float, bool) + * 3) MATLayer - layer + * 4) MATClass - material class + * 5) MATSubClass - material subclass + * 6) MATSprite - material sprite + * 7) MATBind - some binding + * 8) MATInstance - material instance + * 9) MATRenderState - render state + * 10) MATTexture - texture description + * 11) MATColorChannel - color channel description + * 12) MATOption - some boolean option with ref to shader param + * @note See sub_471D70 (ZRenderMaterialBinderParser::ParseMaterialProperties) for details + */ + enum class MATPropertyKind : uint32_t + { + PK_NULL_PROPERTY = 0u, ///< Nothing, used only as 'error' value. Not used by game! + PK_NAME = 0x4E414D45u, ///< [STRREF] Reference to string represents name of something + PK_TYPE = 0x54595045u, ///< [STRREF] Reference to string represents type of object + PK_PATH = 0x50415448u, ///< [STRREF] Reference to string with part to something (asset, scene object, texture name, etc) + PK_IDEN = 0x4944454Eu, ///< [STRREF] Reference to string represents some kind of id + PK_OTYP = 0x4F545950u, ///< [STRREF] Reference to string, idk what represents + PK_STYP = 0x53545950u, ///< [STRREF] Reference to string, idk what represents + PK_LAYER = 0x4C415945u, ///< [MATLayer] Reference to list of properties represents 1 layer. Contains PK_PATH block with path to shader program (with extension) + PK_CLASS = 0x434C4153u, ///< [MATClass] Reference to list of properties represents material class + PK_SUB_CLASS = 0x53554243u, ///< [MATSubClass] Reference to list of properties represents material subclass + PK_ENABLE = 0x454E4142u, ///< [TRIVIAL] Hold boolean value, in `reference` stored value (0 or 1) + PK_SPRITE = 0x53505249u, ///< [MATSprite] Reference to list of properties represents sprite. Contains PK_NAME block (name of sprite?) and one or more enable blocks. @note Need investigate this place more carefully + PK_BIND = 0x42494E44u, ///< [MATBind] Reference to list of properties + PK_INSTANCE = 0x494E5354u, ///< [MATInstance] Reference to list of properties represents material instance on scene (scene object may refs to instance) + PK_BLEND_ENABLE = 0x42454E41u, ///< [TRIVIAL] Hold boolean value, in `reference` stored value (0 or 1 - disable or enable blending) + /** + * @brief [STRREF] Reference to string contains blend mode string repr (part of enum?). + * Possible values: TRANS, TRANS_ON_OPAQUE, TRANSADD_ON_OPAQUE, ADD_BEFORE_TRANS, ADD_ON_OPAQUE, ADD, SHADOW, STATICSHADOW + */ + PK_BLEND_MODE = 0x424D4F44u, + PK_OPACITY = 0x4F504143u, ///< [TRIVIAL] Hold float value, in `reference` stored float (fp32) value represents opacity of something + PK_ALPHA_TEST = 0x41545354u, ///< [TRIVIAL] Hold boolean value, in `reference` stored bool (0 or 1) + PK_ALPHA_REFERENCE = 0x41524546u, ///< [TRIVIAL] Hold uint32 value, in `reference` stored uint32_t (u32) value represents some alpha reference scalar value + PK_FOG_ENABLED = 0x46454E41u, ///< [TRIVIAL] Hold boolean value, in `reference` stored bool (0 or 1). Value represents fog target state + PK_CULL_MODE = 0x43554C4Cu, ///< [STRREF] Reference to string represents culling mode. Possible values: DontCare, OneSided, TwoSided. @note I'm not sure that game uses this values. Be careful! + PK_Z_BIAS = 0x5A424941u, ///< [TRIVIAL] Hold boolean value, in `reference` stored bool (0 or 1) + PK_Z_OFFSET = 0x5A4F4646u, ///< [TRIVIAL] Hold float value (fp32) in `reference`, represents Z offset + PK_TEXTURE_ID = 0x54584944u, ///< [TRIVIAL] Hold uint32 value, in `reference` stored ID of texture (TEXEntry id, see TEX parser for details) + PK_TILINIG_U = 0x54494C55u, ///< [STRREF] Reference to string represents U tiling mode. Possible values: NONE, TILED. See MATTilingMode enum for details + PK_TILINIG_V = 0x54494C56u, ///< [STRREF] Reference to string represents V tiling mode. Possible values: NONE, TILED + PK_TILINIG_W = 0x574C4954u, ///< [STRREF] Reference to string represents W tiling mode. Possible values are unknown. Probably unused, but declared in Glacier1 code. + PK_VAL_I = 0x56414C49u, ///< [STRREF] Reference to string. I guess it's something about 'set boolean parameter by string literal'. `ReflectionEnabled` as example. + PK_VAL_U = 0x56414C55u, ///< [TRIVIAL] Hold float or int value + PK_VAL_F = 0x554C4156u, ///< Idk, unused + PK_RENDER_STATE = 0x52535441u, ///< [MATRenderState] Reference to list of properties which represents render state. + PK_TEXTURE = 0x54455854u, ///< [MATTexture] Reference to list of properties represents texture (PK_PATH - path to texture/PK_TEXTURE_ID - index of texture) + PK_COLOR = 0x434F4C4Fu, ///< [MATColorChannel] Reference to list of properties represents usage of color channel (as example v4IlluminationColor, presented via 2 properties: PK_NAME (name of channel) and PK_ENABLED) + PK_BOOLEAN = 0x424F4F4Cu, ///< [MATOption] Reference to list of properties represents boolean flag. Presented via 3 properties: PK_NAME: AlphaFadeEnabled, PK_ENABLED - use parameter or not and PK_VAL_U - value of flag + PK_FLOAT_VALUE = 0x464C5456u, ///< Weird to see this here. OK, maybe supports (at least referenced) + PK_DMAP = 0x50414D44u, ///< Idk, unused (Glacier supports, but no usage) + PK_FMIN = 0x4E494D46u, ///< Min filter (Idk, looks like legacy from OpenGL times) + PK_FMAG = 0x47414D46u, ///< Mag filter (Idk, looks like legacy from OpenGL times) + PK_FMIP = 0x50494D46u, ///< Idk, unused + PK_SCROLL = 0x4C524353u, ///< Idk, unused + PK_SCROLL_SPEED = 0x44455053u, ///< Idk, unused + PK_ENUM = 0x4D554E45u ///< Idk, unused + }; + + struct MATHeader + { + uint32_t classListOffset { 0 }; + uint32_t instancesListOffset { 0 }; + uint32_t zeroed { 0 }; + uint32_t unknownTableOffset { 0 }; + + static void deserialize(MATHeader& header, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct MATPropertyEntry + { + /** + * @brief Kind of this entry + * @note When PK_NULL_PROPERTY all other data will be invalid! + */ + MATPropertyKind kind { MATPropertyKind::PK_NULL_PROPERTY }; + + /** + * @brief Reference to another block + */ + uint32_t reference { 0 }; + + /** + * @brief Represents how much elements will be captured inside reference. + * For int & float - always 1 + * For str - length of string + * For list - elements count in list + */ + uint32_t containerCapacity { 0u }; + + /** + * @brief Represents type of value inside reference (if reference value or not, idk) + */ + MATValueType valueType { MATValueType::PT_UNKNOWN }; + + /** + * @brief Deserialize binary stream into object + * @param entry + * @param binaryReader + */ + static void deserialize(MATPropertyEntry& entry, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct MATClassDescription + { + std::string parentClass {}; // Name of parent class (a reference to PK_NAME property block) + uint32_t unk4 { 0 }; + uint32_t unk8 { 0 }; + uint32_t unkC { 0 }; + uint32_t unk10 { 0 }; + uint32_t unk14 { 0 }; + uint32_t unk18 { 0 }; + uint32_t classDeclarationOffset {0}; // Class declaration offset + uint32_t unk20 { 0 }; + uint32_t unk24 { 0 }; + uint32_t unk28 { 0 }; + uint32_t unk2C { 0 }; + + static void deserialize(MATClassDescription& classDescription, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct MATInstanceDescription + { + std::string instanceParentClassName {}; // Name of instance class (a reference to PK_NAME property block) + uint32_t unk4 { 0 }; // Usually 0x1 + uint32_t unk8 { 0 }; // 0x0 + uint32_t unkC { 0 }; // Usually 0x77 + uint32_t unk10 { 0 }; // 0x2 + uint32_t unk14 { 0 }; // 0x1 + uint32_t unk18 { 0 }; // 0x1 + uint32_t instanceDeclarationOffset {0}; // Material instance declaration offset + uint32_t unk20 { 0 }; // 0x2 + uint32_t unk24 { 0 }; // 0x0 + uint32_t unk28 { 0 }; // 0x0 + uint32_t unk2C { 0 }; // 0x0 + + static void deserialize(MATInstanceDescription& instanceDescription, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATInstance.h b/BMEdit/GameLib/Include/GameLib/MAT/MATInstance.h new file mode 100644 index 0000000..023bd2c --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATInstance.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + + +namespace gamelib::mat +{ + class MATInstance + { + public: + MATInstance(std::string instanceName, std::string parentClassName, std::vector&& properties, std::vector&& binders); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] const std::string& getParentName() const; + [[nodiscard]] const std::vector& getBinders() const; + + private: + std::string m_name {}; + std::string m_parentName {}; + std::vector m_properties {}; + std::vector m_binders {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATLayer.h b/BMEdit/GameLib/Include/GameLib/MAT/MATLayer.h new file mode 100644 index 0000000..5f8f726 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATLayer.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + /** + * @brief Present layer from Glacier material system + */ + class MATLayer + { + public: + MATLayer(std::string name, std::string type, std::string shaderPath, std::string identity, std::string val_i); + + static MATLayer makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] const std::string& getType() const; + [[nodiscard]] const std::string& getShaderProgramName() const; + [[nodiscard]] const std::string& getIdentity() const; + private: + std::string m_name; + std::string m_type; + std::string m_shaderPath; + std::string m_identity; + std::string m_valI; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h b/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h new file mode 100644 index 0000000..a27e6b8 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATOption + { + public: + MATOption(std::string name, bool bEnabled, bool bDefault); + + static MATOption makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] bool isEnabled() const; + [[nodiscard]] bool getDefault() const; + + + private: + std::string m_name{}; + bool m_bEnabled {}; + bool m_bDefault {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATReader.h b/BMEdit/GameLib/Include/GameLib/MAT/MATReader.h new file mode 100644 index 0000000..cdbbc44 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATReader.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATReader + { + public: + MATReader(); + + bool parse(const uint8_t* pMatBuffer, size_t iMatBufferSize); + + [[nodiscard]] const MATHeader& getHeader() const { return m_header; } + [[nodiscard]] std::vector&& takeClasses() { return std::move(m_classes); } + [[nodiscard]] std::vector&& takeInstances() { return std::move(m_instances); } + + private: + bool collectMaterialClasses(ZBio::ZBinaryReader::BinaryReader* matReader); + bool collectMaterialInstances(ZBio::ZBinaryReader::BinaryReader* matReader); + + private: + MATHeader m_header; + std::vector m_constantTable; + std::vector m_classes; + std::vector m_instances; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h new file mode 100644 index 0000000..7076e7f --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATRenderState + { + public: + MATRenderState(std::string name, + bool bEnabled, bool bBlendEnabled, bool bAlphaTest, bool bFogEnabled, bool bZBias, + float fOpacity, float fZOffset, + uint32_t iAlphaReference, + MATCullMode cullMode, MATBlendMode blendMode): + m_name(std::move(name)), + m_bEnabled(bEnabled), m_bEnableBlend(bBlendEnabled), m_bAlphaTest(bAlphaTest), m_bFogEnabled(bFogEnabled), m_bZBias(bZBias), + m_fOpacity(fOpacity), m_fZOffset(fZOffset), + m_iAlphaReference(iAlphaReference), + m_eCullMode(cullMode), m_eBlendMode(blendMode) + { + } + + static MATRenderState makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const { return m_name; } + [[nodiscard]] bool isEnabled() const { return m_bEnabled; } + [[nodiscard]] bool isBlendEnabled() const { return m_bEnableBlend; } + [[nodiscard]] bool isAlphaTestEnabled() const { return m_bAlphaTest; } + [[nodiscard]] bool isFogEnabled() const { return m_bFogEnabled; } + [[nodiscard]] bool hasZBias() const { return m_bZBias; } + [[nodiscard]] uint32_t getAlphaReference() const { return m_iAlphaReference; } + [[nodiscard]] float getOpacity() const { return m_fOpacity; } + [[nodiscard]] float getZOffset() const { return m_fZOffset; } + [[nodiscard]] MATCullMode getCullMode() const { return m_eCullMode; } + [[nodiscard]] MATBlendMode getBlendMode() const { return m_eBlendMode; } + + private: + std::string m_name {}; + bool m_bEnabled { false }; + bool m_bEnableBlend { false }; + bool m_bAlphaTest { false }; + bool m_bFogEnabled { false }; + bool m_bZBias { false }; + MATCullMode m_eCullMode { MATCullMode::CM_DontCare }; + MATBlendMode m_eBlendMode { MATBlendMode::BM_TRANS }; + uint32_t m_iAlphaReference { 0u }; + float m_fOpacity { 1.f }; + float m_fZOffset { .0f }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATSprite.h b/BMEdit/GameLib/Include/GameLib/MAT/MATSprite.h new file mode 100644 index 0000000..9861cc7 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATSprite.h @@ -0,0 +1,29 @@ +#pragma once + +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATSprite + { + public: + MATSprite(std::string name, bool bUnk0, bool bUnk1); + + static MATSprite makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] bool getUnk0() const; + [[nodiscard]] bool getUnk1() const; + + private: + std::string m_name {}; + bool m_bUnk0 { false }; + bool m_bUnk1 { false }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATSubClass.h b/BMEdit/GameLib/Include/GameLib/MAT/MATSubClass.h new file mode 100644 index 0000000..34eef57 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATSubClass.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATSubClass + { + public: + MATSubClass(std::string name, std::string oTyp, std::string sType, std::vector&& layers) + : m_name(std::move(name)), m_oTyp(std::move(oTyp)), m_sTyp(std::move(sType)), m_layers(std::move(layers)) + { + } + + static MATSubClass makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const { return m_name; } + [[nodiscard]] const std::string& getOTyp() const { return m_oTyp; } + [[nodiscard]] const std::string& getSTyp() const { return m_sTyp; } + [[nodiscard]] const std::vector& getLayers() const { return m_layers; } + + private: + std::string m_name {}; + std::string m_oTyp {}; + std::string m_sTyp {}; + std::vector m_layers {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h b/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h new file mode 100644 index 0000000..4ab1e2a --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATTexture + { + public: + MATTexture(std::string name, bool bEnabled, uint32_t textureId, MATTilingMode tilingU, MATTilingMode tilingV); + + static MATTexture makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const; + [[nodiscard]] bool isEnabled() const; + [[nodiscard]] uint32_t getTextureId() const; + [[nodiscard]] MATTilingMode getTilingU() const; + [[nodiscard]] MATTilingMode getTilingV() const; + + private: + std::string m_name {}; + std::uint32_t m_iTextureId { 0u }; + bool m_bEnabled { false }; + MATTilingMode m_tileU { MATTilingMode::TM_NONE }; + MATTilingMode m_tileV { MATTilingMode::TM_NONE }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h b/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h new file mode 100644 index 0000000..559baca --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h @@ -0,0 +1,11 @@ +#pragma once + + +namespace gamelib::mat +{ + enum class MATTilingMode + { + TM_NONE, + TM_TILED + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h index 2aa085a..3207a8d 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -41,7 +41,14 @@ namespace gamelib::prm struct Mesh { + uint8_t boneDecl = 0; + uint8_t packType = 0; + uint16_t kind = 0; uint16_t textureId = 0; + uint16_t unk6 = 0; + uint32_t nextVariation = 0; + uint8_t unkC = 0; + uint8_t unkD = 0; uint8_t lod = 0; uint16_t material_id = 0; int32_t diffuse_id = 0; diff --git a/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h b/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h new file mode 100644 index 0000000..69b0042 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace gamelib +{ + template<> + struct TObjectExtractor + { + static bool extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static std::int8_t extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static std::int16_t extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static std::int32_t extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static float extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static std::string extract(const Span& instructions) + { + if (instructions.size() == 1) + { + return instructions[0].getOperand().get(); + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static glm::vec2 extract(const Span& instructions) + { + if (instructions.size() == 4) // [0] begin array, [-1] end array + { + return { + instructions[1].getOperand().get(), + instructions[2].getOperand().get() + }; + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static glm::vec3 extract(const Span& instructions) + { + if (instructions.size() == 5) // [0] begin array, [-1] end array + { + return { + instructions[1].getOperand().get(), + instructions[2].getOperand().get(), + instructions[3].getOperand().get() + }; + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static glm::vec4 extract(const Span& instructions) + { + if (instructions.size() == 6) // [0] begin array, [-1] end array + { + return { + instructions[1].getOperand().get(), + instructions[2].getOperand().get(), + instructions[3].getOperand().get(), + instructions[4].getOperand().get() + }; + } + + assert(false && "Bad object"); + return {}; + } + }; + + template<> + struct TObjectExtractor + { + static glm::mat3 extract(const Span& instructions) + { + if (instructions.size() == 11) // [0] begin array, [-1] end array + { + return { + instructions[1].getOperand().get(), + instructions[2].getOperand().get(), + instructions[3].getOperand().get(), + instructions[4].getOperand().get(), + instructions[5].getOperand().get(), + instructions[6].getOperand().get(), + instructions[7].getOperand().get(), + instructions[8].getOperand().get(), + instructions[9].getOperand().get() + }; + } + + assert(false && "Bad object"); + return {}; + } + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRP/PRPObjectExtractor.h b/BMEdit/GameLib/Include/GameLib/PRP/PRPObjectExtractor.h new file mode 100644 index 0000000..01eae31 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/PRP/PRPObjectExtractor.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + + +namespace gamelib +{ + class Value; + + template + struct TObjectExtractor + { + static T extract(const Span& instructions); + }; + + template + concept HasSpecializationTObjectExtractor = requires { + typename TObjectExtractor; + }; + +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/Value.h b/BMEdit/GameLib/Include/GameLib/Value.h index f5036a5..68a458d 100644 --- a/BMEdit/GameLib/Include/GameLib/Value.h +++ b/BMEdit/GameLib/Include/GameLib/Value.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -93,6 +94,21 @@ namespace gamelib bool hasProperty(const char* propertyName) const; + // Extractor + template + T getObject(const std::string& objectName, T def = T()) const requires (HasSpecializationTObjectExtractor) + { + for (const auto& ent : m_entries) + { + if (ent.name == objectName) + { + return TObjectExtractor::extract(Span(m_data).slice(ent.instructions)); + } + } + + return def; + } + private: const Type *m_type {nullptr}; // type std::vector m_data; // instructions diff --git a/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h index 9d29389..8387a01 100644 --- a/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h +++ b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h @@ -31,4 +31,36 @@ namespace gamelib reader->seek(finalPos); } }; + + struct ZBioSeekGuard + { + ZBioSeekGuard() = delete; + ZBioSeekGuard(const ZBioSeekGuard&) = delete; + ZBioSeekGuard(ZBioSeekGuard&&) = delete; + ZBioSeekGuard& operator=(const ZBioSeekGuard&) = delete; + ZBioSeekGuard& operator=(ZBioSeekGuard&&) = delete; + + explicit ZBioSeekGuard(ZBio::ZBinaryReader::BinaryReader* reader) + { + m_reader = reader; + + if (reader) + { + m_seekTo = reader->tell(); + } + } + + ~ZBioSeekGuard() + { + if (m_reader) + { + m_reader->seek(m_seekTo); + m_reader = nullptr; + } + } + + private: + ZBio::ZBinaryReader::BinaryReader* m_reader { nullptr }; + int64_t m_seekTo { 0 }; + }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 1324aab..67aa193 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -44,6 +44,11 @@ namespace gamelib return false; } + if (!loadLevelMaterials()) + { + return false; + } + // TODO: Load things (it's time to combine GMS, PRP & BUF files) m_isLevelLoaded = true; return true; @@ -101,6 +106,16 @@ namespace gamelib return &m_levelGeometry; } + const LevelMaterials* Level::getLevelMaterials() const + { + return &m_levelMaterials; + } + + LevelMaterials* Level::getLevelMaterials() + { + return &m_levelMaterials; + } + const std::vector &Level::getSceneObjects() const { return m_sceneObjects; @@ -274,4 +289,29 @@ namespace gamelib return true; } + + bool Level::loadLevelMaterials() + { + // Read MAT file + int64_t matFileSize = 0; + auto matFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::MATERIALS, matFileSize); + + if (!matFileSize || !matFileBuffer) + { + return false; + } + + mat::MATReader reader; + const bool parseResult = reader.parse(matFileBuffer.get(), matFileSize); + if (!parseResult) + { + return false; + } + + m_levelMaterials.header = reader.getHeader(); + m_levelMaterials.materialClasses = std::move(reader.takeClasses()); + m_levelMaterials.materialInstances = std::move(reader.takeInstances()); + + return true; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp new file mode 100644 index 0000000..2efc579 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATBind::MATBind() = default; + + MATBind MATBind::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + MATBind b {}; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (entry.kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.name = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_RENDER_STATE) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.renderStates.emplace_back(MATRenderState::makeFromStream(binaryReader, entry.containerCapacity)); + } + else if (entry.kind == MATPropertyKind::PK_TEXTURE) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.textures.emplace_back(MATTexture::makeFromStream(binaryReader, entry.containerCapacity)); + } + else if (entry.kind == MATPropertyKind::PK_COLOR) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.colorChannels.emplace_back(MATColorChannel::makeFromStream(binaryReader, entry.containerCapacity)); + } + else if (entry.kind == MATPropertyKind::PK_SPRITE) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.sprites.emplace_back(MATSprite::makeFromStream(binaryReader, entry.containerCapacity)); + } + } + + return b; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATClass.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATClass.cpp new file mode 100644 index 0000000..456cccf --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATClass.cpp @@ -0,0 +1,26 @@ +#include +#include + + +namespace gamelib::mat +{ + MATClass::MATClass(std::string className, std::string parentClass, std::vector&& properties, std::vector&& subClasses) + : m_name(std::move(className)), m_parentClass(std::move(parentClass)), m_properties(std::move(properties)), m_subClasses(std::move(subClasses)) + { + } + + const std::string& MATClass::getName() const + { + return m_name; + } + + const std::string& MATClass::getParentClassName() const + { + return m_parentClass; + } + + const std::vector& MATClass::getSubClasses() const + { + return m_subClasses; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp new file mode 100644 index 0000000..a2afd1f --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATColorChannel::MATColorChannel(std::string name, bool bEnabled, MATColorRGBA&& color) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_color(color) + { + } + + MATColorChannel MATColorChannel::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bEnabled { false }; + MATColorRGBA color; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (auto kind = entry.kind; kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_ENABLE) + { + bEnabled = static_cast(entry.reference); + } + else if (kind == MATPropertyKind::PK_VAL_U) + { + // We will jump to unmarked region (raw buffer) + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + if (entry.containerCapacity == 3) + { + // RGB + if (entry.valueType == MATValueType::PT_UINT32) + { + color.emplace( + binaryReader->read(), + binaryReader->read(), + binaryReader->read() + ); + } + else if (entry.valueType == MATValueType::PT_FLOAT) + { + color.emplace( + binaryReader->read(), + binaryReader->read(), + binaryReader->read() + ); + } + } + else if (entry.containerCapacity == 4) + { + // RGBA + if (entry.valueType == MATValueType::PT_UINT32) + { + color.emplace( + binaryReader->read(), + binaryReader->read(), + binaryReader->read(), + binaryReader->read() + ); + } + else if (entry.valueType == MATValueType::PT_FLOAT) + { + color.emplace( + binaryReader->read(), + binaryReader->read(), + binaryReader->read(), + binaryReader->read() + ); + } + } + } + } + + return MATColorChannel(std::move(name), bEnabled, std::move(color)); + } + + const std::string& MATColorChannel::getName() const + { + return m_name; + } + + bool MATColorChannel::isEnabled() const + { + return m_bEnabled; + } + + const MATColorRGBA& MATColorChannel::getColor() const + { + return m_color; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATEntries.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATEntries.cpp new file mode 100644 index 0000000..3bc99bd --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATEntries.cpp @@ -0,0 +1,84 @@ +#include +#include + + +namespace gamelib::mat +{ + void MATHeader::deserialize(MATHeader& header, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + header.classListOffset = binaryReader->read(); + header.instancesListOffset = binaryReader->read(); + header.zeroed = binaryReader->read(); + header.unknownTableOffset = binaryReader->read(); + } + + void MATPropertyEntry::deserialize(MATPropertyEntry& entry, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + entry.kind = static_cast(binaryReader->read()); + entry.reference = binaryReader->read(); + entry.containerCapacity = binaryReader->read(); + entry.valueType = static_cast(binaryReader->read()); + } + + void MATClassDescription::deserialize(MATClassDescription& classDescription, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + { + const auto offset = binaryReader->read(); + + if (offset >= 0x10) + { + const auto oldPos = binaryReader->tell(); + + // Read class name from valid offset + binaryReader->seek(offset); + classDescription.parentClass = binaryReader->readCString(); + + // Move back + binaryReader->seek(oldPos); + } + } + + classDescription.unk4 = binaryReader->read(); + classDescription.unk8 = binaryReader->read(); + classDescription.unkC = binaryReader->read(); + classDescription.unk10 = binaryReader->read(); + classDescription.unk14 = binaryReader->read(); + classDescription.unk18 = binaryReader->read(); + classDescription.classDeclarationOffset = binaryReader->read(); + classDescription.unk20 = binaryReader->read(); + classDescription.unk24 = binaryReader->read(); + classDescription.unk28 = binaryReader->read(); + classDescription.unk2C = binaryReader->read(); + } + + void MATInstanceDescription::deserialize(MATInstanceDescription& instanceDescription, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + { + const auto offset = binaryReader->read(); + + if (offset >= 0x10) + { + const auto oldPos = binaryReader->tell(); + + // Read class name from valid offset + binaryReader->seek(offset); + instanceDescription.instanceParentClassName = binaryReader->readCString(); + + // Move back + binaryReader->seek(oldPos); + } + } + + instanceDescription.unk4 = binaryReader->read(); + instanceDescription.unk8 = binaryReader->read(); + instanceDescription.unkC = binaryReader->read(); + instanceDescription.unk10 = binaryReader->read(); + instanceDescription.unk14 = binaryReader->read(); + instanceDescription.unk18 = binaryReader->read(); + instanceDescription.instanceDeclarationOffset = binaryReader->read(); + instanceDescription.unk20 = binaryReader->read(); + instanceDescription.unk24 = binaryReader->read(); + instanceDescription.unk28 = binaryReader->read(); + instanceDescription.unk2C = binaryReader->read(); + } +} diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATInstance.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATInstance.cpp new file mode 100644 index 0000000..056a4f2 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATInstance.cpp @@ -0,0 +1,25 @@ +#include + + +namespace gamelib::mat +{ + MATInstance::MATInstance(std::string instanceName, std::string parentClassName, std::vector&& properties, std::vector&& binders) + : m_name(std::move(instanceName)), m_parentName(std::move(parentClassName)), m_properties(std::move(properties)), m_binders(std::move(binders)) + { + } + + const std::string& MATInstance::getName() const + { + return m_name; + } + + const std::string& MATInstance::getParentName() const + { + return m_parentName; + } + + const std::vector& MATInstance::getBinders() const + { + return m_binders; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp new file mode 100644 index 0000000..e3f46ee --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATLayer::MATLayer(std::string name, std::string type, std::string shaderPath, std::string identity, std::string val_i) + : m_name(std::move(name)), m_type(std::move(type)), m_shaderPath(std::move(shaderPath)), m_identity(std::move(identity)), m_valI(std::move(val_i)) + { + } + + const std::string& MATLayer::getName() const + { + return m_name; + } + + const std::string& MATLayer::getType() const + { + return m_type; + } + + const std::string& MATLayer::getShaderProgramName() const + { + return m_shaderPath; + } + + const std::string& MATLayer::getIdentity() const + { + return m_identity; + } + + MATLayer MATLayer::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + assert(propertiesCount > 0 && "Bad props count"); + assert(binaryReader != nullptr && "Bad reader"); + + std::string name {}, type {}, shaderPath {}, identity {}, valI {}; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + // Save pos & seek to value + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + // Read value by type + if (auto kind = entry.kind; kind == MATPropertyKind::PK_NAME) + { + name = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_TYPE) + { + type = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_PATH) + { + shaderPath = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_IDEN) + { + identity = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_VAL_I) + { + valI = binaryReader->readCString(); + } + } + + return MATLayer(std::move(name), std::move(type), std::move(shaderPath), std::move(identity), std::move(valI)); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp new file mode 100644 index 0000000..3dc36ac --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATOption::MATOption(std::string name, bool bEnabled, bool bDefault) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_bDefault(bDefault) + { + } + + const std::string& MATOption::getName() const + { + return m_name; + } + + bool MATOption::isEnabled() const + { + return m_bEnabled; + } + + bool MATOption::getDefault() const + { + return m_bDefault; + } + + MATOption MATOption::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bEnabled = false, bDefault = false; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (auto kind = entry.kind; kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_ENABLE) + { + bEnabled = static_cast(entry.reference); + } + else if (kind == MATPropertyKind::PK_VAL_U) + { + bDefault = static_cast(entry.reference); + } + } + + return MATOption(std::move(name), bEnabled, bDefault); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp new file mode 100644 index 0000000..66e5c05 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp @@ -0,0 +1,238 @@ +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATReader::MATReader() = default; + + bool MATReader::parse(const uint8_t *pMatBuffer, size_t iMatBufferSize) + { + // https://github.com/glacier-modding/io_scene_blood_money/blob/libraries/BMExport/src/mati.cpp#L148 + // https://github.com/glacier-modding/io_scene_blood_money/blob/libraries/BMExport/src/mati.h + // see sub_4930C0 + // see sub_497820 + // see sub_471D70 + ZBio::ZBinaryReader::BinaryReader matReader { reinterpret_cast(pMatBuffer), static_cast(iMatBufferSize) }; + + // Read header + MATHeader::deserialize(m_header, &matReader); + + // Collect classes + matReader.seek(m_header.classListOffset); + if (!collectMaterialClasses(&matReader)) + { + return false; + } + + // Collect instances + matReader.seek(m_header.instancesListOffset); + if (!collectMaterialInstances(&matReader)) + { + return false; + } + + // Save constant table + // TODO: Copy const table + + return true; + } + + bool MATReader::collectMaterialClasses(ZBio::ZBinaryReader::BinaryReader* matReader) + { + static constexpr size_t kMaxClassCount = 0x20; + + // Clear class list + m_classes.clear(); + + for (size_t classIndex = 0; classIndex < kMaxClassCount; ++classIndex) + { + const auto classDeclOffset = matReader->read(); + if (classDeclOffset == 0) + continue; // null reference (skipped) + + // Save position to seek back + ZBioSeekGuard guard(matReader); + + // Continue work (we will seek back on end of iteration) + matReader->seek(classDeclOffset); + + // Read class description + MATClassDescription classDescription; + MATClassDescription::deserialize(classDescription, matReader); + + if (classDescription.classDeclarationOffset < 0x10) + { + assert(false && "Bad class declaration offset"); + return false; + } + + // Jump to class decl + matReader->seek(classDescription.classDeclarationOffset); + + // Read header decl + MATPropertyEntry classDeclEntry; + MATPropertyEntry::deserialize(classDeclEntry, matReader); + + // Check that entry is valid + if (classDeclEntry.kind != MATPropertyKind::PK_CLASS) + { + assert(false && "Expected to have class here"); + return false; + } + + if (classDeclEntry.valueType != MATValueType::PT_LIST) + { + assert(false && "Expected to have list here"); + return false; + } + + // Jump to first entry and iterate over entries + matReader->seek(classDeclEntry.reference); + + std::vector properties; + std::vector subclasses; + subclasses.reserve(classDeclEntry.containerCapacity); + properties.resize(classDeclEntry.containerCapacity); + + std::string className; + + for (int i = 0; i < classDeclEntry.containerCapacity; i++) + { + MATPropertyEntry::deserialize(properties[i], matReader); + + if (properties[i].kind == MATPropertyKind::PK_NAME) + { + assert(className.empty() && "Possible bug: class name override detected!"); + + // Maybe class name override? + ZBioSeekGuard readNameGuard { matReader }; + + matReader->seek(properties[i].reference); + className = matReader->readCString(); + } + else if (properties[i].kind == MATPropertyKind::PK_SUB_CLASS) + { + // Build subclass from entry + ZBioSeekGuard buildSubClassGuard { matReader }; + matReader->seek(properties[i].reference); + + subclasses.emplace_back(MATSubClass::makeFromStream(matReader, properties[i].containerCapacity)); + } + } + + if (className.empty()) + { + assert(false && "Expected at least 1 PK_NAME property, but no one presented."); + return false; + } + + m_classes.emplace_back( + std::move(className), + std::move(classDescription.parentClass), + std::move(properties), + std::move(subclasses) + ); + } + + return true; + } + + bool MATReader::collectMaterialInstances(ZBio::ZBinaryReader::BinaryReader* matReader) + { + static constexpr size_t kMaxMaterialInstancesCount = 0x800; + + for (size_t instanceIndex = 0; instanceIndex < kMaxMaterialInstancesCount; ++instanceIndex) + { + const auto instanceOffset = matReader->read(); + if (instanceOffset == 0) + continue; // skip null reference + + // Save position in guard + ZBioSeekGuard guard { matReader }; + + // Jump to offset + matReader->seek(instanceOffset); + + MATInstanceDescription instanceDescription; + MATInstanceDescription::deserialize(instanceDescription, matReader); + + if (instanceDescription.instanceDeclarationOffset < 0x10) + { + assert(false && "Bad instance declaration offset"); + return false; + } + + // Seek to decl position + matReader->seek(instanceDescription.instanceDeclarationOffset); + + // Read instance definition property + MATPropertyEntry instanceDeclarationProperty; + MATPropertyEntry::deserialize(instanceDeclarationProperty, matReader); + + if (instanceDeclarationProperty.kind != MATPropertyKind::PK_INSTANCE) + { + assert(false && "Instance expected!"); + return false; + } + + if (instanceDeclarationProperty.valueType != MATValueType::PT_LIST) + { + assert(false && "List in instance expected!"); + return false; + } + + // Jump to instance + matReader->seek(instanceDeclarationProperty.reference); + + // Read properties + std::vector properties; + std::vector binders; + binders.reserve(instanceDeclarationProperty.containerCapacity); + properties.resize(instanceDeclarationProperty.containerCapacity); + + std::string instanceName; + + for (int i = 0; i < instanceDeclarationProperty.containerCapacity; i++) + { + MATPropertyEntry::deserialize(properties[i], matReader); + + if (properties[i].kind == MATPropertyKind::PK_NAME) + { + assert(instanceName.empty() && "Possible bug: instance name override detected!"); + + ZBioSeekGuard seekNameGuard { matReader }; + + matReader->seek(properties[i].reference); + instanceName = matReader->readCString(); + } + else if (properties[i].kind == MATPropertyKind::PK_BIND) + { + ZBioSeekGuard seekBinderGuard { matReader }; + + matReader->seek(properties[i].reference); + + binders.emplace_back(MATBind::makeFromStream(matReader, properties[i].containerCapacity)); + } + } + + if (instanceName.empty()) + { + assert(false && "Expected to have instance name, but it's not presented!"); + return false; + } + + // Save instance + m_instances.emplace_back( + std::move(instanceName), + std::move(instanceDescription.instanceParentClassName), + std::move(properties), + std::move(binders) + ); + } + + return true; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp new file mode 100644 index 0000000..3cfed2d --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATRenderState MATRenderState::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bEnabled { false }, bBlendEnabled { false }, bAlphaTest { false }, bFogEnabled { false }, bZBias { false }; + float fOpacity { 1.0f }, fZOffset { .0f }; + uint32_t iAlphaReference { 0u }; + MATCullMode cullMode { MATCullMode::CM_DontCare }; + MATBlendMode blendMode { MATBlendMode::BM_ADD }; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + switch (entry.kind) + { + case MATPropertyKind::PK_NAME: + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + break; + case MATPropertyKind::PK_ENABLE: + { + bEnabled = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_BLEND_ENABLE: + { + bBlendEnabled = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_ALPHA_TEST: + { + bAlphaTest = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_FOG_ENABLED: + { + bFogEnabled = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_Z_BIAS: + { + bZBias = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_OPACITY: + { + fOpacity = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_Z_OFFSET: + { + fZOffset = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_ALPHA_REFERENCE: + { + iAlphaReference = static_cast(entry.reference); + } + break; + case MATPropertyKind::PK_CULL_MODE: + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + std::string temp = binaryReader->readCString(); + + if (temp == "DontCare") + { + cullMode = MATCullMode::CM_DontCare; + } + else if (temp == "OneSided") + { + cullMode = MATCullMode::CM_OneSided; + } + else if (temp == "TwoSided") + { + cullMode = MATCullMode::CM_TwoSided; + } + else + { + assert(false && "Unsupported mode!"); + } + } + break; + case MATPropertyKind::PK_BLEND_MODE: + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + std::string temp = binaryReader->readCString(); + + if (temp == "TRANS") blendMode = MATBlendMode::BM_TRANS; + else if (temp == "TRANS_ON_OPAQUE") blendMode = MATBlendMode::BM_TRANS_ON_OPAQUE; + else if (temp == "TRANSADD_ON_OPAQUE") blendMode = MATBlendMode::BM_TRANSADD_ON_OPAQUE; + else if (temp == "ADD_BEFORE_TRANS") blendMode = MATBlendMode::BM_ADD_BEFORE_TRANS; + else if (temp == "ADD_ON_OPAQUE") blendMode = MATBlendMode::BM_ADD_ON_OPAQUE; + else if (temp == "ADD") blendMode = MATBlendMode::BM_ADD; + else if (temp == "SHADOW") blendMode = MATBlendMode::BM_SHADOW; + else if (temp == "STATICSHADOW") blendMode = MATBlendMode::BM_STATICSHADOW; + else + { + assert(false && "Unsupported mode!"); + } + } + break; + default: + break; + } + } + + return MATRenderState(std::move(name), bEnabled, bBlendEnabled, bAlphaTest, bFogEnabled, bZBias, fOpacity, fZOffset, iAlphaReference, cullMode, blendMode); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp new file mode 100644 index 0000000..e721298 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATSprite::MATSprite(std::string name, bool bUnk0, bool bUnk1) + : m_name(std::move(name)), m_bUnk0(bUnk0), m_bUnk1(bUnk1) + { + } + + MATSprite MATSprite::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bUnk0 { false }; + bool bUnk1 { false }; + int lastUpdatedUnk { 0 }; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (auto kind = entry.kind; kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_ENABLE) + { + if (lastUpdatedUnk == 0) bUnk0 = static_cast(entry.reference); + else if (lastUpdatedUnk == 1) bUnk1 = static_cast(entry.reference); + + ++lastUpdatedUnk; + } + } + + return MATSprite(std::move(name), bUnk0, bUnk1); + } + + const std::string& MATSprite::getName() const + { + return m_name; + } + + bool MATSprite::getUnk0() const + { + return m_bUnk0; + } + + bool MATSprite::getUnk1() const + { + return m_bUnk1; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp new file mode 100644 index 0000000..673ead7 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATSubClass MATSubClass::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}, oTyp {}, sTyp {}; + std::vector layers {}; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (entry.kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_OTYP) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + oTyp = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_STYP) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + sTyp = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_LAYER) + { + // Read layer + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + layers.emplace_back(MATLayer::makeFromStream(binaryReader, entry.containerCapacity)); + } + } + + return MATSubClass(std::move(name), std::move(oTyp), std::move(sTyp), std::move(layers)); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp new file mode 100644 index 0000000..e7a962f --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATTexture::MATTexture(std::string name, bool bEnabled, uint32_t textureId, MATTilingMode tilingU, MATTilingMode tilingV) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_iTextureId(textureId), m_tileU(tilingU), m_tileV(tilingV) + { + } + + MATTexture MATTexture::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name{}; + bool bEnabled{false}; + uint32_t textureId {0}; + MATTilingMode tileU { MATTilingMode::TM_NONE }, tileV { MATTilingMode::TM_NONE }; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (auto kind = entry.kind; kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (kind == MATPropertyKind::PK_ENABLE) + { + bEnabled = static_cast(entry.reference); + } + else if (kind == MATPropertyKind::PK_TEXTURE_ID) + { + textureId = entry.reference; + } + else if (kind == MATPropertyKind::PK_TILINIG_U || kind == MATPropertyKind::PK_TILINIG_V) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + std::string temp = binaryReader->readCString(); + MATTilingMode tempMode = MATTilingMode::TM_NONE; + + if (temp == "NONE" || temp.empty()) + { + tempMode = MATTilingMode::TM_NONE; + } + else if (temp == "TILED") + { + tempMode = MATTilingMode::TM_TILED; + } + else + { + assert(false && "Unsupported mode!"); + continue; + } + + if (kind == MATPropertyKind::PK_TILINIG_U) tileU = tempMode; + if (kind == MATPropertyKind::PK_TILINIG_V) tileV = tempMode; + } + } + + return MATTexture(std::move(name), bEnabled, textureId, tileU, tileV); + } + + const std::string& MATTexture::getName() const + { + return m_name; + } + + uint32_t MATTexture::getTextureId() const + { + return m_iTextureId; + } + + MATTilingMode MATTexture::getTilingU() const + { + return m_tileU; + } + + MATTilingMode MATTexture::getTilingV() const + { + return m_tileV; + } +} diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index e65da1a..eb0cfe4 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -24,19 +24,15 @@ namespace gamelib::prm void Mesh::deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile) { -#ifdef TRY_HARD - // Skip header? - binaryReader->seek(0x4); + mesh.boneDecl = binaryReader->read(); + mesh.packType = binaryReader->read(); + mesh.kind = binaryReader->read(); mesh.textureId = binaryReader->read(); - - binaryReader->seek(0xB); - //binaryReader->seek(0xE); - mesh.lod = binaryReader->read(); -#else - binaryReader->seek(0xE); - + mesh.unk6 = binaryReader->read(); + mesh.nextVariation = binaryReader->read(); + mesh.unkC = binaryReader->read(); + mesh.unkD = binaryReader->read(); mesh.lod = binaryReader->read(); -#endif if ((mesh.lod & static_cast(1)) == static_cast(1)) { From 5f8ae497eeb81b907d053feeec2f5b8e6ccc9662 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 8 Oct 2023 21:59:41 +0300 Subject: [PATCH 12/80] Trash texturing v0 --- .../Source/Widgets/SceneRenderWidget.cpp | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0cdfb47..6fd32cc 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -23,6 +23,8 @@ namespace widgets GLuint vbo { kInvalidResource }; GLuint ibo { kInvalidResource }; size_t textureId { 0 }; + uint16_t materialId { 0 }; + int trianglesCount { 0 }; void discard(QOpenGLFunctions_3_3_Core* gapi) @@ -707,7 +709,35 @@ namespace widgets glFunctions->glEnableVertexAttribArray(1); // Precache color texture - glMesh.textureId = mesh.textureId != 0 ? mesh.textureId : m_resources->m_iDebugTextureIndex; //TODO: here we need to ask material first! + glMesh.materialId = mesh.material_id; + + if (mesh.material_id > 0) + { + // Use attached material + const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; + const auto& matInstance = instances[mesh.material_id - 1]; + + //TODO: Need fix this place, not perfect solution + if (!matInstance.getBinders().empty() && !matInstance.getBinders()[0].textures.empty()) + { + // Take texture id + const uint32_t textureId = matInstance.getBinders()[0].textures[0].getTextureId(); + + // And save texture index back + glMesh.textureId = textureId; + } + } + else + { + // Try to use texture from description + glMesh.textureId = mesh.textureId; + } + + if (glMesh.textureId == 0) + { + // Not presented yet, use debug texture + glMesh.textureId = m_resources->m_iDebugTextureIndex; + } // Next mesh ++meshIdx; @@ -952,16 +982,18 @@ void main() // 3. Activate VAO glFunctions->glBindVertexArray(mesh.vao); + const GLenum mode = GL_TRIANGLES; + // 4. Submit if (mesh.ibo != GLResources::kInvalidResource) { // Draw indexed - glFunctions->glDrawElements(GL_TRIANGLES, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); + glFunctions->glDrawElements(mode, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); } else { // Draw elements - glFunctions->glDrawArrays(GL_TRIANGLES, 0, mesh.trianglesCount); + glFunctions->glDrawArrays(mode, 0, mesh.trianglesCount); } // ... End From bed46506d5293456b24300354f50b90f38800fd0 Mon Sep 17 00:00:00 2001 From: DronCode Date: Mon, 9 Oct 2023 12:24:14 +0300 Subject: [PATCH 13/80] Nice texturing & fixed bug in buffer preparation, but still have bug with world transformations & culling. --- .../Include/Widgets/SceneRenderWidget.h | 40 +- .../Source/Widgets/SceneRenderWidget.cpp | 387 +++++++++++++----- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 60 ++- BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 104 ++++- .../GameLib/Include/GameLib/PRM/PRMEntries.h | 10 + .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 61 ++- .../GameLib/Source/GameLib/PRM/PRMReader.cpp | 9 +- 7 files changed, 510 insertions(+), 161 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 960e835..0029c58 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -149,6 +149,17 @@ class QOpenGLFunctions_3_3_Core; namespace widgets { + using RenderModeFlags = uint8_t; + + enum RenderMode : RenderModeFlags + { + RM_TEXTURE = 1 << 0, + RM_WIREFRAME = 1 << 1, + + RM_ALL = RM_TEXTURE | RM_WIREFRAME, + RM_DEFAULT = RM_TEXTURE + }; + class SceneRenderWidget : public QOpenGLWidget { Q_OBJECT @@ -165,6 +176,14 @@ namespace widgets [[nodiscard]] float getFOV() const { return m_fFOV; } void setFOV(float fov) { m_fFOV = fov; m_bDirtyProj = true; } + void setGeomViewMode(gamelib::scene::SceneObject* sceneObject); + void setWorldViewMode(); + void resetViewMode(); + + [[nodiscard]] RenderModeFlags getRenderMode() const; + void setRenderMode(RenderModeFlags renderMode); + void resetRenderMode(); + void moveCameraTo(const glm::vec3& position); signals: @@ -189,20 +208,23 @@ namespace widgets void doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions); void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); void doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions); - void doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject::Ptr& geom); + void doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility = false); void discardResources(QOpenGLFunctions_3_3_Core* glFunctions); - void acquireMouseCapture(); - void releaseMouseCapture(); private: + // Data gamelib::Level* m_pLevel { nullptr }; + + // Camera & world view renderer::Camera m_camera {}; glm::mat4 m_matProjection {}; float m_fFOV { 67.664f }; float m_fZNear { .1f }; - float m_fZFar { 1000.f }; + float m_fZFar { 100'000.f }; bool m_bDirtyProj { true }; + uint8_t m_renderMode = RenderMode::RM_DEFAULT; + // State enum class ELevelState : uint8_t { LS_NONE = 0, @@ -217,6 +239,16 @@ namespace widgets QPoint m_mouseLastPosition {}; bool m_bFirstMouseQuery { true }; + // View mode + enum class EViewMode : uint8_t + { + VM_WORLD_VIEW, + VM_GEOM_PREVIEW + }; + + EViewMode m_eViewMode { EViewMode::VM_WORLD_VIEW }; + gamelib::scene::SceneObject* m_pSceneObjectToView {}; + struct GLResources; std::unique_ptr m_resources; }; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 6fd32cc..ef76e87 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -22,8 +22,9 @@ namespace widgets GLuint vao { kInvalidResource }; GLuint vbo { kInvalidResource }; GLuint ibo { kInvalidResource }; - size_t textureId { 0 }; - uint16_t materialId { 0 }; + GLuint glTextureId { kInvalidResource }; /// Render OpenGL texture resource handle + + uint16_t materialId { 0 }; /// Id of material from Glacier mesh (just copy) int trianglesCount { 0 }; @@ -303,7 +304,9 @@ namespace widgets std::vector m_textures {}; std::vector m_shaders {}; std::vector m_models {}; - std::uint32_t m_iDebugTextureIndex { 0 }; + GLuint m_iGLDebugTexture { 0 }; + size_t m_iTexturedShaderIdx = 0; + size_t m_iGizmoShaderIdx = 0; GLResources() {} ~GLResources() {} @@ -337,7 +340,7 @@ namespace widgets m_models.clear(); } - m_iDebugTextureIndex = 0u; + m_iGLDebugTexture = 0u; } [[nodiscard]] bool hasResources() const @@ -418,7 +421,18 @@ namespace widgets break; case ELevelState::LS_READY: { - doRenderScene(funcs); + if (m_eViewMode == EViewMode::VM_WORLD_VIEW) + { + doRenderScene(funcs); + } + else if (m_eViewMode == EViewMode::VM_GEOM_PREVIEW) + { + if (m_pSceneObjectToView) + { + // Render object & ignore Invisible flag + doRenderGeom(funcs, m_pSceneObjectToView, true); + } + } } break; } @@ -525,6 +539,8 @@ namespace widgets m_eState = ELevelState::LS_NONE; m_pLevel = pLevel; m_bFirstMouseQuery = true; + resetViewMode(); + resetRenderMode(); } } @@ -534,9 +550,54 @@ namespace widgets { m_pLevel = nullptr; m_bFirstMouseQuery = true; + resetViewMode(); + resetRenderMode(); } } + void SceneRenderWidget::setGeomViewMode(gamelib::scene::SceneObject* sceneObject) + { + assert(sceneObject != nullptr); + + if (sceneObject) + { + m_eViewMode = EViewMode::VM_GEOM_PREVIEW; + m_pSceneObjectToView = sceneObject; + m_camera.setPosition(glm::vec3(0.f)); + repaint(); + } + } + + void SceneRenderWidget::setWorldViewMode() + { + m_eViewMode = EViewMode::VM_WORLD_VIEW; + m_pSceneObjectToView = nullptr; + m_camera.setPosition(glm::vec3(0.f)); + repaint(); + } + + void SceneRenderWidget::resetViewMode() + { + setWorldViewMode(); + } + + RenderModeFlags SceneRenderWidget::getRenderMode() const + { + return m_renderMode; + } + + void SceneRenderWidget::setRenderMode(RenderModeFlags renderMode) + { + m_renderMode = renderMode; + repaint(); + } + + void SceneRenderWidget::resetRenderMode() + { + m_renderMode = RenderMode::RM_DEFAULT; + repaint(); + } + void SceneRenderWidget::moveCameraTo(const glm::vec3& position) { if (!m_pLevel) @@ -601,16 +662,17 @@ namespace widgets glFunctions->glBindTexture(GL_TEXTURE_2D, 0); - m_resources->m_textures.emplace_back(newTexture); - // Precache debug texture if it's not precached yet static constexpr const char* kGlacierMissingTex = "_Glacier/Missing_01"; static constexpr const char* kWorldColiTex = "_TEST/Worldcoli"; - if (m_resources->m_iDebugTextureIndex == 0 && texture.m_fileName.has_value() && (texture.m_fileName.value() == kGlacierMissingTex || texture.m_fileName.value() == kWorldColiTex)) + if (m_resources->m_iGLDebugTexture == 0 && texture.m_fileName.has_value() && (texture.m_fileName.value() == kGlacierMissingTex || texture.m_fileName.value() == kWorldColiTex)) { - m_resources->m_iDebugTextureIndex = static_cast(m_resources->m_textures.size() - 1); + m_resources->m_iGLDebugTexture = newTexture.texture; } + + // Save texture + m_resources->m_textures.emplace_back(newTexture); } qDebug() << "All textures (" << m_pLevel->getSceneTextures()->entries.size() << ") are loaded and ready to be used"; @@ -668,16 +730,16 @@ namespace widgets } } - for (const auto& index : mesh.indices) + for (const auto& [a,b,c] : mesh.indices) { - indices.emplace_back(index.a); - indices.emplace_back(index.b); - indices.emplace_back(index.c); + indices.emplace_back(a); + indices.emplace_back(b); + indices.emplace_back(c); } // And upload it to GPU GLResources::Mesh& glMesh = glModel.meshes.emplace_back(); - glMesh.trianglesCount = static_cast(mesh.indices.size() / 3); + glMesh.trianglesCount = mesh.trianglesCount; // Allocate VAO, VBO & IBO stuff glFunctions->glGenVertexArrays(1, &glMesh.vao); @@ -702,42 +764,103 @@ namespace widgets } // Setup vertex format - glFunctions->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + const GLintptr vertexCoordinateOffset = 0 * sizeof(float); + const GLintptr UVCoordinateOffset = 3 * sizeof(float); + const GLsizei stride = 5 * sizeof(float); + glFunctions->glEnableVertexAttribArray(0); + glFunctions->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (const GLvoid*)vertexCoordinateOffset); - glFunctions->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); glFunctions->glEnableVertexAttribArray(1); + glFunctions->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid*)UVCoordinateOffset); // Precache color texture glMesh.materialId = mesh.material_id; - if (mesh.material_id > 0) + if (glMesh.materialId > 0) { - // Use attached material + // Use material (for meshes) + // First of all we need to know that 'shadows' and other things must be filtered here const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; + const auto& classes = m_pLevel->getLevelMaterials()->materialClasses; const auto& matInstance = instances[mesh.material_id - 1]; - //TODO: Need fix this place, not perfect solution - if (!matInstance.getBinders().empty() && !matInstance.getBinders()[0].textures.empty()) + if (const auto& parentName = matInstance.getParentName(); parentName == "StaticShadow" || parentName == "StaticShadowTextureShadow" || matInstance.getName().find("AlwaysInShadow") != std::string::npos) { - // Take texture id - const uint32_t textureId = matInstance.getBinders()[0].textures[0].getTextureId(); + // Shadows - do not use texturing (and don't show for now) + glMesh.glTextureId = GLResources::kInvalidResource; + } + else if (parentName == "Old" || parentName == "Glow") + { + // TODO: Impl later + glMesh.glTextureId = GLResources::kInvalidResource; + } + else if (matInstance.getBinders().empty()) + { + // No texture at all + glMesh.glTextureId = GLResources::kInvalidResource; + } + else if (parentName == "Bad") + { + // Use 'bad' debug texture + glMesh.glTextureId = m_resources->m_iGLDebugTexture; + } + else + { + bool bTextureFound = false; - // And save texture index back - glMesh.textureId = textureId; + // Here we need to find 'color' texture. In most cases we able to use matDiffuse as color texture + for (const auto& binder : matInstance.getBinders()) + { + if (bTextureFound) + break; + + for (const auto& texture : binder.textures) + { + if (texture.getName() == "mapDiffuse" && texture.getTextureId() != 0) + { + // And find texture in textures pool + for (const auto& textureResource : m_resources->m_textures) + { + if (textureResource.index.has_value() && textureResource.index.value() == texture.getTextureId()) + { + glMesh.glTextureId = textureResource.texture; + + // Ok, we are ready to show this + bTextureFound = true; + break; + } + } + + if (!bTextureFound) + { + // Use error texture + glMesh.glTextureId = m_resources->m_iGLDebugTexture; + } + + // But mark us as 'found' + bTextureFound = true; + + // Done + break; + } + } + } } } - else - { - // Try to use texture from description - glMesh.textureId = mesh.textureId; - } - - if (glMesh.textureId == 0) + else if (mesh.textureId > 0) { - // Not presented yet, use debug texture - glMesh.textureId = m_resources->m_iDebugTextureIndex; + // Use texture here (for sprites). Need to find that texture in loaded textures list + for (const auto& texture : m_resources->m_textures) + { + if (texture.index.has_value() && texture.index.value() == mesh.textureId) + { + glMesh.glTextureId = texture.texture; + break; + } + } } + // Otherwise no texture. So, we will render only bounding box (if it needed) // Next mesh ++meshIdx; @@ -753,7 +876,7 @@ namespace widgets LEVEL_SAFE_CHECK() // TODO: In future we will use shaders from FS, but now I need to debug this stuff easier - static constexpr const char* kVertexShader = R"( + static constexpr const char* kTexturedVertexShader = R"( #version 330 core // Layout @@ -787,46 +910,95 @@ void main() } )"; - static constexpr const char* kFragmentShader = R"( + static constexpr const char* kTexturedFragmentShader = R"( +#version 330 core + +uniform sampler2D i_ActiveTexture; +in vec2 g_TexCoord; + +// Out +out vec4 o_FragColor; + +void main() +{ + o_FragColor = texture(i_ActiveTexture, g_TexCoord); +} +)"; + + static constexpr const char* kGizmoVertexShader = R"( #version 330 core -vec4 decodeDebugId(int objectId) +// Layout +layout (location = 0) in vec3 aPos; + +// Common +struct Camera { - float r = float((objectId >> 24) & 0xFF) / 255.0; - float g = float((objectId >> 16) & 0xFF) / 255.0; - float b = float((objectId >> 8) & 0xFF) / 255.0; - float a = float(objectId & 0xFF) / 255.0; + mat4 proj; + mat4 view; + ivec2 resolution; +}; + +struct Transform +{ + mat4 model; +}; + +// Uniforms +uniform Camera i_uCamera; +uniform Transform i_uTransform; - return vec4(r, g, b, a); +void main() +{ + gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); } +)"; -uniform sampler2D i_ActiveTexture; -uniform int i_DebugObjectID; -in vec2 g_TexCoord; + static constexpr const char* kGizmoFragmentShader = R"( +#version 330 core + +uniform vec4 i_Color; // Out out vec4 o_FragColor; void main() { - //o_FragColor = decodeDebugId(i_DebugObjectID); - o_FragColor = texture(i_ActiveTexture, g_TexCoord); + o_FragColor = i_Color; } )"; - // TODO: Compile shader + // Compile shaders std::string compileError; - GLResources::Shader defaultShader; - if (!defaultShader.compile(glFunctions, kVertexShader, kFragmentShader, compileError)) { - m_pLevel = nullptr; - m_eState = ELevelState::LS_NONE; + GLResources::Shader texturedShader; + if (!texturedShader.compile(glFunctions, kTexturedVertexShader, kTexturedFragmentShader, compileError)) + { + m_pLevel = nullptr; + m_eState = ELevelState::LS_NONE; - emit resourceLoadFailed(QString("Failed to compile shaders: %1").arg(QString::fromStdString(compileError))); - return; + emit resourceLoadFailed(QString("Failed to compile shaders (textured): %1").arg(QString::fromStdString(compileError))); + return; + } + + m_resources->m_shaders.emplace_back(texturedShader); + m_resources->m_iTexturedShaderIdx = m_resources->m_shaders.size() - 1; } - m_resources->m_shaders.emplace_back(defaultShader); + { + GLResources::Shader gizmoShader; + if (!gizmoShader.compile(glFunctions, kGizmoVertexShader, kGizmoFragmentShader, compileError)) + { + m_pLevel = nullptr; + m_eState = ELevelState::LS_NONE; + + emit resourceLoadFailed(QString("Failed to compile shaders (gizmo): %1").arg(QString::fromStdString(compileError))); + return; + } + + m_resources->m_shaders.emplace_back(gizmoShader); + m_resources->m_iGizmoShaderIdx = m_resources->m_shaders.size() - 1; + } qDebug() << "Shaders (" << m_resources->m_shaders.size() << ") compiled and ready to use!"; m_eState = ELevelState::LS_RESET_CAMERA_STATE; @@ -871,7 +1043,7 @@ void main() // Out ROOT is always first object. Start tree hierarchy visit from ROOT const gamelib::scene::SceneObject::Ptr& root = m_pLevel->getSceneObjects()[0]; - doRenderGeom(glFunctions, root); + doRenderGeom(glFunctions, root.get()); } void SceneRenderWidget::discardResources(QOpenGLFunctions_3_3_Core* glFunctions) @@ -884,16 +1056,20 @@ void main() m_eState = ELevelState::LS_NONE; // Switch to none, everything is gone } - void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject::Ptr& geom) // NOLINT(*-no-recursion) + void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { + // Save "scene" resolution + const glm::ivec2 viewResolution { QWidget::width(), QWidget::height() }; + // Take params const auto primId = geom->getProperties().getObject("PrimId", 0); const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); const auto mMatrix = geom->getProperties().getObject("Matrix", glm::mat3(1.f)); + const auto mMatrixT = glm::transpose(mMatrix); // Don't draw invisible things - if (bInvisible) + if (bInvisible && !bIgnoreVisibility) return; // TODO: Check for culling here (object visible or not) @@ -902,7 +1078,7 @@ void main() if (primId != 0) { // Extract matrix from properties - const glm::mat4 transformMatrix = glm::mat4(mMatrix); + const glm::mat4 transformMatrix = glm::mat4(mMatrixT); const glm::mat4 translateMatrix = glm::translate(glm::mat4(1.f), vPosition); // Am I right here? @@ -924,79 +1100,64 @@ void main() for (const auto& mesh : model.meshes) { // Render single mesh - // 1. Activate default shader - m_resources->m_shaders[0].bind(glFunctions); - - // 2. Submit uniforms - m_resources->m_shaders[0].setUniform(glFunctions, "i_uTransform.model", modelMatrix); - m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.proj", m_matProjection); - m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.view", m_camera.getViewMatrix()); - m_resources->m_shaders[0].setUniform(glFunctions, "i_uCamera.resolution", glm::ivec2(QWidget::width(), QWidget::height())); - - // Encode debug object marker + // 0. Check that we able to draw it + if (mesh.glTextureId == GLResources::kInvalidResource) { - // Here we have about 31 bits of data - int32_t debugMarker = 0; - - union UEncoder - { - int32_t i32_1{0}; - int16_t i16_2[2]; - } encoder; - - auto djb2Hash16 = [](const std::string& str) -> std::int16_t { - int16_t hash = 5381; // Initial hash value - - for (char c : str) { - hash = ((hash << static_cast(5)) + hash) ^ static_cast(c); - } + // Draw "error" bounding box + // And continue + continue; + } - return hash; - }; + // 1. Activate default shader + GLResources::Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; - encoder.i16_2[0] = static_cast(model.chunkId); - encoder.i16_2[1] = djb2Hash16(geom->getName()); + texturedShader.bind(glFunctions); - m_resources->m_shaders[0].setUniform(glFunctions, "i_DebugObjectID", encoder.i32_1); - } + // 2. Submit uniforms + texturedShader.setUniform(glFunctions, "i_uTransform.model", modelMatrix); + texturedShader.setUniform(glFunctions, "i_uCamera.proj", m_matProjection); + texturedShader.setUniform(glFunctions, "i_uCamera.view", m_camera.getViewMatrix()); + texturedShader.setUniform(glFunctions, "i_uCamera.resolution", viewResolution); // 3. Bind texture - // TODO: Optimize me + if (mesh.glTextureId != GLResources::kInvalidResource) { - const auto& allTextures = m_resources->m_textures; - auto textureIt = std::find_if(allTextures.begin(), allTextures.end(), [index = mesh.textureId](const GLResources::Texture& tex) -> bool { - return tex.index.has_value() && tex.index.value() == index; - }); + glFunctions->glBindTexture(GL_TEXTURE_2D, mesh.glTextureId); + } + + // 3. Activate VAO + glFunctions->glBindVertexArray(mesh.vao); - if (textureIt != allTextures.end()) + auto doSubmitGPUCommands = [glFunctions, mesh]() { + if (mesh.ibo != GLResources::kInvalidResource) { - glFunctions->glBindTexture(GL_TEXTURE_2D, textureIt->texture); + // Draw indexed + glFunctions->glDrawElements(GL_TRIANGLES, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); } else { - // Need bind 'error' texture - glFunctions->glBindTexture(GL_TEXTURE_2D, m_resources->m_textures[m_resources->m_iDebugTextureIndex].texture); + // Draw elements + glFunctions->glDrawArrays(GL_TRIANGLES, 0, mesh.trianglesCount); } - } - - // 3. Activate VAO - glFunctions->glBindVertexArray(mesh.vao); + }; - const GLenum mode = GL_TRIANGLES; - - // 4. Submit - if (mesh.ibo != GLResources::kInvalidResource) + if (m_renderMode & RenderMode::RM_TEXTURE) { - // Draw indexed - glFunctions->glDrawElements(mode, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); + // normal draw + doSubmitGPUCommands(); } - else + + if (m_renderMode & RenderMode::RM_WIREFRAME) { - // Draw elements - glFunctions->glDrawArrays(mode, 0, mesh.trianglesCount); + glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + doSubmitGPUCommands(); + // reset back + glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } - // ... End + // 5. Draw bounding box + + // 6. Unbind texture and shader (expected to switch between materials, but not now) glFunctions->glBindTexture(GL_TEXTURE_2D, 0); m_resources->m_shaders[0].unbind(glFunctions); } @@ -1009,7 +1170,7 @@ void main() { if (auto child = childRef.lock()) { - doRenderGeom(glFunctions, child); + doRenderGeom(glFunctions, child.get()); } } } diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 8c65184..6a911da 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -105,6 +105,29 @@ void BMEditMainWindow::connectActions() connect(ui->actionSave_properties, &QAction::triggered, this, &BMEditMainWindow::onExportProperties); connect(ui->actionExport_PRP_properties, &QAction::triggered, this, &BMEditMainWindow::onExportPRP); connect(ui->actionTextures, &QAction::triggered, this, &BMEditMainWindow::onShowTexturesDialog); + connect(ui->actionView_whole_scene, &QAction::triggered, this, [this] { ui->sceneGLView->setWorldViewMode(); }); + + // Modes + connect(ui->actionRenderMode_Texture, &QAction::toggled, this, [this](bool val) { + widgets::RenderModeFlags newMode = ui->sceneGLView->getRenderMode(); + + if (val) + newMode |= widgets::RenderMode::RM_TEXTURE; + else + newMode &= ~widgets::RenderMode::RM_TEXTURE; + + ui->sceneGLView->setRenderMode(newMode); + }); + connect(ui->actionRenderMode_Wireframe, &QAction::toggled, this, [this](bool val) { + widgets::RenderModeFlags newMode = ui->sceneGLView->getRenderMode(); + + if (val) + newMode |= widgets::RenderMode::RM_WIREFRAME; + else + newMode &= ~widgets::RenderMode::RM_WIREFRAME; + + ui->sceneGLView->setRenderMode(newMode); + }); } void BMEditMainWindow::connectDockWidgetActions() @@ -140,7 +163,7 @@ void BMEditMainWindow::connectDockWidgetActions() }); // Properties - connect(ui->propertiesDock, &QDockWidget::visibilityChanged, [=](bool visibility) + connect(ui->gameObjectDock, &QDockWidget::visibilityChanged, [=](bool visibility) { QSignalBlocker actionPropertiesBlocker{ui->actionProperties}; @@ -149,9 +172,9 @@ void BMEditMainWindow::connectDockWidgetActions() connect(ui->actionProperties, &QAction::triggered, [=](bool checked) { - QSignalBlocker propertiesDockBlocker{ui->propertiesDock}; + QSignalBlocker propertiesDockBlocker{ui->gameObjectDock}; - ui->propertiesDock->setVisible(checked); + ui->gameObjectDock->setVisible(checked); }); } @@ -192,9 +215,9 @@ void BMEditMainWindow::onOpenLevel() } void BMEditMainWindow::onRestoreLayout() { - ui->propertiesDock->setVisible(true); + ui->gameObjectDock->setVisible(true); ui->sceneDock->setVisible(true); - ui->propertiesDock->setVisible(true); + ui->gameObjectDock->setVisible(true); } void BMEditMainWindow::onShowTypesViewer() @@ -213,7 +236,14 @@ void BMEditMainWindow::onLevelLoadSuccess() // Level loaded, show objects tree ui->searchInputField->clear(); - + ui->actionView_whole_scene->setEnabled(true); + ui->actionView_whole_scene->setChecked(true); + ui->actionRenderMode_Texture->setEnabled(true); + ui->actionRenderMode_Texture->setChecked(true); + ui->actionRenderMode_Wireframe->setEnabled(true); + ui->actionRenderMode_Wireframe->setChecked(false); + + // Setup models if (m_sceneTreeModel) { m_sceneTreeModel->setLevel(currentLevel); @@ -357,6 +387,14 @@ void BMEditMainWindow::onCloseLevel() ui->actionExport_PRP_properties->setEnabled(false); ui->actionTextures->setEnabled(false); + // Reset world view mode + ui->actionView_whole_scene->setEnabled(false); + ui->actionView_whole_scene->setChecked(true); + ui->actionRenderMode_Texture->setEnabled(false); + ui->actionRenderMode_Texture->setChecked(true); + ui->actionRenderMode_Wireframe->setEnabled(false); + ui->actionRenderMode_Wireframe->setChecked(true); + // Disable filtering QSignalBlocker blocker { ui->searchInputField }; ui->searchInputField->setEnabled(false); @@ -441,12 +479,22 @@ void BMEditMainWindow::onContextMenuRequestedForSceneTreeNode(const QPoint& poin ui->sceneGLView->moveCameraTo(vPosition); }; + auto implShowSelectedGeom = [this](gamelib::scene::SceneObject* sceneObject) + { + ui->actionView_whole_scene->setChecked(false); + ui->sceneGLView->setGeomViewMode(sceneObject); + }; + contextMenu.addAction(QString("Object: '%1'").arg(QString::fromStdString(selectedGeom->getName())))->setDisabled(true); contextMenu.addAction(QString("Type: '%1'").arg(QString::fromStdString(selectedGeom->getType()->getName())))->setDisabled(true); + contextMenu.addSeparator(); contextMenu.addAction("Copy path", [implCopyPathToGeom, selectedGeom] { implCopyPathToGeom(selectedGeom, false); }); contextMenu.addAction("Copy path (ignore ROOT)", [implCopyPathToGeom, selectedGeom] { implCopyPathToGeom(selectedGeom, true); }); + + contextMenu.addSeparator(); contextMenu.addAction("Move camera to this object", [implMoveCameraToGeom, selectedGeom] { implMoveCameraToGeom(const_cast(selectedGeom)); }); + contextMenu.addAction("Show only this geom", [implShowSelectedGeom, selectedGeom] { implShowSelectedGeom(const_cast(selectedGeom)); }); contextMenu.exec(ui->sceneTreeView->viewport()->mapToGlobal(point)); } diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index 9e0ad4c..e5f09c0 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -75,7 +75,17 @@ Scene + + + Render Mode + + + + + + + @@ -193,7 +203,7 @@ - + 260 @@ -207,7 +217,7 @@ QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable - Properties + Game Object 2 @@ -289,6 +299,55 @@ + + + Material + + + + + + + + Material ID: + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Material Name: + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + @@ -375,6 +434,47 @@ Localization + + + true + + + true + + + false + + + View whole scene + + + Alt+V + + + + + true + + + true + + + Texture + + + + + true + + + Wireframe + + + + + Texture & Wireframe + + diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h index 3207a8d..49522e1 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -19,6 +19,14 @@ namespace ZBio::ZBinaryReader namespace gamelib::prm { + enum class VertexFormat : uint32_t { + VF_ERROR = 0, + VF_10 = 0x10, + VF_24 = 0x24, + VF_28 = 0x28, + VF_34 = 0x34 + }; + #pragma pack(push, 1) // TODO: Need to use some sort of macro to make this place cross-compiler supportable struct PrmFile; @@ -54,9 +62,11 @@ namespace gamelib::prm int32_t diffuse_id = 0; int32_t normal_id = 0; int32_t specular_id = 0; + uint16_t trianglesCount = 0; std::vector vertices {}; std::vector indices {}; std::vector uvs {}; + VertexFormat vertexFormat { VertexFormat::VF_ERROR }; //BoundingBox boundingBox {}; static void deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile); diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index eb0cfe4..d083612 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace gamelib::prm @@ -32,9 +33,14 @@ namespace gamelib::prm mesh.nextVariation = binaryReader->read(); mesh.unkC = binaryReader->read(); mesh.unkD = binaryReader->read(); + + assert(binaryReader->tell() == 0xE && "Bad offset"); + if (binaryReader->tell() != 0xE) + return; + mesh.lod = binaryReader->read(); - if ((mesh.lod & static_cast(1)) == static_cast(1)) + if (mesh.lod & (uint8_t)1 == (uint8_t)1) { // Seed another 3 bytes? ZBioHelpers::seekBy(binaryReader, 0x3); @@ -106,6 +112,7 @@ namespace gamelib::prm uint16_t trianglesCount = 0; trianglesCount = trianglesReader.read(); + mesh.trianglesCount = trianglesCount; // Magic (rly?) uint32_t vertexSize = 0; @@ -124,28 +131,19 @@ namespace gamelib::prm static_cast(prmFile.entries[vertexChunk].size) }; - enum VertexFormat : uint32_t { - VF_10 = 0x10, - VF_24 = 0x24, - VF_28 = 0x28, - VF_34 = 0x34 - }; - assert(vertexSize == 0x10 || vertexSize == 0x24 || vertexSize == 0x28 || vertexSize == 0x34); if (vertexSize == 0x10 || vertexSize == 0x24 || vertexSize == 0x28 || vertexSize == 0x34) { - VertexFormat format = static_cast(vertexSize); // NOLINT(*-use-auto) + mesh.vertexFormat = static_cast(vertexSize); - switch (format) + switch (mesh.vertexFormat) { - case VF_10: + case VertexFormat::VF_10: { for (uint32_t j = 0; j < vertexCount; j++) { glm::vec3& vertex = mesh.vertices.emplace_back(); - vertex.x = vertexReader.read(); - vertex.y = vertexReader.read(); - vertex.z = vertexReader.read(); + vertexReader.read(glm::value_ptr(vertex), 3); // Skip 4 bytes (it's some sort of data, but ignored by us) // TODO: Fix this! @@ -153,14 +151,12 @@ namespace gamelib::prm } } break; - case VF_24: + case VertexFormat::VF_24: { for (uint32_t j = 0; j < vertexCount; j++) { glm::vec3& vertex = mesh.vertices.emplace_back(); - vertex.x = vertexReader.read(); - vertex.y = vertexReader.read(); - vertex.z = vertexReader.read(); + vertexReader.read(glm::value_ptr(vertex), 3); // Skip another 0x10 useful info // TODO: Fix this! @@ -168,19 +164,17 @@ namespace gamelib::prm // Read UVs glm::vec2& uv = mesh.uvs.emplace_back(); - uv.x = vertexReader.read(); - uv.y = 1.f - vertexReader.read(); + vertexReader.read(glm::value_ptr(uv), 2); + uv.y = 1.f - uv.y; } } break; - case VF_28: + case VertexFormat::VF_28: { for (uint32_t j = 0; j < vertexCount; j++) { glm::vec3& vertex = mesh.vertices.emplace_back(); - vertex.x = vertexReader.read(); - vertex.y = vertexReader.read(); - vertex.z = vertexReader.read(); + vertexReader.read(glm::value_ptr(vertex), 3); // Skip another 0x10 useful info // TODO: Fix this! @@ -188,8 +182,8 @@ namespace gamelib::prm // Read UVs glm::vec2& uv = mesh.uvs.emplace_back(); - uv.x = vertexReader.read(); - uv.y = 1.f - vertexReader.read(); + vertexReader.read(glm::value_ptr(uv), 2); + uv.y = 1.f - uv.y; // Another seek // TODO: Fix this! @@ -197,28 +191,29 @@ namespace gamelib::prm } } break; - case VF_34: + case VertexFormat::VF_34: { glm::vec3& vertex = mesh.vertices.emplace_back(); - vertex.x = vertexReader.read(); - vertex.y = vertexReader.read(); - vertex.z = vertexReader.read(); + vertexReader.read(glm::value_ptr(vertex), 3); // TODO: Fix this! ZBioHelpers::seekBy(&vertexReader, 0x18); glm::vec2& uv = mesh.uvs.emplace_back(); - uv.x = vertexReader.read(); - uv.y = 1.f - vertexReader.read(); + vertexReader.read(glm::value_ptr(uv), 2); + uv.y = 1.f - uv.y; // TODO: Fix this ZBioHelpers::seekBy(&vertexReader, 0x8); } break; + default: + assert(false && "Unsupported format"); + break; } // Store triangle indices - for (uint32_t j = 0; j < trianglesCount / 3; j++) + for (uint32_t j = 0; j < mesh.trianglesCount / 3; j++) { prm::Index& index = mesh.indices.emplace_back(); prm::Index::deserialize(index, &trianglesReader); diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp index 089fc13..9415693 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp @@ -44,7 +44,7 @@ namespace gamelib chunk.data = std::make_unique(m_file.entries[i].size); // TODO: Need to check that data is valid (malloc is ok) - chunksReader.read(&chunk.data[0], m_file.entries[i].size); + chunksReader.read(chunk.data.get(), m_file.entries[i].size); // TODO: Need to refactor and use former header for chunk buffer instead of cropping few bytes (will fix later) // TODO: Need to use proper way to read bytes (endianness) @@ -95,8 +95,11 @@ namespace gamelib prm::Mesh currentMesh {}; prm::Mesh::deserialize(currentMesh, &meshEntryReader, m_file); - // Save mesh - model.meshes.emplace_back(std::move(currentMesh)); + if (currentMesh.lod & (uint8_t)1 == (uint8_t)1) + { + // Save mesh + model.meshes.emplace_back(std::move(currentMesh)); + } } } } From 4a56c4f7f7ed8a3595a92fd7f78f8caf6a924626 Mon Sep 17 00:00:00 2001 From: DronCode Date: Mon, 9 Oct 2023 20:11:51 +0300 Subject: [PATCH 14/80] Full material decompression support. --- .../Include/Widgets/SceneRenderWidget.h | 3 + .../Source/Widgets/SceneRenderWidget.cpp | 150 ++++++++++++++---- BMEdit/GameLib/Include/GameLib/Level.h | 6 +- BMEdit/GameLib/Include/GameLib/MAT/MATBind.h | 5 + .../Include/GameLib/MAT/MATColorChannel.h | 11 +- .../GameLib/Include/GameLib/MAT/MATEntries.h | 8 +- BMEdit/GameLib/Include/GameLib/MAT/MATFloat.h | 34 ++++ .../GameLib/Include/GameLib/MAT/MATOption.h | 7 +- .../Include/GameLib/MAT/MATRenderState.h | 8 +- .../GameLib/Include/GameLib/MAT/MATScroll.h | 29 ++++ .../GameLib/Include/GameLib/MAT/MATTexture.h | 15 +- .../Include/GameLib/MAT/MATTilingMode.h | 3 +- BMEdit/GameLib/Include/GameLib/MAT/MATValU.h | 33 ++++ BMEdit/GameLib/Source/GameLib/Level.cpp | 36 ++++- BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp | 25 +++ .../Source/GameLib/MAT/MATColorChannel.cpp | 58 ++----- .../GameLib/Source/GameLib/MAT/MATFloat.cpp | 51 ++++++ .../GameLib/Source/GameLib/MAT/MATLayer.cpp | 4 + .../GameLib/Source/GameLib/MAT/MATOption.cpp | 19 ++- .../GameLib/Source/GameLib/MAT/MATReader.cpp | 2 +- .../Source/GameLib/MAT/MATRenderState.cpp | 11 +- .../GameLib/Source/GameLib/MAT/MATScroll.cpp | 55 +++++++ .../GameLib/Source/GameLib/MAT/MATSprite.cpp | 4 + .../Source/GameLib/MAT/MATSubClass.cpp | 12 ++ .../GameLib/Source/GameLib/MAT/MATTexture.cpp | 57 ++++++- BMEdit/GameLib/Source/GameLib/MAT/MATValU.cpp | 71 +++++++++ 26 files changed, 600 insertions(+), 117 deletions(-) create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATFloat.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATScroll.h create mode 100644 BMEdit/GameLib/Include/GameLib/MAT/MATValU.h create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATFloat.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATScroll.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/MAT/MATValU.cpp diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 0029c58..9b6dbe5 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -190,6 +190,9 @@ namespace widgets void resourcesReady(); void resourceLoadFailed(const QString& reason); + public slots: + void onRedrawRequested(); + protected: void initializeGL() override; void paintGL() override; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index ef76e87..0f9adae 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -72,6 +72,7 @@ namespace widgets uint16_t height { 0 }; GLuint texture { kInvalidResource }; std::optional index {}; /// Index of texture from TEX container + std::optional texPath {}; /// [Optional] Path to texture in TEX container (path may not be defined in TEX!) void discard(QOpenGLFunctions_3_3_Core* gapi) { @@ -304,6 +305,7 @@ namespace widgets std::vector m_textures {}; std::vector m_shaders {}; std::vector m_models {}; + std::unordered_map m_modelsCache {}; /// primitive index to model index in m_models GLuint m_iGLDebugTexture { 0 }; size_t m_iTexturedShaderIdx = 0; size_t m_iGizmoShaderIdx = 0; @@ -313,6 +315,7 @@ namespace widgets void discard(QOpenGLFunctions_3_3_Core* gapi) { + // Destroy textures { for (auto& texture : m_textures) { @@ -322,6 +325,7 @@ namespace widgets m_textures.clear(); } + // Destroy shaders { for (auto& shader : m_shaders) { @@ -331,6 +335,7 @@ namespace widgets m_shaders.clear(); } + // Destroy models { for (auto& model : m_models) { @@ -340,7 +345,13 @@ namespace widgets m_models.clear(); } + // Empty cache + m_modelsCache.clear(); + + // Release refs m_iGLDebugTexture = 0u; + m_iTexturedShaderIdx = 0u; + m_iGizmoShaderIdx = 0u; } [[nodiscard]] bool hasResources() const @@ -383,11 +394,19 @@ namespace widgets } // Begin frame - funcs->glEnable(GL_DEPTH_TEST); funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); funcs->glClearColor(0.15f, 0.2f, 0.45f, 1.0f); - funcs->glDepthFunc(GL_ALWAYS); + // Z-Buffer testing + funcs->glEnable(GL_DEPTH_TEST); + funcs->glDepthFunc(GL_LESS); + + // Blending + funcs->glEnable(GL_BLEND); + funcs->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // NOTE: Before render anything we need to look at material and check MATRenderState. + // If it's applied we need to setup OpenGL into correct state to make perfect rendering switch (m_eState) { case ELevelState::LS_NONE: @@ -453,7 +472,11 @@ namespace widgets { bool bMoved = false; constexpr float kBaseDt = 1.f / 60.f; - constexpr float kSpeedUp = 100.f; + float kSpeedUp = 100.f; + + if (event->modifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) + kSpeedUp *= 4.f; + if (event->key() == Qt::Key_W) { @@ -607,6 +630,12 @@ namespace widgets repaint(); } + void SceneRenderWidget::onRedrawRequested() + { + if (m_pLevel) + repaint(); + } + #define LEVEL_SAFE_CHECK() \ if (!m_pLevel) \ { \ @@ -648,6 +677,7 @@ namespace widgets // Store texture index from TEX container newTexture.index = std::make_optional(texture.m_index); + newTexture.texPath = texture.m_fileName; // just copy file name from tex (if it defined!) // Create GL resource glFunctions->glGenTextures(1, &newTexture.texture); @@ -677,12 +707,16 @@ namespace widgets qDebug() << "All textures (" << m_pLevel->getSceneTextures()->entries.size() << ") are loaded and ready to be used"; m_eState = ELevelState::LS_LOAD_GEOMETRY; + repaint(); // call to force jump into next state } void SceneRenderWidget::doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions) { LEVEL_SAFE_CHECK() + // To avoid of future problems we've allocating null model at slot #0 and assign to it chunk #0 + m_resources->m_models.emplace_back().chunkId = 0; + // TODO: Optimize and load "chunk by chunk" for (const auto& model : m_pLevel->getLevelGeometry()->primitives.models) { @@ -697,6 +731,10 @@ namespace widgets GLResources::Model& glModel = m_resources->m_models.emplace_back(); glModel.chunkId = model.chunk; + // Store cache + m_resources->m_modelsCache[model.chunk] = m_resources->m_models.size() - 1; + + // Lookup mesh int meshIdx = 0; for (const auto& mesh : model.meshes) { @@ -790,16 +828,6 @@ namespace widgets // Shadows - do not use texturing (and don't show for now) glMesh.glTextureId = GLResources::kInvalidResource; } - else if (parentName == "Old" || parentName == "Glow") - { - // TODO: Impl later - glMesh.glTextureId = GLResources::kInvalidResource; - } - else if (matInstance.getBinders().empty()) - { - // No texture at all - glMesh.glTextureId = GLResources::kInvalidResource; - } else if (parentName == "Bad") { // Use 'bad' debug texture @@ -817,18 +845,46 @@ namespace widgets for (const auto& texture : binder.textures) { - if (texture.getName() == "mapDiffuse" && texture.getTextureId() != 0) + if (texture.getName() == "mapDiffuse" && (texture.getTextureId() != 0 || !texture.getTexturePath().empty())) { // And find texture in textures pool for (const auto& textureResource : m_resources->m_textures) { - if (textureResource.index.has_value() && textureResource.index.value() == texture.getTextureId()) + switch (texture.getPresentedTextureSources()) { - glMesh.glTextureId = textureResource.texture; - - // Ok, we are ready to show this - bTextureFound = true; + case gamelib::mat::PresentedTextureSource::PTS_NOTHING: + continue; // Nothing + + case gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID: + { + // Only texture id + if (textureResource.index.has_value() && textureResource.index.value() == texture.getTextureId()) + { + // Good + glMesh.glTextureId = textureResource.texture; + bTextureFound = true; + break; + } + } + break; + case gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH: + { + // Only path + if (textureResource.texPath.has_value() && textureResource.texPath.value() == texture.getTexturePath()) + { + // Good + glMesh.glTextureId = textureResource.texture; + bTextureFound = true; + break; + } + } break; + default: + { + // Bad case! Undefined behaviour! + assert(false && "Impossible case!"); + continue; + } } } @@ -846,6 +902,12 @@ namespace widgets } } } + + if (matInstance.getBinders().empty()) + { + // use debug texture + glMesh.glTextureId = m_resources->m_iGLDebugTexture; + } } } else if (mesh.textureId > 0) @@ -869,6 +931,7 @@ namespace widgets qDebug() << "All models (" << m_pLevel->getLevelGeometry()->primitives.models.size() << ") are loaded & ready to use!"; m_eState = ELevelState::LS_COMPILE_SHADERS; + repaint(); // call to force jump into next state } void SceneRenderWidget::doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions) @@ -1002,6 +1065,7 @@ void main() qDebug() << "Shaders (" << m_resources->m_shaders.size() << ") compiled and ready to use!"; m_eState = ELevelState::LS_RESET_CAMERA_STATE; + repaint(); // call to force jump into next state } void SceneRenderWidget::doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions) @@ -1028,6 +1092,7 @@ void main() emit resourcesReady(); m_eState = ELevelState::LS_READY; // Done! + repaint(); // call to force jump into next state } void SceneRenderWidget::doRenderScene(QOpenGLFunctions_3_3_Core* glFunctions) @@ -1040,10 +1105,35 @@ void main() updateProjectionMatrix(QWidget::width(), QWidget::height()); } - // Out ROOT is always first object. Start tree hierarchy visit from ROOT - const gamelib::scene::SceneObject::Ptr& root = m_pLevel->getSceneObjects()[0]; + // First of all we need to find ZBackdrop and render scene from this geom + m_pLevel->forEachObjectOfType("ZBackdrop", [this, glFunctions](const gamelib::scene::SceneObject::Ptr& sceneObject) -> bool { + doRenderGeom(glFunctions, sceneObject.get()); + + // Render only 1 ZBackdrop + return false; + }); - doRenderGeom(glFunctions, root.get()); + // Then we need to find our 'current room' + // How to find current room? Idk, let's find all 'rooms'? + std::vector roomsToRender; + roomsToRender.reserve(16); + + m_pLevel->forEachObjectOfTypeWithInheritance("ZROOM", [&roomsToRender](const gamelib::scene::SceneObject::Ptr& sceneObject) -> bool { + if (sceneObject->getName() != "ROOT" && sceneObject->getType()->getName() != "ZBackdrop") + { + // Save room + roomsToRender.emplace_back(sceneObject); + } + + // Render every room + return true; + }); + + // Ok, we have rooms to draw, let's draw 'em all + for (const auto& room : roomsToRender) + { + doRenderGeom(glFunctions, room.get()); + } } void SceneRenderWidget::discardResources(QOpenGLFunctions_3_3_Core* glFunctions) @@ -1066,7 +1156,6 @@ void main() const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); const auto mMatrix = geom->getProperties().getObject("Matrix", glm::mat3(1.f)); - const auto mMatrixT = glm::transpose(mMatrix); // Don't draw invisible things if (bInvisible && !bIgnoreVisibility) @@ -1078,23 +1167,16 @@ void main() if (primId != 0) { // Extract matrix from properties - const glm::mat4 transformMatrix = glm::mat4(mMatrixT); + const glm::mat4 transformMatrix = glm::mat4(glm::transpose(mMatrix)); const glm::mat4 translateMatrix = glm::translate(glm::mat4(1.f), vPosition); // Am I right here? const glm::mat4 modelMatrix = translateMatrix * transformMatrix; - // And bind required resources - // TODO: Optimize me - const auto& models = m_resources->m_models; - auto modelIt = std::find_if(models.begin(), models.end(), - [&primId](const GLResources::Model& model) -> bool { - return model.chunkId == (primId - 1); - }); - - if (modelIt != models.end()) + // RenderGod + if (m_resources->m_modelsCache.contains(primId)) { - const GLResources::Model& model = *modelIt; + const GLResources::Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; // Render all meshes for (const auto& mesh : model.meshes) diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 154d2e4..42a7440 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -67,10 +68,13 @@ namespace gamelib [[nodiscard]] const LevelMaterials* getLevelMaterials() const; [[nodiscard]] LevelMaterials* getLevelMaterials(); - [[nodiscard]] const std::vector &getSceneObjects() const; + [[nodiscard]] const std::vector& getSceneObjects() const; void dumpAsset(io::AssetKind assetKind, std::vector &outBuffer) const; + void forEachObjectOfType(const std::string& objectTypeName, const std::function& pred) const; + void forEachObjectOfTypeWithInheritance(const std::string& objectBaseType, const std::function& pred) const; + private: bool loadLevelProperties(); bool loadLevelScene(); diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h b/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h index 5321ed7..6627321 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATBind.h @@ -2,8 +2,10 @@ #include +#include #include #include +#include #include #include #include @@ -29,6 +31,9 @@ namespace gamelib::mat std::vector textures; std::vector colorChannels; std::vector sprites; + std::vector options; + std::vector scrolls; + std::vector floats; /// ... another fields? }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h b/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h index bed0c89..7f39c36 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATColorChannel.h @@ -1,9 +1,8 @@ #pragma once +#include #include #include -#include -#include namespace ZBio::ZBinaryReader { @@ -12,25 +11,23 @@ namespace ZBio::ZBinaryReader namespace gamelib::mat { - using MATColorRGBA = std::variant; - /** * @brief Present option for color channel (able to pass rgba as float & int values) */ class MATColorChannel { public: - MATColorChannel(std::string name, bool bEnabled, MATColorRGBA&& color); + MATColorChannel(std::string name, bool bEnabled, MATValU&& color); static MATColorChannel makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); [[nodiscard]] const std::string& getName() const; [[nodiscard]] bool isEnabled() const; - [[nodiscard]] const MATColorRGBA& getColor() const; + [[nodiscard]] const MATValU& getColor() const; private: std::string m_name {}; bool m_bEnabled { false }; - MATColorRGBA m_color {}; + MATValU m_color {}; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h b/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h index 7b56c38..c491d83 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATEntries.h @@ -76,7 +76,7 @@ namespace gamelib::mat PK_TEXTURE_ID = 0x54584944u, ///< [TRIVIAL] Hold uint32 value, in `reference` stored ID of texture (TEXEntry id, see TEX parser for details) PK_TILINIG_U = 0x54494C55u, ///< [STRREF] Reference to string represents U tiling mode. Possible values: NONE, TILED. See MATTilingMode enum for details PK_TILINIG_V = 0x54494C56u, ///< [STRREF] Reference to string represents V tiling mode. Possible values: NONE, TILED - PK_TILINIG_W = 0x574C4954u, ///< [STRREF] Reference to string represents W tiling mode. Possible values are unknown. Probably unused, but declared in Glacier1 code. + PK_TILINIG_W = 0x54494C57u, ///< [STRREF] Reference to string represents W tiling mode. Possible values are unknown. Probably unused, but declared in Glacier1 code. PK_VAL_I = 0x56414C49u, ///< [STRREF] Reference to string. I guess it's something about 'set boolean parameter by string literal'. `ReflectionEnabled` as example. PK_VAL_U = 0x56414C55u, ///< [TRIVIAL] Hold float or int value PK_VAL_F = 0x554C4156u, ///< Idk, unused @@ -84,13 +84,13 @@ namespace gamelib::mat PK_TEXTURE = 0x54455854u, ///< [MATTexture] Reference to list of properties represents texture (PK_PATH - path to texture/PK_TEXTURE_ID - index of texture) PK_COLOR = 0x434F4C4Fu, ///< [MATColorChannel] Reference to list of properties represents usage of color channel (as example v4IlluminationColor, presented via 2 properties: PK_NAME (name of channel) and PK_ENABLED) PK_BOOLEAN = 0x424F4F4Cu, ///< [MATOption] Reference to list of properties represents boolean flag. Presented via 3 properties: PK_NAME: AlphaFadeEnabled, PK_ENABLED - use parameter or not and PK_VAL_U - value of flag - PK_FLOAT_VALUE = 0x464C5456u, ///< Weird to see this here. OK, maybe supports (at least referenced) + PK_FLOAT_VALUE = 0x464C5456u, ///< [MATFloat] Reference to list of properties represents some float argument (or group of floats) PK_DMAP = 0x50414D44u, ///< Idk, unused (Glacier supports, but no usage) PK_FMIN = 0x4E494D46u, ///< Min filter (Idk, looks like legacy from OpenGL times) PK_FMAG = 0x47414D46u, ///< Mag filter (Idk, looks like legacy from OpenGL times) PK_FMIP = 0x50494D46u, ///< Idk, unused - PK_SCROLL = 0x4C524353u, ///< Idk, unused - PK_SCROLL_SPEED = 0x44455053u, ///< Idk, unused + PK_SCROLL = 0x5343524Cu, ///< [MATScroll] Some scrollable something... + PK_SCROLL_SPEED = 0x53504544u, ///< Idk, unused PK_ENUM = 0x4D554E45u ///< Idk, unused }; diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATFloat.h b/BMEdit/GameLib/Include/GameLib/MAT/MATFloat.h new file mode 100644 index 0000000..8ed449a --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATFloat.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + using IntOrFloat = std::variant; + + class MATFloat + { + public: + MATFloat(std::string name, bool bEnabled, MATValU&& valU); + + static MATFloat makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const { return m_name; } + [[nodiscard]] bool isEnabled() const { return m_bEnabled; } + [[nodiscard]] const MATValU& getValU() const { return m_valU; } + + private: + std::string m_name {}; + bool m_bEnabled { false }; + MATValU m_valU; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h b/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h index a27e6b8..d4e8030 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATOption.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -14,18 +15,18 @@ namespace gamelib::mat class MATOption { public: - MATOption(std::string name, bool bEnabled, bool bDefault); + MATOption(std::string name, bool bEnabled, MATValU&& valU); static MATOption makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); [[nodiscard]] const std::string& getName() const; [[nodiscard]] bool isEnabled() const; - [[nodiscard]] bool getDefault() const; + [[nodiscard]] const MATValU& getValU() const; private: std::string m_name{}; bool m_bEnabled {}; - bool m_bDefault {}; + MATValU m_valU {}; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h index 7076e7f..0f17b03 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h @@ -2,6 +2,7 @@ #include #include +#include #include @@ -19,12 +20,14 @@ namespace gamelib::mat bool bEnabled, bool bBlendEnabled, bool bAlphaTest, bool bFogEnabled, bool bZBias, float fOpacity, float fZOffset, uint32_t iAlphaReference, - MATCullMode cullMode, MATBlendMode blendMode): + MATCullMode cullMode, MATBlendMode blendMode, + MATValU&& valU): m_name(std::move(name)), m_bEnabled(bEnabled), m_bEnableBlend(bBlendEnabled), m_bAlphaTest(bAlphaTest), m_bFogEnabled(bFogEnabled), m_bZBias(bZBias), m_fOpacity(fOpacity), m_fZOffset(fZOffset), m_iAlphaReference(iAlphaReference), - m_eCullMode(cullMode), m_eBlendMode(blendMode) + m_eCullMode(cullMode), m_eBlendMode(blendMode), + m_valU(std::move(valU)) { } @@ -54,5 +57,6 @@ namespace gamelib::mat uint32_t m_iAlphaReference { 0u }; float m_fOpacity { 1.f }; float m_fZOffset { .0f }; + MATValU m_valU {}; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATScroll.h b/BMEdit/GameLib/Include/GameLib/MAT/MATScroll.h new file mode 100644 index 0000000..1ac2f7e --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATScroll.h @@ -0,0 +1,29 @@ +#pragma once + +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATScroll + { + public: + MATScroll(std::string name, bool bEnabled, std::vector&& speedVector); + + static MATScroll makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); + + [[nodiscard]] const std::string& getName() const { return m_name; } + [[nodiscard]] bool isEnabled() const { return m_bEnabled; } + [[nodiscard]] const std::vector& getSpeedVec() const { return m_vfSpeed; } + + private: + std::string m_name {}; + bool m_bEnabled { false }; + std::vector m_vfSpeed {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h b/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h index 4ab1e2a..b3ad8e9 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATTexture.h @@ -13,24 +13,37 @@ namespace ZBio::ZBinaryReader namespace gamelib::mat { + enum PresentedTextureSource : uint8_t + { + PTS_NOTHING, + PTS_TEXTURE_ID, + PTS_TEXTURE_PATH, + PTS_TEXTURE_ID_AND_PATH + }; + class MATTexture { public: - MATTexture(std::string name, bool bEnabled, uint32_t textureId, MATTilingMode tilingU, MATTilingMode tilingV); + MATTexture(std::string name, bool bEnabled, uint32_t textureId, std::string path, MATTilingMode tilingU, MATTilingMode tilingV, MATTilingMode tilingW); static MATTexture makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount); [[nodiscard]] const std::string& getName() const; [[nodiscard]] bool isEnabled() const; [[nodiscard]] uint32_t getTextureId() const; + [[nodiscard]] const std::string& getTexturePath() const; [[nodiscard]] MATTilingMode getTilingU() const; [[nodiscard]] MATTilingMode getTilingV() const; + [[nodiscard]] MATTilingMode getTilingW() const; + [[nodiscard]] PresentedTextureSource getPresentedTextureSources() const; private: std::string m_name {}; + std::string m_texturePath {}; std::uint32_t m_iTextureId { 0u }; bool m_bEnabled { false }; MATTilingMode m_tileU { MATTilingMode::TM_NONE }; MATTilingMode m_tileV { MATTilingMode::TM_NONE }; + MATTilingMode m_tileW { MATTilingMode::TM_NONE }; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h b/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h index 559baca..f7f00d7 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATTilingMode.h @@ -6,6 +6,7 @@ namespace gamelib::mat enum class MATTilingMode { TM_NONE, - TM_TILED + TM_TILED, + TM_MIRRORED }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATValU.h b/BMEdit/GameLib/Include/GameLib/MAT/MATValU.h new file mode 100644 index 0000000..4a7ff28 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATValU.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::mat +{ + class MATValU + { + public: + using Value = std::variant; + + MATValU(); + explicit MATValU(std::vector&& values); + + MATValU& operator=(std::vector&& values) noexcept; + + static MATValU makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, const MATPropertyEntry& selfDecl); + + [[nodiscard]] const std::vector& getValues() const { return m_values; } + + private: + std::vector m_values {}; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 67aa193..1a048bf 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -6,8 +6,9 @@ #include #include -#include #include +#include +#include namespace gamelib @@ -130,6 +131,39 @@ namespace gamelib } } + void Level::forEachObjectOfType(const std::string& objectTypeName, const std::function& pred) const + { + for (const auto& object: m_sceneObjects) + { + if (object->getType()->getName() == objectTypeName) + { + if (pred(object)) + continue; + + return; + } + } + } + + bool isComplexTypeInheritedOf(const std::string& baseTypeName, const TypeComplex* pType) + { + return pType != nullptr && (pType->getName() == baseTypeName || isComplexTypeInheritedOf(baseTypeName, reinterpret_cast(pType->getParent()))); + } + + void Level::forEachObjectOfTypeWithInheritance(const std::string& objectBaseType, const std::function& pred) const + { + for (const auto& object: m_sceneObjects) + { + if (object->getType()->getKind() == TypeKind::COMPLEX && isComplexTypeInheritedOf(objectBaseType, reinterpret_cast(object->getType()))) + { + if (pred(object)) + continue; + + return; + } + } + } + bool Level::loadLevelProperties() { int64_t prpFileSize = 0; diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp index 2efc579..9a36fcd 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATBind.cpp @@ -52,6 +52,31 @@ namespace gamelib::mat b.sprites.emplace_back(MATSprite::makeFromStream(binaryReader, entry.containerCapacity)); } + else if (entry.kind == MATPropertyKind::PK_BOOLEAN) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.options.emplace_back(MATOption::makeFromStream(binaryReader, entry.containerCapacity)); + } + else if (entry.kind == MATPropertyKind::PK_SCROLL) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.scrolls.emplace_back(MATScroll::makeFromStream(binaryReader, entry.containerCapacity)); + } + else if (entry.kind == MATPropertyKind::PK_FLOAT_VALUE) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + b.floats.emplace_back(MATFloat::makeFromStream(binaryReader, entry.containerCapacity)); + } + else + { + assert(false && "Unprocessed entry!"); + } } return b; diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp index a2afd1f..83e72ab 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATColorChannel.cpp @@ -7,7 +7,7 @@ namespace gamelib::mat { - MATColorChannel::MATColorChannel(std::string name, bool bEnabled, MATColorRGBA&& color) + MATColorChannel::MATColorChannel(std::string name, bool bEnabled, MATValU&& color) : m_name(std::move(name)), m_bEnabled(bEnabled), m_color(color) { } @@ -16,7 +16,7 @@ namespace gamelib::mat { std::string name {}; bool bEnabled { false }; - MATColorRGBA color; + MATValU color; for (int i = 0; i < propertiesCount; i++) { @@ -36,52 +36,12 @@ namespace gamelib::mat } else if (kind == MATPropertyKind::PK_VAL_U) { - // We will jump to unmarked region (raw buffer) - ZBioSeekGuard guard { binaryReader }; - binaryReader->seek(entry.reference); - - if (entry.containerCapacity == 3) - { - // RGB - if (entry.valueType == MATValueType::PT_UINT32) - { - color.emplace( - binaryReader->read(), - binaryReader->read(), - binaryReader->read() - ); - } - else if (entry.valueType == MATValueType::PT_FLOAT) - { - color.emplace( - binaryReader->read(), - binaryReader->read(), - binaryReader->read() - ); - } - } - else if (entry.containerCapacity == 4) - { - // RGBA - if (entry.valueType == MATValueType::PT_UINT32) - { - color.emplace( - binaryReader->read(), - binaryReader->read(), - binaryReader->read(), - binaryReader->read() - ); - } - else if (entry.valueType == MATValueType::PT_FLOAT) - { - color.emplace( - binaryReader->read(), - binaryReader->read(), - binaryReader->read(), - binaryReader->read() - ); - } - } + // Read ValU + color = MATValU::makeFromStream(binaryReader, entry); + } + else + { + assert(false && "Unprocessed entry!"); } } @@ -98,7 +58,7 @@ namespace gamelib::mat return m_bEnabled; } - const MATColorRGBA& MATColorChannel::getColor() const + const MATValU& MATColorChannel::getColor() const { return m_color; } diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATFloat.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATFloat.cpp new file mode 100644 index 0000000..3fda106 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATFloat.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATFloat::MATFloat(std::string name, bool bEnabled, MATValU&& valU) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_valU(std::move(valU)) + { + } + + MATFloat MATFloat::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bEnabled { false }; + MATValU valU {}; + + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (entry.kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_ENABLE) + { + bEnabled = static_cast(entry.reference); + } + else if (entry.kind == MATPropertyKind::PK_VAL_U) + { + // Make ValU by right way + valU = MATValU::makeFromStream(binaryReader, entry); + } + else + { + assert(false && "Unprocessed case"); + } + } + + return MATFloat(std::move(name), bEnabled, std::move(valU)); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp index e3f46ee..d9f0779 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATLayer.cpp @@ -68,6 +68,10 @@ namespace gamelib::mat { valI = binaryReader->readCString(); } + else + { + assert(false && "Unprocessed entry!"); + } } return MATLayer(std::move(name), std::move(type), std::move(shaderPath), std::move(identity), std::move(valI)); diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp index 3dc36ac..0d0729c 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATOption.cpp @@ -7,8 +7,8 @@ namespace gamelib::mat { - MATOption::MATOption(std::string name, bool bEnabled, bool bDefault) - : m_name(std::move(name)), m_bEnabled(bEnabled), m_bDefault(bDefault) + MATOption::MATOption(std::string name, bool bEnabled, MATValU&& valU) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_valU(std::move(valU)) { } @@ -22,15 +22,16 @@ namespace gamelib::mat return m_bEnabled; } - bool MATOption::getDefault() const + const MATValU& MATOption::getValU() const { - return m_bDefault; + return m_valU; } MATOption MATOption::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) { std::string name {}; - bool bEnabled = false, bDefault = false; + bool bEnabled = false; + MATValU valU {}; for (int i = 0; i < propertiesCount; i++) { @@ -50,10 +51,14 @@ namespace gamelib::mat } else if (kind == MATPropertyKind::PK_VAL_U) { - bDefault = static_cast(entry.reference); + valU = MATValU::makeFromStream(binaryReader, entry); + } + else + { + assert(false && "Unprocessed entry!"); } } - return MATOption(std::move(name), bEnabled, bDefault); + return MATOption(std::move(name), bEnabled, std::move(valU)); } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp index 66e5c05..e03cb1b 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATReader.cpp @@ -214,7 +214,7 @@ namespace gamelib::mat matReader->seek(properties[i].reference); - binders.emplace_back(MATBind::makeFromStream(matReader, properties[i].containerCapacity)); + binders.emplace_back(MATBind::makeFromStream(matReader, static_cast(properties[i].containerCapacity))); } } diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp index 3cfed2d..e3a72b5 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATRenderState.cpp @@ -14,6 +14,7 @@ namespace gamelib::mat uint32_t iAlphaReference { 0u }; MATCullMode cullMode { MATCullMode::CM_DontCare }; MATBlendMode blendMode { MATBlendMode::BM_ADD }; + MATValU valU {}; for (int i = 0; i < propertiesCount; i++) { @@ -116,11 +117,19 @@ namespace gamelib::mat } } break; + case MATPropertyKind::PK_VAL_U: + { + assert(valU.getValues().empty() && "Must be empty here!"); + + valU = MATValU::makeFromStream(binaryReader, entry); + } + break; default: + assert(false && "Unprocessed entry!"); break; } } - return MATRenderState(std::move(name), bEnabled, bBlendEnabled, bAlphaTest, bFogEnabled, bZBias, fOpacity, fZOffset, iAlphaReference, cullMode, blendMode); + return MATRenderState(std::move(name), bEnabled, bBlendEnabled, bAlphaTest, bFogEnabled, bZBias, fOpacity, fZOffset, iAlphaReference, cullMode, blendMode, std::move(valU)); } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATScroll.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATScroll.cpp new file mode 100644 index 0000000..9898b14 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATScroll.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::mat +{ + MATScroll::MATScroll(std::string name, bool bEnabled, std::vector&& speedVector) + : m_name(std::move(name)), m_bEnabled(bEnabled), m_vfSpeed(std::move(speedVector)) + { + } + + MATScroll MATScroll::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) + { + std::string name {}; + bool bEnabled { false }; + std::vector speedVector {}; + + for (int i = 0; i < propertiesCount; i++) + { + MATPropertyEntry entry; + MATPropertyEntry::deserialize(entry, binaryReader); + + if (entry.kind == MATPropertyKind::PK_NAME) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + name = binaryReader->readCString(); + } + else if (entry.kind == MATPropertyKind::PK_ENABLE) + { + bEnabled = static_cast(entry.reference); + } + else if (entry.kind == MATPropertyKind::PK_SCROLL_SPEED) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + assert(speedVector.empty() && "It should be empty here!"); + + speedVector.resize(entry.containerCapacity); + binaryReader->read(speedVector.data(), entry.containerCapacity); + } + else + { + assert(false && "Unprocessed case"); + } + } + + return MATScroll(std::move(name), bEnabled, std::move(speedVector)); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp index e721298..644e956 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATSprite.cpp @@ -38,6 +38,10 @@ namespace gamelib::mat ++lastUpdatedUnk; } + else + { + assert(false && "Unprocessed entry!"); + } } return MATSprite(std::move(name), bUnk0, bUnk1); diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp index 673ead7..091905e 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATSubClass.cpp @@ -11,6 +11,7 @@ namespace gamelib::mat { std::string name {}, oTyp {}, sTyp {}; std::vector layers {}; + std::vector valI {}; for (int i = 0; i < propertiesCount; i++) { @@ -46,6 +47,17 @@ namespace gamelib::mat layers.emplace_back(MATLayer::makeFromStream(binaryReader, entry.containerCapacity)); } + else if (entry.kind == MATPropertyKind::PK_VAL_I) + { + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + valI.emplace_back(binaryReader->readCString()); + } + else + { + assert(false && "Unprocessed entry!"); + } } return MATSubClass(std::move(name), std::move(oTyp), std::move(sTyp), std::move(layers)); diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp index e7a962f..bc4befb 100644 --- a/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATTexture.cpp @@ -7,17 +7,18 @@ namespace gamelib::mat { - MATTexture::MATTexture(std::string name, bool bEnabled, uint32_t textureId, MATTilingMode tilingU, MATTilingMode tilingV) - : m_name(std::move(name)), m_bEnabled(bEnabled), m_iTextureId(textureId), m_tileU(tilingU), m_tileV(tilingV) + MATTexture::MATTexture(std::string name, bool bEnabled, uint32_t textureId, std::string path, MATTilingMode tilingU, MATTilingMode tilingV, MATTilingMode tilingW) + : m_name(std::move(name)), m_texturePath(std::move(path)), m_bEnabled(bEnabled), m_iTextureId(textureId), m_tileU(tilingU), m_tileV(tilingV), m_tileW(tilingW) { } MATTexture MATTexture::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, int propertiesCount) { std::string name{}; + std::string path{}; bool bEnabled{false}; uint32_t textureId {0}; - MATTilingMode tileU { MATTilingMode::TM_NONE }, tileV { MATTilingMode::TM_NONE }; + MATTilingMode tileU { MATTilingMode::TM_NONE }, tileV { MATTilingMode::TM_NONE }, tileW { MATTilingMode::TM_NONE }; for (int i = 0; i < propertiesCount; i++) { @@ -39,7 +40,7 @@ namespace gamelib::mat { textureId = entry.reference; } - else if (kind == MATPropertyKind::PK_TILINIG_U || kind == MATPropertyKind::PK_TILINIG_V) + else if (kind == MATPropertyKind::PK_TILINIG_U || kind == MATPropertyKind::PK_TILINIG_V || kind == MATPropertyKind::PK_TILINIG_W) { ZBioSeekGuard guard { binaryReader }; binaryReader->seek(entry.reference); @@ -55,6 +56,10 @@ namespace gamelib::mat { tempMode = MATTilingMode::TM_TILED; } + else if (temp == "MIRRORED") + { + tempMode = MATTilingMode::TM_MIRRORED; + } else { assert(false && "Unsupported mode!"); @@ -63,10 +68,23 @@ namespace gamelib::mat if (kind == MATPropertyKind::PK_TILINIG_U) tileU = tempMode; if (kind == MATPropertyKind::PK_TILINIG_V) tileV = tempMode; + if (kind == MATPropertyKind::PK_TILINIG_W) tileW = tempMode; + } + else if (kind == MATPropertyKind::PK_PATH) + { + // Path to the texture + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(entry.reference); + + path = binaryReader->readCString(); + } + else + { + assert(false && "Unsupported entry! Need to support!"); } } - return MATTexture(std::move(name), bEnabled, textureId, tileU, tileV); + return MATTexture(std::move(name), bEnabled, textureId, path, tileU, tileV, tileW); } const std::string& MATTexture::getName() const @@ -74,11 +92,21 @@ namespace gamelib::mat return m_name; } + bool MATTexture::isEnabled() const + { + return m_bEnabled; + } + uint32_t MATTexture::getTextureId() const { return m_iTextureId; } + const std::string& MATTexture::getTexturePath() const + { + return m_texturePath; + } + MATTilingMode MATTexture::getTilingU() const { return m_tileU; @@ -88,4 +116,23 @@ namespace gamelib::mat { return m_tileV; } + + MATTilingMode MATTexture::getTilingW() const + { + return m_tileW; + } + + PresentedTextureSource MATTexture::getPresentedTextureSources() const + { + if (m_texturePath.empty() && m_iTextureId == 0) + return PresentedTextureSource::PTS_NOTHING; + + if (m_texturePath.empty() && m_iTextureId > 0) + return PresentedTextureSource::PTS_TEXTURE_ID; + + if (!m_texturePath.empty() && m_iTextureId == 0) + return PresentedTextureSource::PTS_TEXTURE_PATH; + + return PresentedTextureSource::PTS_TEXTURE_ID_AND_PATH; + } } diff --git a/BMEdit/GameLib/Source/GameLib/MAT/MATValU.cpp b/BMEdit/GameLib/Source/GameLib/MAT/MATValU.cpp new file mode 100644 index 0000000..987c55e --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/MAT/MATValU.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + + + +namespace gamelib::mat +{ + MATValU::MATValU() = default; + + MATValU::MATValU(std::vector&& values) : m_values(std::move(values)) + { + } + + MATValU &MATValU::operator=(std::vector &&values) noexcept + { + m_values = std::move(values); + return *this; + } + + MATValU MATValU::makeFromStream(ZBio::ZBinaryReader::BinaryReader* binaryReader, const MATPropertyEntry& selfDecl) + { + assert(selfDecl.kind == MATPropertyKind::PK_VAL_U); + + std::vector values {}; + values.resize(selfDecl.containerCapacity); + + if (selfDecl.containerCapacity == 1) + { + // Parse single value + if (selfDecl.valueType == MATValueType::PT_UINT32) + { + // Single uint32 + values[0] = static_cast(selfDecl.reference); + } + else if (selfDecl.valueType == MATValueType::PT_FLOAT) + { + // Single float + values[0] = static_cast(selfDecl.reference); + } + else + { + assert(false && "Unsupported & impossible case!"); + } + } + else + { + // Parse group + ZBioSeekGuard guard { binaryReader }; + binaryReader->seek(selfDecl.reference); + + for (int i = 0; i < selfDecl.containerCapacity; i++) + { + if (selfDecl.valueType == MATValueType::PT_UINT32) + { + values[i] = binaryReader->read(); + } + else if (selfDecl.valueType == MATValueType::PT_FLOAT) + { + values[i] = binaryReader->read(); + } + else + { + assert(false && "Unsupported & impossible case!"); + } + } + } + + return MATValU(std::move(values)); + } +} \ No newline at end of file From 194edec57a17c067df0bf85ab12f37decaee6290 Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 10 Oct 2023 11:30:13 +0300 Subject: [PATCH 15/80] Add debug textures, move shader sources our of C++ code, partially fixed transforms on map. --- BMEdit/Editor/Resources/BMEdit.qrc | 8 + .../Resources/Shaders/ColoredEntity_GL33.fsh | 15 ++ .../Resources/Shaders/ColoredEntity_GL33.vsh | 30 +++ .../Resources/Shaders/TexturedEntity_GL33.fsh | 16 ++ .../Resources/Shaders/TexturedEntity_GL33.vsh | 35 +++ .../Resources/Textures/missing_texture.png | Bin 0 -> 2743 bytes .../Textures/unsupported_material.png | Bin 0 -> 2743 bytes .../Source/Widgets/SceneRenderWidget.cpp | 208 +++++++++--------- BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 3 + .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 5 +- README.md | 1 + 11 files changed, 210 insertions(+), 111 deletions(-) create mode 100644 BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh create mode 100644 BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.vsh create mode 100644 BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh create mode 100644 BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh create mode 100644 BMEdit/Editor/Resources/Textures/missing_texture.png create mode 100644 BMEdit/Editor/Resources/Textures/unsupported_material.png diff --git a/BMEdit/Editor/Resources/BMEdit.qrc b/BMEdit/Editor/Resources/BMEdit.qrc index d77923a..e6a5813 100644 --- a/BMEdit/Editor/Resources/BMEdit.qrc +++ b/BMEdit/Editor/Resources/BMEdit.qrc @@ -11,5 +11,13 @@ Icons/Glacier/Weapon.png Icons/Glacier/Cloth.png Icons/Glacier/Unknown.png + + Textures/unsupported_material.png + Textures/missing_texture.png + + Shaders/ColoredEntity_GL33.vsh + Shaders/ColoredEntity_GL33.fsh + Shaders/TexturedEntity_GL33.vsh + Shaders/TexturedEntity_GL33.fsh \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh new file mode 100644 index 0000000..4fbbac2 --- /dev/null +++ b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh @@ -0,0 +1,15 @@ +#version 330 core +// +// This file is a part of BMEdit project +// Description: Basic shader to render color filled entity & gizmo +// + +uniform vec4 i_Color; + +// Out +out vec4 o_FragColor; + +void main() +{ + o_FragColor = i_Color; +} \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.vsh b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.vsh new file mode 100644 index 0000000..cd0c076 --- /dev/null +++ b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.vsh @@ -0,0 +1,30 @@ +#version 330 core +// +// This file is a part of BMEdit project +// Description: Basic shader to render color filled entity & gizmo +// + +// Layout +layout (location = 0) in vec3 aPos; + +// Common +struct Camera +{ + mat4 proj; + mat4 view; + ivec2 resolution; +}; + +struct Transform +{ + mat4 model; +}; + +// Uniforms +uniform Camera i_uCamera; +uniform Transform i_uTransform; + +void main() +{ + gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); +} \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh new file mode 100644 index 0000000..de6e6a1 --- /dev/null +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh @@ -0,0 +1,16 @@ +#version 330 core +// +// This file is a part of BMEdit project +// Description: Basic shader to render textured entity +// + +uniform sampler2D i_ActiveTexture; +in vec2 g_TexCoord; + +// Out +out vec4 o_FragColor; + +void main() +{ + o_FragColor = texture(i_ActiveTexture, g_TexCoord); +} \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh new file mode 100644 index 0000000..8a90dbd --- /dev/null +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh @@ -0,0 +1,35 @@ +#version 330 core +// +// This file is a part of BMEdit project +// Description: Basic shader to render textured entity +// + +// Layout +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aUV; + +// Common +struct Camera +{ + mat4 proj; + mat4 view; + ivec2 resolution; +}; + +struct Transform +{ + mat4 model; +}; + +// Uniforms +uniform Camera i_uCamera; +uniform Transform i_uTransform; + +// Out +out vec2 g_TexCoord; + +void main() +{ + gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); + g_TexCoord = aUV; +} \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Textures/missing_texture.png b/BMEdit/Editor/Resources/Textures/missing_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..07cfc4120c3a00e5b7b6c55d700ca5fc4cdbdf82 GIT binary patch literal 2743 zcmeAS@N?(olHy`uVBq!ia0y~yU;#2&7+9ErRIjnw1`sdZ(btiIVPik{pF~z5pFhAS z#PyYN^nc^{|Ns9#Wia~BV6pU|p&0`MSFopxV@SoVx7Qqbk2vtKI0hTEE*9W=*#FYu zhhTKDn!&43+ii7vzfK7+VXjE~&iDRj*?#Ta@<2030jVL7Al-1A@$%nSZ|X?ZIjSBO z0u8nddF(#lcW?U52ooBmNezK-3_D~E?yf)gj-6DUqw0YnaD#0@dide&-|_F585-Dq z?y)m8+@3$zopr05tX?}hy5=d zeh5Yfs~NlswcS>y_v@7K66T7u?|konmhIQxEe|wv6p$JM3DOO>8882R^`?$gouleu zA<$sUkjL)xefOr{j4+{5n$!^Z#;`-y;O_c!@7PJzIjSBQ0yo$eq=z5Q{vH30nW2H* z=N>yl!|nMqUe<+|*^h<~86j}MjA1+Tvfo#4{v|{6s1`^FR5RS+oAItZ^*g8;Fbc>D xfj5i=)(N}f=icQfOZ!OIw1Kyw+m7MTd;Wm6k$T6x#Oy!;;_2$=vd$@?2>{SvWGesw literal 0 HcmV?d00001 diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0f9adae..5dce3a7 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -3,7 +3,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -307,6 +310,8 @@ namespace widgets std::vector m_models {}; std::unordered_map m_modelsCache {}; /// primitive index to model index in m_models GLuint m_iGLDebugTexture { 0 }; + GLuint m_iGLMissingTexture { 0 }; + GLuint m_iGLUnsupportedMaterialTexture { 0 }; size_t m_iTexturedShaderIdx = 0; size_t m_iGizmoShaderIdx = 0; @@ -350,6 +355,8 @@ namespace widgets // Release refs m_iGLDebugTexture = 0u; + m_iGLMissingTexture = 0u; + m_iGLUnsupportedMaterialTexture = 0u; m_iTexturedShaderIdx = 0u; m_iGizmoShaderIdx = 0u; } @@ -586,7 +593,6 @@ namespace widgets { m_eViewMode = EViewMode::VM_GEOM_PREVIEW; m_pSceneObjectToView = sceneObject; - m_camera.setPosition(glm::vec3(0.f)); repaint(); } } @@ -595,7 +601,6 @@ namespace widgets { m_eViewMode = EViewMode::VM_WORLD_VIEW; m_pSceneObjectToView = nullptr; - m_camera.setPosition(glm::vec3(0.f)); repaint(); } @@ -705,6 +710,48 @@ namespace widgets m_resources->m_textures.emplace_back(newTexture); } + // And load extra textures (render specific) + auto uploadQImageToGPU = [](QOpenGLFunctions_3_3_Core* gapi, const QImage& image) -> GLuint + { + GLuint textureId; + gapi->glGenTextures(1, &textureId); + gapi->glBindTexture(GL_TEXTURE_2D, textureId); + gapi->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); + + gapi->glGenerateMipmap(GL_TEXTURE_2D); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + gapi->glBindTexture(GL_TEXTURE_2D, 0); + + return textureId; + }; + + { + QImage missingTextureImage = QImage(":/bmedit/mtl_missing_texture.png").convertToFormat(QImage::Format_RGBA8888, Qt::AutoColor); + + auto& missingTexture = m_resources->m_textures.emplace_back(); + missingTexture.texture = uploadQImageToGPU(glFunctions, missingTextureImage); + missingTexture.width = missingTextureImage.width(); + missingTexture.height = missingTextureImage.height(); + + m_resources->m_iGLMissingTexture = missingTexture.texture; + } + + { + QImage unsupportedMaterialTextureImage = QImage(":/bmedit/mtl_unsupported.png").convertToFormat(QImage::Format_RGBA8888, Qt::AutoColor); + + auto& unsupportedMaterial = m_resources->m_textures.emplace_back(); + unsupportedMaterial.texture = uploadQImageToGPU(glFunctions, unsupportedMaterialTextureImage); + unsupportedMaterial.width = unsupportedMaterialTextureImage.width(); + unsupportedMaterial.height = unsupportedMaterialTextureImage.height(); + + m_resources->m_iGLUnsupportedMaterialTexture = unsupportedMaterial.texture; + } + + // It's done qDebug() << "All textures (" << m_pLevel->getSceneTextures()->entries.size() << ") are loaded and ready to be used"; m_eState = ELevelState::LS_LOAD_GEOMETRY; repaint(); // call to force jump into next state @@ -831,7 +878,7 @@ namespace widgets else if (parentName == "Bad") { // Use 'bad' debug texture - glMesh.glTextureId = m_resources->m_iGLDebugTexture; + glMesh.glTextureId = m_resources->m_iGLUnsupportedMaterialTexture; } else { @@ -891,7 +938,7 @@ namespace widgets if (!bTextureFound) { // Use error texture - glMesh.glTextureId = m_resources->m_iGLDebugTexture; + glMesh.glTextureId = m_resources->m_iGLMissingTexture; } // But mark us as 'found' @@ -903,11 +950,11 @@ namespace widgets } } - if (matInstance.getBinders().empty()) - { - // use debug texture - glMesh.glTextureId = m_resources->m_iGLDebugTexture; - } + // For debug only +// if (glMesh.glTextureId == GLResources::kInvalidResource) +// { +// glMesh.glTextureId = m_resources->m_iGLMissingTexture; +// } } } else if (mesh.textureId > 0) @@ -938,104 +985,23 @@ namespace widgets { LEVEL_SAFE_CHECK() - // TODO: In future we will use shaders from FS, but now I need to debug this stuff easier - static constexpr const char* kTexturedVertexShader = R"( -#version 330 core - -// Layout -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec2 aUV; - -// Common -struct Camera -{ - mat4 proj; - mat4 view; - ivec2 resolution; -}; - -struct Transform -{ - mat4 model; -}; - -// Uniforms -uniform Camera i_uCamera; -uniform Transform i_uTransform; + // Load shaders from resources + QFile coloredEntityVertexShader(":/bmedit/mtl_colored_gl33.vsh"); + QFile coloredEntityFragmentShader(":/bmedit/mtl_colored_gl33.fsh"); + QFile texturedEntityVertexShader(":/bmedit/mtl_textured_gl33.vsh"); + QFile texturedEntityFragmentShader(":/bmedit/mtl_textured_gl33.fsh"); -// Out -out vec2 g_TexCoord; - -void main() -{ - gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); - g_TexCoord = aUV; -} -)"; - - static constexpr const char* kTexturedFragmentShader = R"( -#version 330 core - -uniform sampler2D i_ActiveTexture; -in vec2 g_TexCoord; - -// Out -out vec4 o_FragColor; - -void main() -{ - o_FragColor = texture(i_ActiveTexture, g_TexCoord); -} -)"; - - static constexpr const char* kGizmoVertexShader = R"( -#version 330 core - -// Layout -layout (location = 0) in vec3 aPos; - -// Common -struct Camera -{ - mat4 proj; - mat4 view; - ivec2 resolution; -}; - -struct Transform -{ - mat4 model; -}; - -// Uniforms -uniform Camera i_uCamera; -uniform Transform i_uTransform; - -void main() -{ - gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); -} -)"; - - static constexpr const char* kGizmoFragmentShader = R"( -#version 330 core - -uniform vec4 i_Color; - -// Out -out vec4 o_FragColor; - -void main() -{ - o_FragColor = i_Color; -} -)"; + coloredEntityVertexShader.open(QIODevice::ReadOnly); + coloredEntityFragmentShader.open(QIODevice::ReadOnly); + texturedEntityVertexShader.open(QIODevice::ReadOnly); + texturedEntityFragmentShader.open(QIODevice::ReadOnly); // Compile shaders std::string compileError; { GLResources::Shader texturedShader; - if (!texturedShader.compile(glFunctions, kTexturedVertexShader, kTexturedFragmentShader, compileError)) + + if (!texturedShader.compile(glFunctions, texturedEntityVertexShader.readAll().toStdString(), texturedEntityFragmentShader.readAll().toStdString(), compileError)) { m_pLevel = nullptr; m_eState = ELevelState::LS_NONE; @@ -1050,7 +1016,7 @@ void main() { GLResources::Shader gizmoShader; - if (!gizmoShader.compile(glFunctions, kGizmoVertexShader, kGizmoFragmentShader, compileError)) + if (!gizmoShader.compile(glFunctions, coloredEntityVertexShader.readAll().toStdString(), texturedEntityFragmentShader.readAll().toStdString(), compileError)) { m_pLevel = nullptr; m_eState = ELevelState::LS_NONE; @@ -1167,11 +1133,37 @@ void main() if (primId != 0) { // Extract matrix from properties - const glm::mat4 transformMatrix = glm::mat4(glm::transpose(mMatrix)); - const glm::mat4 translateMatrix = glm::translate(glm::mat4(1.f), vPosition); - - // Am I right here? - const glm::mat4 modelMatrix = translateMatrix * transformMatrix; + glm::mat4 mTransform = glm::mat4(1.f); + + /** + * Convert from DX9 to OpenGL + * + * | m00 m10 m20 | + * mSrc = | m01 m11 m21 | + * | m02 m12 m22 | + * + * | m02 m01 m00 | + * mDst = | m12 m11 m21 | + * | m22 m21 m20 | + * + * TODO: Need to find how to fix X flipping of map (or missrotating, idk) + */ + mTransform[0][0] = mMatrix[0][2]; + mTransform[1][0] = mMatrix[0][1]; + mTransform[2][0] = mMatrix[0][0]; + + mTransform[0][1] = mMatrix[1][2]; + mTransform[1][1] = mMatrix[1][1]; + mTransform[2][1] = mMatrix[2][1]; + + mTransform[0][2] = mMatrix[2][2]; + mTransform[1][2] = mMatrix[2][1]; + mTransform[2][2] = mMatrix[2][0]; + + mTransform[3][3] = 1.f; + + glm::mat4 mTranslate = glm::translate(glm::mat4(1.f), vPosition); + glm::mat4 mModelMatrix = mTranslate * mTransform; // RenderGod if (m_resources->m_modelsCache.contains(primId)) @@ -1196,7 +1188,7 @@ void main() texturedShader.bind(glFunctions); // 2. Submit uniforms - texturedShader.setUniform(glFunctions, "i_uTransform.model", modelMatrix); + texturedShader.setUniform(glFunctions, "i_uTransform.model", mModelMatrix); texturedShader.setUniform(glFunctions, "i_uCamera.proj", m_matProjection); texturedShader.setUniform(glFunctions, "i_uCamera.view", m_camera.getViewMatrix()); texturedShader.setUniform(glFunctions, "i_uCamera.resolution", viewResolution); diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index e5f09c0..12c68ee 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -357,6 +357,9 @@ Open level + + Ctrl+O + diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index d083612..35d2590 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -145,9 +145,8 @@ namespace gamelib::prm glm::vec3& vertex = mesh.vertices.emplace_back(); vertexReader.read(glm::value_ptr(vertex), 3); - // Skip 4 bytes (it's some sort of data, but ignored by us) - // TODO: Fix this! - ZBioHelpers::seekBy(&vertexReader, 0x4); + uint8_t l[4] { 0, 0, 0, 0 }; + vertexReader.read(&l[0], 4); } } break; diff --git a/README.md b/README.md index c7a42a0..e07dcba 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Dependencies * [Nlohmann JSON](https://github.com/nlohmann/json) * [zlib](https://github.com/madler/zlib) * [conan](https://conan.io) (see "Build" for details) + * [Kenney Prototype Textures](https://www.kenney.nl/assets/prototype-textures) Build ----- From 54d6bfd999c9bb273e64c9b9904967aa60b95a9f Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 10 Oct 2023 16:00:34 +0300 Subject: [PATCH 16/80] Fix shader loader --- .../Source/Widgets/SceneRenderWidget.cpp | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 5dce3a7..a2b36b1 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -374,12 +374,6 @@ namespace widgets }; SceneRenderWidget::SceneRenderWidget(QWidget *parent, Qt::WindowFlags f) : QOpenGLWidget(parent, f) - { - } - - SceneRenderWidget::~SceneRenderWidget() noexcept = default; - - void SceneRenderWidget::initializeGL() { QSurfaceFormat format; format.setDepthBufferSize(24); @@ -387,7 +381,12 @@ namespace widgets format.setVersion(3, 3); format.setProfile(QSurfaceFormat::CoreProfile); setFormat(format); + } + SceneRenderWidget::~SceneRenderWidget() noexcept = default; + + void SceneRenderWidget::initializeGL() + { // Create resource holder m_resources = std::make_unique(); } @@ -996,12 +995,41 @@ namespace widgets texturedEntityVertexShader.open(QIODevice::ReadOnly); texturedEntityFragmentShader.open(QIODevice::ReadOnly); + const std::string texturedEntityVertexShaderSource = texturedEntityVertexShader.readAll().toStdString(); + const std::string texturedEntityFragmentShaderSource = texturedEntityFragmentShader.readAll().toStdString(); + const std::string coloredEntityVertexShaderSource = coloredEntityVertexShader.readAll().toStdString(); + const std::string coloredEntityFragmentShaderSource = coloredEntityFragmentShader.readAll().toStdString(); + + if (texturedEntityVertexShaderSource.empty()) + { + emit resourceLoadFailed(QString("Failed to compile shaders (textured:vertex): no embedded asset found.")); + return; + } + + if (texturedEntityFragmentShaderSource.empty()) + { + emit resourceLoadFailed(QString("Failed to compile shaders (textured:fragment): no embedded asset found.")); + return; + } + + if (coloredEntityVertexShaderSource.empty()) + { + emit resourceLoadFailed(QString("Failed to compile shaders (colored:vertex): no embedded asset found.")); + return; + } + + if (coloredEntityFragmentShaderSource.empty()) + { + emit resourceLoadFailed(QString("Failed to compile shaders (colored:fragment): no embedded asset found.")); + return; + } + // Compile shaders std::string compileError; { GLResources::Shader texturedShader; - if (!texturedShader.compile(glFunctions, texturedEntityVertexShader.readAll().toStdString(), texturedEntityFragmentShader.readAll().toStdString(), compileError)) + if (!texturedShader.compile(glFunctions, texturedEntityVertexShaderSource, texturedEntityFragmentShaderSource, compileError)) { m_pLevel = nullptr; m_eState = ELevelState::LS_NONE; @@ -1016,12 +1044,12 @@ namespace widgets { GLResources::Shader gizmoShader; - if (!gizmoShader.compile(glFunctions, coloredEntityVertexShader.readAll().toStdString(), texturedEntityFragmentShader.readAll().toStdString(), compileError)) + if (!gizmoShader.compile(glFunctions, coloredEntityVertexShaderSource, coloredEntityFragmentShaderSource, compileError)) { m_pLevel = nullptr; m_eState = ELevelState::LS_NONE; - emit resourceLoadFailed(QString("Failed to compile shaders (gizmo): %1").arg(QString::fromStdString(compileError))); + emit resourceLoadFailed(QString("Failed to compile shaders (colored): %1").arg(QString::fromStdString(compileError))); return; } From 92cdfba1e241804a83f59ae7fad7eefe05bb76c8 Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 10 Oct 2023 16:23:03 +0300 Subject: [PATCH 17/80] Finally fixed transforms, movement and few little bugs in camera control --- BMEdit/Editor/Include/Widgets/SceneRenderWidget.h | 4 ++-- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 9b6dbe5..d2c79da 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -87,9 +87,9 @@ namespace renderer { float velocity = MovementSpeed * deltaTime * moveScale; if (direction == FORWARD) - Position += Front * velocity; - if (direction == BACKWARD) Position -= Front * velocity; + if (direction == BACKWARD) + Position += Front * velocity; if (direction == LEFT) Position -= Right * velocity; if (direction == RIGHT) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index a2b36b1..4818fcd 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -553,11 +553,14 @@ namespace widgets void SceneRenderWidget::mouseReleaseEvent(QMouseEvent* event) { QOpenGLWidget::mouseReleaseEvent(event); + + m_bFirstMouseQuery = true; + m_mouseLastPosition = QPoint(0, 0); } void SceneRenderWidget::updateProjectionMatrix(int w, int h) { - m_matProjection = glm::perspectiveFov(glm::radians(m_fFOV), static_cast(w), static_cast(h), m_fZNear, m_fZFar); + m_matProjection = glm::perspectiveFovLH(glm::radians(m_fFOV), static_cast(w), static_cast(h), m_fZNear, m_fZFar); m_bDirtyProj = false; } @@ -1173,8 +1176,6 @@ namespace widgets * | m02 m01 m00 | * mDst = | m12 m11 m21 | * | m22 m21 m20 | - * - * TODO: Need to find how to fix X flipping of map (or missrotating, idk) */ mTransform[0][0] = mMatrix[0][2]; mTransform[1][0] = mMatrix[0][1]; From 2cf4b15c5d1b010de47f86d32b097802ea59c25a Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 10 Oct 2023 19:14:20 +0300 Subject: [PATCH 18/80] Bounding Boxes support --- .../Editor/Include/Widgets/SceneRenderWidget.h | 1 - .../Source/Widgets/SceneRenderWidget.cpp | 18 ++++-------------- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 10 +++++----- .../GameLib/Include/GameLib/PRM/PRMEntries.h | 2 +- BMEdit/GameLib/Source/GameLib/BoundingBox.cpp | 4 ++-- .../GameLib/Source/GameLib/PRM/PRMReader.cpp | 9 +++++++-- 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index d2c79da..2b671e4 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -212,7 +212,6 @@ namespace widgets void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); void doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions); void doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility = false); - void discardResources(QOpenGLFunctions_3_3_Core* glFunctions); private: // Data diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 4818fcd..3983a8c 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -56,7 +57,8 @@ namespace widgets struct Model { std::vector meshes {}; - std::uint32_t chunkId { 0 }; + gamelib::BoundingBox boundingBox {}; + [[maybe_unused]] uint32_t chunkId {0u}; void discardAll(QOpenGLFunctions_3_3_Core* gapi) { @@ -763,9 +765,6 @@ namespace widgets { LEVEL_SAFE_CHECK() - // To avoid of future problems we've allocating null model at slot #0 and assign to it chunk #0 - m_resources->m_models.emplace_back().chunkId = 0; - // TODO: Optimize and load "chunk by chunk" for (const auto& model : m_pLevel->getLevelGeometry()->primitives.models) { @@ -779,6 +778,7 @@ namespace widgets GLResources::Model& glModel = m_resources->m_models.emplace_back(); glModel.chunkId = model.chunk; + glModel.boundingBox = gamelib::BoundingBox(model.boundingBox.vMin, model.boundingBox.vMax); // Store cache m_resources->m_modelsCache[model.chunk] = m_resources->m_models.size() - 1; @@ -1133,16 +1133,6 @@ namespace widgets } } - void SceneRenderWidget::discardResources(QOpenGLFunctions_3_3_Core* glFunctions) - { - if (m_resources) - { - m_resources->discard(glFunctions); - } - - m_eState = ELevelState::LS_NONE; // Switch to none, everything is gone - } - void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { // Save "scene" resolution diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index a0821ae..5c663be 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -1,18 +1,18 @@ #pragma once -#include +#include namespace gamelib { struct BoundingBox { - Vector3 min; - Vector3 max; + glm::vec3 min; + glm::vec3 max; BoundingBox() = default; - BoundingBox(const Vector3 &vMin, const Vector3 &vMax); + BoundingBox(const glm::vec3 &vMin, const glm::vec3 &vMax); - Vector3 getCenter() const; + glm::vec3 getCenter() const; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h index 49522e1..d19b639 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -67,7 +67,6 @@ namespace gamelib::prm std::vector indices {}; std::vector uvs {}; VertexFormat vertexFormat { VertexFormat::VF_ERROR }; - //BoundingBox boundingBox {}; static void deserialize(Mesh& mesh, ZBio::ZBinaryReader::BinaryReader* binaryReader, const PrmFile& prmFile); }; @@ -75,6 +74,7 @@ namespace gamelib::prm struct Model { uint32_t chunk = 0; + BoundingBox boundingBox {}; std::vector meshes {}; }; diff --git a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp index aa9709e..b6e0d7c 100644 --- a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp +++ b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp @@ -3,12 +3,12 @@ using namespace gamelib; -BoundingBox::BoundingBox(const gamelib::Vector3 &vMin, const gamelib::Vector3 &vMax) +BoundingBox::BoundingBox(const glm::vec3 &vMin, const glm::vec3 &vMax) : min(vMin), max(vMax) { } -Vector3 BoundingBox::getCenter() const +glm::vec3 BoundingBox::getCenter() const { return (min + max) / 2.f; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp index 9415693..467c021 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMReader.cpp @@ -71,8 +71,13 @@ namespace gamelib modelReader.seek(0x14); uint32_t meshCount = 0, meshTable = 0; - meshCount = modelReader.read(); - meshTable = modelReader.read(); + meshCount = modelReader.read(); // 0x14 -> 0x18 + meshTable = modelReader.read(); // 0x18 -> 0x1C + + // Read bbox + ZBioHelpers::seekBy(&modelReader, 0x4); + + prm::BoundingBox::deserialize(model.boundingBox, &modelReader); // Read mesh table ZBio::ZBinaryReader::BinaryReader meshTableReader { From 72cc846759c3c8cf18a90c58c553740f2499e07f Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 11 Oct 2023 13:06:35 +0300 Subject: [PATCH 19/80] Add RMC/RMI file support. Fix bug with leaked resources between levels. --- .../Source/Widgets/SceneRenderWidget.cpp | 2 + BMEdit/GameLib/Include/GameLib/Level.h | 20 +++++ BMEdit/GameLib/Include/GameLib/OCT/OCT.h | 9 +++ .../GameLib/Include/GameLib/OCT/OCTEntries.h | 72 +++++++++++++++++ .../GameLib/Include/GameLib/OCT/OCTReader.h | 28 +++++++ BMEdit/GameLib/Source/GameLib/Level.cpp | 80 +++++++++++++++++++ .../GameLib/Source/GameLib/OCT/OCTEntries.cpp | 54 +++++++++++++ .../GameLib/Source/GameLib/OCT/OCTReader.cpp | 69 ++++++++++++++++ 8 files changed, 334 insertions(+) create mode 100644 BMEdit/GameLib/Include/GameLib/OCT/OCT.h create mode 100644 BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h create mode 100644 BMEdit/GameLib/Include/GameLib/OCT/OCTReader.h create mode 100644 BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 3983a8c..59b78c4 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -582,10 +582,12 @@ namespace widgets { if (m_pLevel != nullptr) { + m_eState = ELevelState::LS_NONE; m_pLevel = nullptr; m_bFirstMouseQuery = true; resetViewMode(); resetRenderMode(); + repaint(); } } diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 42a7440..3320ecf 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,23 @@ namespace gamelib std::vector materialInstances; }; + struct LevelRooms + { + struct RoomGroup + { + oct::OCTHeader header {}; + std::vector nodes{}; + std::vector objects{}; + std::vector ubs{}; + + [[nodiscard]] glm::vec3 worldToRoom(const glm::vec3& vWorld) const; + [[nodiscard]] glm::vec3 roomToWorld(const glm::vec3& vTree) const; + }; + + RoomGroup outside {}; + RoomGroup inside {}; + }; + class Level { public: @@ -81,6 +99,7 @@ namespace gamelib bool loadLevelPrimitives(); bool loadLevelTextures(); bool loadLevelMaterials(); + bool loadLevelRooms(); private: // Core @@ -93,6 +112,7 @@ namespace gamelib LevelTextures m_levelTextures; LevelGeometry m_levelGeometry; LevelMaterials m_levelMaterials; + LevelRooms m_levelRooms; // Managed objects std::vector m_sceneObjects {}; diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCT.h b/BMEdit/GameLib/Include/GameLib/OCT/OCT.h new file mode 100644 index 0000000..192260c --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCT.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +namespace gamelib::oct +{ +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h new file mode 100644 index 0000000..02d7fd2 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::oct +{ + struct OCTHeader + { + uint32_t objectsOffset { 0 }; + glm::vec3 vWorldOrigin { .0f }; + float fWorldScale { .0f }; + + static void deserialize(OCTHeader& header, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct OCTNode + { + uint16_t childCount { 0 }; // It's mask. Real count of objects could be extracted via (childCount >> 3) & 0xFFF + uint16_t childIndex { 0 }; // It's index of NODE + uint16_t objectIndex { 0 }; + + [[nodiscard]] uint16_t getChildCount() const { return (childCount >> 3) & 0xFFF; } + + static void deserialize(OCTNode& node, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + struct OCTObject + { + uint32_t gameObjectREF { 0 }; + glm::i16vec3 vMin { 0 }; + glm::i16vec3 vMax { 0 }; + + static void deserialize(OCTObject& object, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; + + /** + * Idk what this block contains + */ + struct OCTUnknownBlock + { + uint32_t unk0 { 0 }; + float unk4 { 0.f }; + float unk8 { 0.f }; + float unkC { 0.f }; + float unk10 { 0.f }; + float unk14 { 0.f }; + float unk18 { 0.f }; + float unk1C { 0.f }; + float unk20 { 0.f }; + float unk24 { 0.f }; + float unk28 { 0.f }; + float unk2C { 0.f }; + float unk30 { 0.f }; + float unk34 { 0.f }; + float unk38 { 0.f }; + float unk3C { 0.f }; + float unk40 { 0.f }; + float unk44 { 0.f }; + float unk48 { 0.f }; + float unk4C { 0.f }; + float unk50 { 0.f }; + + static void deserialize(OCTUnknownBlock& block, ZBio::ZBinaryReader::BinaryReader* binaryReader); + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCTReader.h b/BMEdit/GameLib/Include/GameLib/OCT/OCTReader.h new file mode 100644 index 0000000..b6ff7f3 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCTReader.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + + +namespace gamelib::oct +{ + class OCTReader + { + public: + OCTReader(); + + bool parse(const uint8_t* pOCTBuffer, size_t iBufferSize); + + [[nodiscard]] const OCTHeader& getHeader() const { return m_header; } + [[nodiscard]] std::vector&& takeNodes() { return std::move(m_nodes); } + [[nodiscard]] std::vector&& takeObjects() { return std::move(m_objects); } + [[nodiscard]] std::vector&& takeUBS() { return std::move(m_unknownBlocks); } + + private: + OCTHeader m_header {}; + std::vector m_nodes {}; + std::vector m_objects {}; + std::vector m_unknownBlocks {}; // associated with m_objects + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 1a048bf..056b62e 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -13,6 +13,24 @@ namespace gamelib { + glm::vec3 LevelRooms::RoomGroup::worldToRoom(const glm::vec3& vWorld) const + { + return { + (vWorld.x - header.vWorldOrigin.x) * header.fWorldScale + 32768.0f, + (vWorld.y - header.vWorldOrigin.y) * header.fWorldScale + 32768.0f, + (vWorld.z - header.vWorldOrigin.z) * header.fWorldScale + 32768.0f + }; + } + + glm::vec3 LevelRooms::RoomGroup::roomToWorld(const glm::vec3& vRoom) const + { + return { + (vRoom.x - 32768.0f) / header.fWorldScale + header.vWorldOrigin.x, + (vRoom.y - 32768.0f) / header.fWorldScale + header.vWorldOrigin.y, + (vRoom.z - 32768.0f) / header.fWorldScale + header.vWorldOrigin.z + }; + } + Level::Level(std::unique_ptr &&levelAssetsProvider) : m_assetProvider(std::move(levelAssetsProvider)) { @@ -50,6 +68,11 @@ namespace gamelib return false; } + if (!loadLevelRooms()) + { + return false; + } + // TODO: Load things (it's time to combine GMS, PRP & BUF files) m_isLevelLoaded = true; return true; @@ -348,4 +371,61 @@ namespace gamelib return true; } + + bool Level::loadLevelRooms() + { + // Read outside rooms + { + int64_t rmcFileSize = 0; + auto rmcFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::ROOM_TREE_OUTSIDE, rmcFileSize); + + if (!rmcFileBuffer || !rmcFileSize) + { + return false; + } + + oct::OCTReader rmcReader {}; + const bool bParseResult = rmcReader.parse(rmcFileBuffer.get(), rmcFileSize); + + if (!bParseResult) + { + return false; + } + + m_levelRooms.outside.header = rmcReader.getHeader(); + m_levelRooms.outside.nodes = std::move(rmcReader.takeNodes()); + m_levelRooms.outside.objects = std::move(rmcReader.takeObjects()); + m_levelRooms.outside.ubs = std::move(rmcReader.takeUBS()); + } + + // Read inside rooms + { + int64_t rmcFileSize = 0; + auto rmcFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::ROOM_TREE_INSIDE, rmcFileSize); + + if (!rmcFileBuffer || !rmcFileSize) + { + return false; + } + + oct::OCTReader rmcReader {}; + const bool bParseResult = rmcReader.parse(rmcFileBuffer.get(), rmcFileSize); + + if (!bParseResult) + { + return false; + } + + m_levelRooms.inside.header = rmcReader.getHeader(); + m_levelRooms.inside.nodes = std::move(rmcReader.takeNodes()); + m_levelRooms.inside.objects = std::move(rmcReader.takeObjects()); + m_levelRooms.inside.ubs = std::move(rmcReader.takeUBS()); + } + + // Read collisions + { + } + + return true; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp new file mode 100644 index 0000000..3b3569e --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + + + +namespace gamelib::oct +{ + void OCTHeader::deserialize(OCTHeader& header, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + header.objectsOffset = binaryReader->read(); + binaryReader->read(glm::value_ptr(header.vWorldOrigin), 3); + header.fWorldScale = binaryReader->read(); + } + + void OCTNode::deserialize(OCTNode& node, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + node.childCount = binaryReader->read(); + node.childIndex = binaryReader->read(); + node.objectIndex = binaryReader->read(); + } + + void OCTObject::deserialize(OCTObject& object, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + object.gameObjectREF = binaryReader->read(); + binaryReader->read(glm::value_ptr(object.vMin), 3); + binaryReader->read(glm::value_ptr(object.vMax), 3); + } + + void OCTUnknownBlock::deserialize(gamelib::oct::OCTUnknownBlock& block, ZBio::ZBinaryReader::BinaryReader* binaryReader) + { + block.unk0 = binaryReader->read(); + block.unk4 = binaryReader->read(); + block.unk8 = binaryReader->read(); + block.unkC = binaryReader->read(); + block.unk10 = binaryReader->read(); + block.unk14 = binaryReader->read(); + block.unk18 = binaryReader->read(); + block.unk1C = binaryReader->read(); + block.unk20 = binaryReader->read(); + block.unk24 = binaryReader->read(); + block.unk28 = binaryReader->read(); + block.unk2C = binaryReader->read(); + block.unk30 = binaryReader->read(); + block.unk34 = binaryReader->read(); + block.unk38 = binaryReader->read(); + block.unk3C = binaryReader->read(); + block.unk40 = binaryReader->read(); + block.unk44 = binaryReader->read(); + block.unk48 = binaryReader->read(); + block.unk4C = binaryReader->read(); + block.unk50 = binaryReader->read(); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp new file mode 100644 index 0000000..b74ff8a --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + + +namespace gamelib::oct +{ + OCTReader::OCTReader() = default; + + bool OCTReader::parse(const uint8_t* pOCTBuffer, size_t iBufferSize) + { + if (!pOCTBuffer || !iBufferSize) + return false; + + ZBio::ZBinaryReader::BinaryReader octReader { reinterpret_cast(pOCTBuffer), static_cast(iBufferSize) }; + + // Read header + OCTHeader::deserialize(m_header, &octReader); + + // Seek by 0x14 (after header) + octReader.seek(0x14); + + // And read objects + const uint32_t nodesCount = (m_header.objectsOffset - 0x14) / 0x6; // 0x14 - always start point of nodes list, 0x6 - size of each node entry + int totalObjects = 0; + + m_nodes.reserve(nodesCount); + for (int i = 0; i < nodesCount; i++) + { + OCTNode node; + OCTNode::deserialize(node, &octReader); + + if (node.childCount == 0xCDCDu && node.childIndex == 0xCDCDu && node.objectIndex == 0xCDCDu) + { + // Alignment node. Skip and break + break; + } + + // Store max index of requested object to obtain count of objects at all + if (node.objectIndex > totalObjects) + totalObjects = node.objectIndex; + + m_nodes.emplace_back(node); + } + + // Because previously totalObjects was index, not a count + ++totalObjects; + + // Read objects + // Seek to begin + octReader.seek(m_header.objectsOffset); + m_objects.resize(totalObjects); + + for (int i = 0; i < totalObjects; i++) + { + OCTObject::deserialize(m_objects[i], &octReader); + } + + // And last block... + m_unknownBlocks.resize(totalObjects); + + for (int i = 0; i < totalObjects; i++) + { + OCTUnknownBlock::deserialize(m_unknownBlocks[i], &octReader); + } + + return true; + } +} \ No newline at end of file From 13ebe2eb4e714492c0adae0559574c35e22bc6a1 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 11 Oct 2023 17:13:49 +0300 Subject: [PATCH 20/80] Refactored renderer, fixed UV --- BMEdit/Editor/Include/Render/Camera.h | 140 ++++++ BMEdit/Editor/Include/Render/GLResource.h | 9 + BMEdit/Editor/Include/Render/GlacierVertex.h | 17 + BMEdit/Editor/Include/Render/Model.h | 141 ++++++ BMEdit/Editor/Include/Render/RenderTopology.h | 19 + BMEdit/Editor/Include/Render/Shader.h | 46 ++ .../Editor/Include/Render/ShaderConstants.h | 13 + BMEdit/Editor/Include/Render/Texture.h | 28 ++ .../Include/Render/VertexFormatDescription.h | 72 ++++ .../Include/Widgets/SceneRenderWidget.h | 136 +----- BMEdit/Editor/Source/Render/GlacierVertex.cpp | 10 + BMEdit/Editor/Source/Render/Model.cpp | 173 ++++++++ BMEdit/Editor/Source/Render/Shader.cpp | 211 +++++++++ BMEdit/Editor/Source/Render/Texture.cpp | 30 ++ .../Source/Render/VertexFormatDescription.cpp | 94 ++++ .../Source/Widgets/SceneRenderWidget.cpp | 400 ++---------------- .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 3 - 17 files changed, 1044 insertions(+), 498 deletions(-) create mode 100644 BMEdit/Editor/Include/Render/Camera.h create mode 100644 BMEdit/Editor/Include/Render/GLResource.h create mode 100644 BMEdit/Editor/Include/Render/GlacierVertex.h create mode 100644 BMEdit/Editor/Include/Render/Model.h create mode 100644 BMEdit/Editor/Include/Render/RenderTopology.h create mode 100644 BMEdit/Editor/Include/Render/Shader.h create mode 100644 BMEdit/Editor/Include/Render/ShaderConstants.h create mode 100644 BMEdit/Editor/Include/Render/Texture.h create mode 100644 BMEdit/Editor/Include/Render/VertexFormatDescription.h create mode 100644 BMEdit/Editor/Source/Render/GlacierVertex.cpp create mode 100644 BMEdit/Editor/Source/Render/Model.cpp create mode 100644 BMEdit/Editor/Source/Render/Shader.cpp create mode 100644 BMEdit/Editor/Source/Render/Texture.cpp create mode 100644 BMEdit/Editor/Source/Render/VertexFormatDescription.cpp diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h new file mode 100644 index 0000000..573a00f --- /dev/null +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace render +{ + // Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods + enum Camera_Movement { + FORWARD, + BACKWARD, + LEFT, + RIGHT + }; + + // Default camera values + const float YAW = -90.0f; + const float PITCH = 0.0f; + const float SPEED = 2.5f; + const float SENSITIVITY = 0.1f; + const float ZOOM = 45.0f; + + /** + * @credits https://learnopengl.com/Getting-started/Camera + */ + class Camera + { + public: + // camera Attributes + glm::vec3 Position; + glm::vec3 Front; + glm::vec3 Up; + glm::vec3 Right; + glm::vec3 WorldUp; + // euler Angles + float Yaw; + float Pitch; + // camera options + float MovementSpeed; + float MouseSensitivity; + float Zoom; + + // constructor with vectors + Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + { + Position = position; + WorldUp = up; + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); + } + + // constructor with scalar values + Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) + { + Position = glm::vec3(posX, posY, posZ); + WorldUp = glm::vec3(upX, upY, upZ); + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); + } + + void setPosition(const glm::vec3& position) + { + Position = position; + updateCameraVectors(); + } + + // returns the view matrix calculated using Euler Angles and the LookAt Matrix + glm::mat4 getViewMatrix() + { + return glm::lookAt(Position, Position + Front, Up); + } + + // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) + void processKeyboard(Camera_Movement direction, float deltaTime, float moveScale = 1.f) + { + float velocity = MovementSpeed * deltaTime * moveScale; + if (direction == FORWARD) + Position -= Front * velocity; + if (direction == BACKWARD) + Position += Front * velocity; + if (direction == LEFT) + Position -= Right * velocity; + if (direction == RIGHT) + Position += Right * velocity; + } + + // processes input received from a mouse input system. Expects the offset value in both the x and y direction. + void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) + { + xoffset *= MouseSensitivity; + yoffset *= MouseSensitivity; + + Yaw += xoffset; + Pitch += yoffset; + + // make sure that when pitch is out of bounds, screen doesn't get flipped + if (constrainPitch) + { + if (Pitch > 89.0f) + Pitch = 89.0f; + if (Pitch < -89.0f) + Pitch = -89.0f; + } + + // update Front, Right and Up Vectors using the updated Euler angles + updateCameraVectors(); + } + + // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis + void processMouseScroll(float yoffset) + { + Zoom -= (float)yoffset; + if (Zoom < 1.0f) + Zoom = 1.0f; + if (Zoom > 45.0f) + Zoom = 45.0f; + } + + private: + // calculates the front vector from the Camera's (updated) Euler Angles + void updateCameraVectors() + { + // calculate the new Front vector + glm::vec3 front; + front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + front.y = sin(glm::radians(Pitch)); + front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + Front = glm::normalize(front); + // also re-calculate the Right and Up vector + Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. + Up = glm::normalize(glm::cross(Right, Front)); + } + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/GLResource.h b/BMEdit/Editor/Include/Render/GLResource.h new file mode 100644 index 0000000..1b75b26 --- /dev/null +++ b/BMEdit/Editor/Include/Render/GLResource.h @@ -0,0 +1,9 @@ +#pragma once + +#include + + +namespace render +{ + static constexpr GLuint kInvalidResource = 0xFDEADC0D; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/GlacierVertex.h b/BMEdit/Editor/Include/Render/GlacierVertex.h new file mode 100644 index 0000000..2bea620 --- /dev/null +++ b/BMEdit/Editor/Include/Render/GlacierVertex.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + + +namespace render +{ + struct GlacierVertex + { + glm::vec3 vPos {}; + glm::vec2 vUV {}; + + static const VertexFormatDescription g_FormatDescription; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Model.h b/BMEdit/Editor/Include/Render/Model.h new file mode 100644 index 0000000..710e1f6 --- /dev/null +++ b/BMEdit/Editor/Include/Render/Model.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +namespace render +{ + class Mesh + { + private: + VertexFormatDescription m_vertexFormat {}; + uint32_t m_maxVerticesNr { 0u }; + uint32_t m_maxIndicesNr { 0u }; + bool m_bIsDynamic { false }; + + public: + GLuint vao { kInvalidResource }; + GLuint vbo { kInvalidResource }; + GLuint ibo { kInvalidResource }; + GLuint glTextureId { kInvalidResource }; /// Render OpenGL texture resource handle + uint16_t materialId { 0 }; /// Id of material from Glacier mesh (just copy) + + int trianglesCount { 0 }; + + [[nodiscard]] bool isDynamic() const { return m_bIsDynamic; } + + /** + * @brief Construct and upload vertex data into VAO + VBO + IBO + * @note For dynamic buffers allowed to use upload(...) method. Otherwise it will do nothing + * @tparam TVertex + * @param gapi + * @param vertexFormat + * @param vertices + * @param indices + * @param bIsDynamic + * @return + */ + template + bool setup(QOpenGLFunctions_3_3_Core* gapi, const VertexFormatDescription& vertexFormat, const std::vector& vertices, const std::vector& indices, bool bIsDynamic) + { + if (vao != kInvalidResource || vbo != kInvalidResource || ibo != kInvalidResource) + { + assert(false && "Need discard resource before create a new one!"); + return false; + } + + if (!gapi || vertexFormat.getEntries().empty() || vertices.empty()) + return false; + + return setup(gapi, + vertexFormat, + reinterpret_cast(vertices.data()), static_cast(vertices.size()), + indices.empty() ? nullptr : reinterpret_cast(indices.data()), indices.empty() ? 0 : static_cast(indices.size()), + bIsDynamic); + } + + /** + * @brief Update vertex and index buffer (able to update only vertex buffer, but unable to update only index buffer because it may be a root of inconsistent) + * @note This method will return false when not enough space in buffer (initialized at setup)! + * @note This method will return false when user trying to upload index buffer without initialise index buffer in setup! + * @tparam TVertex + * @param gapi + * @param vertices + * @param indices + * @return + */ + template + bool update(QOpenGLFunctions_3_3_Core* gapi, uint32_t verticesOffset, const std::vector& vertices, uint32_t indicesOffset = 0, const std::vector& indices = {}) + { + if (!gapi || vertices.empty()) + return false; + + if (!m_bIsDynamic) + return false; + + if (vao == kInvalidResource || vbo == kInvalidResource) + { + assert(false && "Call setup() before update!"); + return false; + } + + return update(gapi, + verticesOffset, reinterpret_cast(vertices.data()), static_cast(vertices.size()), + indicesOffset, indices.empty() ? nullptr : reinterpret_cast(indices.data()), indices.empty() ? 0 : static_cast(indices.size())); + } + + /** + * @brief Discard all resources and makes object invalid + * @param gapi + */ + void discard(QOpenGLFunctions_3_3_Core* gapi); + + /** + * @brief Do render of mesh + * @param gapi + * @param topology - which element topology stored inside buffer + */ + void render(QOpenGLFunctions_3_3_Core* gapi, RenderTopology topology = RenderTopology::RT_TRIANGLES) const; + + private: + /** + * @brief Create & upload vertices & indices into a single mesh + * @param gapi + * @param vertexFormat + * @param vertices + * @param verticesCount + * @param indices + * @param indicesCount + * @return true if everything is ok + */ + bool setup(QOpenGLFunctions_3_3_Core* gapi, const VertexFormatDescription& vertexFormat, const uint8_t* vertices, uint32_t verticesCount, const uint8_t* indices, uint32_t indicesCount, bool bDynamic); + + /** + * @brief Update vertex & index buffer (or only vertex buffer) + * @param gapi + * @param verticesOffset + * @param vertices + * @param verticesCount + * @param indices + * @param indicesCount + * @param indicesOffset + * @return + */ + bool update(QOpenGLFunctions_3_3_Core* gapi, uint32_t verticesOffset, const uint8_t* vertices, uint32_t verticesCount, uint32_t indicesOffset, const uint8_t* indices, uint32_t indicesCount); + }; + + struct Model + { + std::vector meshes {}; + gamelib::BoundingBox boundingBox {}; + [[maybe_unused]] uint32_t chunkId {0u}; + + void discard(QOpenGLFunctions_3_3_Core* gapi); + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/RenderTopology.h b/BMEdit/Editor/Include/Render/RenderTopology.h new file mode 100644 index 0000000..4e35df8 --- /dev/null +++ b/BMEdit/Editor/Include/Render/RenderTopology.h @@ -0,0 +1,19 @@ +#pragma once + +#include + + +namespace render +{ + enum class RenderTopology : uint8_t + { + RT_NONE = 0, + RT_POINTS, + RT_LINES, + RT_LINE_STRIP, + RT_LINE_LOOP, + RT_TRIANGLES, + RT_TRIANGLE_STRIP, + RT_TRIANGLE_FAN + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Shader.h b/BMEdit/Editor/Include/Render/Shader.h new file mode 100644 index 0000000..e90bb4d --- /dev/null +++ b/BMEdit/Editor/Include/Render/Shader.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +namespace render +{ + struct Shader + { + GLuint vertexProgramId { kInvalidResource }; + GLuint fragmentProgramId { kInvalidResource }; + GLuint programId { kInvalidResource }; + + Shader() = default; + + void discard(QOpenGLFunctions_3_3_Core* gapi); + + void bind(QOpenGLFunctions_3_3_Core* gapi); + + void unbind(QOpenGLFunctions_3_3_Core* gapi); + + bool compile(QOpenGLFunctions_3_3_Core* gapi, const std::string& vertexProgram, const std::string& fragmentProgram, std::string& error); + + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, float s); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, std::int32_t s); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec2& v); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::ivec2& v); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec3& v); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec4& v); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat3& v); + void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat4& v); + GLint resolveLocation(QOpenGLFunctions_3_3_Core* gapi, const std::string& id); + + private: + bool compileUnit(QOpenGLFunctions_3_3_Core* gapi, GLuint unitId, GLenum unitType, const std::string& unitSource, std::string& error); + + private: + std::map m_locationsCache; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/ShaderConstants.h b/BMEdit/Editor/Include/Render/ShaderConstants.h new file mode 100644 index 0000000..475c2bd --- /dev/null +++ b/BMEdit/Editor/Include/Render/ShaderConstants.h @@ -0,0 +1,13 @@ +#pragma once + + +namespace render +{ + struct ShaderConstants + { + static constexpr const char* kModelTransform = "i_uTransform.model"; + static constexpr const char* kCameraProjection = "i_uCamera.proj"; + static constexpr const char* kCameraView = "i_uCamera.view"; + static constexpr const char* kCameraResolution = "i_uCamera.resolution"; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Texture.h b/BMEdit/Editor/Include/Render/Texture.h new file mode 100644 index 0000000..16cb914 --- /dev/null +++ b/BMEdit/Editor/Include/Render/Texture.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace render +{ + struct Texture + { + uint16_t width { 0 }; + uint16_t height { 0 }; + GLuint texture { kInvalidResource }; + std::optional index {}; /// Index of texture from TEX container + std::optional texPath {}; /// [Optional] Path to texture in TEX container (path may not be defined in TEX!) + + Texture(); + + void discard(QOpenGLFunctions_3_3_Core* gapi); + + void bind(QOpenGLFunctions_3_3_Core* gapi); + + void unbind(QOpenGLFunctions_3_3_Core* gapi); + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/VertexFormatDescription.h b/BMEdit/Editor/Include/Render/VertexFormatDescription.h new file mode 100644 index 0000000..f2350d7 --- /dev/null +++ b/BMEdit/Editor/Include/Render/VertexFormatDescription.h @@ -0,0 +1,72 @@ +// +// Code from this file is based on BestByte Framework created by DronCode +// +#pragma once + +#include +#include +#include + + +namespace render +{ + /** + * @note If you would like to change this you need to add changes in g_typeSizeMap and g_entryTypeToGLType arrays. + */ + enum class VertexDescriptionEntryType + { + VDE_None = 0, + VDE_Bool, + VDE_Int32, + VDE_UInt32, + VDE_Float32, + VDE_Vec2, + VDE_Vec3, + VDE_Vec4, + VDE_IVec2, + VDE_IVec3, + VDE_IVec4, + VDE_Mat3x3, + VDE_Mat4x4 + }; + + struct VertexDescriptionEntry + { + uint32_t index { 0 }; + uint32_t offset { 0 }; + uint32_t size { 0 }; + VertexDescriptionEntryType type { VertexDescriptionEntryType::VDE_None }; + bool normalized { false }; + }; + + class VertexFormatDescription + { + public: + using Entries = std::vector; + using Visitor = std::function; + + VertexFormatDescription(); + + VertexFormatDescription& addField(uint32_t index, VertexDescriptionEntryType type, bool normalized = false); + + [[nodiscard]] const Entries& getEntries() const; + void visit(const Visitor& visitor) const; + + [[nodiscard]] uint32_t getStride() const; + + private: + void updateOrder() const; + + private: + mutable Entries m_entries {}; + uint32_t m_stride { 0 }; + mutable bool m_isOrdered { false }; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 2b671e4..faef6d8 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -2,12 +2,11 @@ #include #include +#include #include #include -#include #include #include -#include #include #include #include @@ -15,134 +14,7 @@ namespace renderer { - // Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods - enum Camera_Movement { - FORWARD, - BACKWARD, - LEFT, - RIGHT - }; - - // Default camera values - const float YAW = -90.0f; - const float PITCH = 0.0f; - const float SPEED = 2.5f; - const float SENSITIVITY = 0.1f; - const float ZOOM = 45.0f; - - /** - * @credits https://learnopengl.com/Getting-started/Camera - */ - class Camera - { - public: - // camera Attributes - glm::vec3 Position; - glm::vec3 Front; - glm::vec3 Up; - glm::vec3 Right; - glm::vec3 WorldUp; - // euler Angles - float Yaw; - float Pitch; - // camera options - float MovementSpeed; - float MouseSensitivity; - float Zoom; - - // constructor with vectors - Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = position; - WorldUp = up; - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - // constructor with scalar values - Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = glm::vec3(posX, posY, posZ); - WorldUp = glm::vec3(upX, upY, upZ); - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - void setPosition(const glm::vec3& position) - { - Position = position; - updateCameraVectors(); - } - - // returns the view matrix calculated using Euler Angles and the LookAt Matrix - glm::mat4 getViewMatrix() - { - return glm::lookAt(Position, Position + Front, Up); - } - // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) - void processKeyboard(Camera_Movement direction, float deltaTime, float moveScale = 1.f) - { - float velocity = MovementSpeed * deltaTime * moveScale; - if (direction == FORWARD) - Position -= Front * velocity; - if (direction == BACKWARD) - Position += Front * velocity; - if (direction == LEFT) - Position -= Right * velocity; - if (direction == RIGHT) - Position += Right * velocity; - } - - // processes input received from a mouse input system. Expects the offset value in both the x and y direction. - void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) - { - xoffset *= MouseSensitivity; - yoffset *= MouseSensitivity; - - Yaw += xoffset; - Pitch += yoffset; - - // make sure that when pitch is out of bounds, screen doesn't get flipped - if (constrainPitch) - { - if (Pitch > 89.0f) - Pitch = 89.0f; - if (Pitch < -89.0f) - Pitch = -89.0f; - } - - // update Front, Right and Up Vectors using the updated Euler angles - updateCameraVectors(); - } - - // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis - void processMouseScroll(float yoffset) - { - Zoom -= (float)yoffset; - if (Zoom < 1.0f) - Zoom = 1.0f; - if (Zoom > 45.0f) - Zoom = 45.0f; - } - - private: - // calculates the front vector from the Camera's (updated) Euler Angles - void updateCameraVectors() - { - // calculate the new Front vector - glm::vec3 front; - front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - front.y = sin(glm::radians(Pitch)); - front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - Front = glm::normalize(front); - // also re-calculate the Right and Up vector - Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. - Up = glm::normalize(glm::cross(Right, Front)); - } - }; } class QOpenGLFunctions_3_3_Core; @@ -170,8 +42,8 @@ namespace widgets void setLevel(gamelib::Level* pLevel); void resetLevel(); - [[nodiscard]] renderer::Camera& getCamera() { return m_camera; } - [[nodiscard]] const renderer::Camera& getCamera() const { return m_camera; } + [[nodiscard]] render::Camera& getCamera() { return m_camera; } + [[nodiscard]] const render::Camera& getCamera() const { return m_camera; } [[nodiscard]] float getFOV() const { return m_fFOV; } void setFOV(float fov) { m_fFOV = fov; m_bDirtyProj = true; } @@ -218,7 +90,7 @@ namespace widgets gamelib::Level* m_pLevel { nullptr }; // Camera & world view - renderer::Camera m_camera {}; + render::Camera m_camera {}; glm::mat4 m_matProjection {}; float m_fFOV { 67.664f }; float m_fZNear { .1f }; diff --git a/BMEdit/Editor/Source/Render/GlacierVertex.cpp b/BMEdit/Editor/Source/Render/GlacierVertex.cpp new file mode 100644 index 0000000..f35cadd --- /dev/null +++ b/BMEdit/Editor/Source/Render/GlacierVertex.cpp @@ -0,0 +1,10 @@ +#include + + +namespace render +{ + const VertexFormatDescription GlacierVertex::g_FormatDescription = + VertexFormatDescription() + .addField(0, VertexDescriptionEntryType::VDE_Vec3, false) + .addField(1, VertexDescriptionEntryType::VDE_Vec2, false); +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/Model.cpp b/BMEdit/Editor/Source/Render/Model.cpp new file mode 100644 index 0000000..650ef79 --- /dev/null +++ b/BMEdit/Editor/Source/Render/Model.cpp @@ -0,0 +1,173 @@ +#include + + +namespace render +{ + static constexpr GLenum g_entryTypeToGLType[] = { + GL_NONE, + GL_BOOL, + GL_INT, + GL_UNSIGNED_INT, + GL_FLOAT, + GL_FLOAT, + GL_FLOAT, + GL_FLOAT, + GL_INT, + GL_INT, + GL_INT, + GL_FLOAT, + GL_FLOAT, + }; + + bool Mesh::setup(QOpenGLFunctions_3_3_Core* gapi, const VertexFormatDescription& vertexFormat, const uint8_t* vertices, uint32_t verticesCount, const uint8_t* indices, uint32_t indicesCount, bool bDynamic) + { + // Allocate resources + gapi->glGenVertexArrays(1, &vao); + gapi->glGenBuffers(1, &vbo); + if (indices != nullptr) + gapi->glGenBuffers(1, &ibo); + + // Attach VAO + gapi->glBindVertexArray(vao); + gapi->glBindBuffer(GL_ARRAY_BUFFER, vbo); + + // Upload vertices + gapi->glBufferData(GL_ARRAY_BUFFER, vertexFormat.getStride() * verticesCount, vertices, bDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + + // Upload indices (if required) + if (indices != nullptr) + { + gapi->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); + gapi->glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast(sizeof(uint16_t) * indicesCount), indices, bDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + } + + // Initialize vertex format + vertexFormat.visit([gapi](uint32_t index, uint32_t offset, uint32_t size, uint32_t stride, VertexDescriptionEntryType t, bool isNormalized) { + const int componentsNr = static_cast(size) / 4; + const GLboolean normalized = isNormalized ? GL_TRUE : GL_FALSE; + const GLenum glType = g_entryTypeToGLType[static_cast(t)]; + + gapi->glEnableVertexAttribArray(index); + gapi->glVertexAttribPointer(index, componentsNr, glType, normalized, static_cast(stride), reinterpret_cast(static_cast(offset))); + }); + + // Save data + m_vertexFormat = vertexFormat; + m_bIsDynamic = bDynamic; + + if (indices != nullptr) + { + trianglesCount = static_cast(indicesCount / 3); + } + else + { + trianglesCount = static_cast(verticesCount / 3); + } + + m_maxVerticesNr = verticesCount; + m_maxIndicesNr = indices != nullptr ? indicesCount : 0u; + + return true; + } + + bool Mesh::update(QOpenGLFunctions_3_3_Core* gapi, uint32_t verticesOffset, const uint8_t* vertices, uint32_t verticesCount, uint32_t indicesOffset, const uint8_t* indices, uint32_t indicesCount) // NOLINT(*-make-member-function-const) + { + gapi->glBindBuffer(GL_ARRAY_BUFFER, vbo); + gapi->glBufferSubData(GL_ARRAY_BUFFER, static_cast(verticesOffset), static_cast(verticesCount * m_vertexFormat.getStride()), vertices); + gapi->glBindBuffer(GL_ARRAY_BUFFER, 0); + + if (indices) + { + gapi->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); + gapi->glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(indicesOffset), static_cast(indicesCount * sizeof(uint16_t)), indices); + gapi->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + return true; + } + + void Mesh::discard(QOpenGLFunctions_3_3_Core *gapi) + { + if (vao != kInvalidResource) + { + gapi->glDeleteVertexArrays(1, &vao); + vao = kInvalidResource; + } + + if (vbo != kInvalidResource) + { + gapi->glDeleteBuffers(1, &vbo); + vbo = kInvalidResource; + } + + if (ibo != kInvalidResource) + { + gapi->glDeleteBuffers(1, &ibo); + ibo = kInvalidResource; + } + + m_vertexFormat = {}; + m_maxVerticesNr = 0u; + m_maxIndicesNr = 0u; + m_bIsDynamic = false; + trianglesCount = 0; + } + + void Model::discard(QOpenGLFunctions_3_3_Core *gapi) + { + for (auto& mesh : meshes) + { + mesh.discard(gapi); + } + + meshes.clear(); + } + + void Mesh::render(QOpenGLFunctions_3_3_Core* gapi, render::RenderTopology topology) const + { + if (vao == kInvalidResource) + { + assert(false && "Not initialised!"); + return; + } + + assert(trianglesCount > 0); + + // Select topology + GLenum glTopology = GL_NONE; + switch (topology) + { + case RenderTopology::RT_NONE: + { + assert(false && "Invalid topology"); + return; + } + case RenderTopology::RT_POINTS: glTopology = GL_POINTS; break; + case RenderTopology::RT_LINES: glTopology = GL_LINES; break; + case RenderTopology::RT_LINE_STRIP: glTopology = GL_LINE_STRIP; break; + case RenderTopology::RT_LINE_LOOP: glTopology = GL_LINE_LOOP; break; + case RenderTopology::RT_TRIANGLES: glTopology = GL_TRIANGLES; break; + case RenderTopology::RT_TRIANGLE_STRIP: glTopology = GL_TRIANGLE_STRIP; break; + case RenderTopology::RT_TRIANGLE_FAN: glTopology = GL_TRIANGLE_FAN; break; + } + + + // Activate us + gapi->glBindVertexArray(vao); + + // Perform draw + if (ibo != kInvalidResource) + { + // Indexed + gapi->glDrawElements(glTopology, (trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); + } + else + { + // Arrays + gapi->glDrawArrays(GL_TRIANGLES, 0, trianglesCount); + } + + // Deactivate us + gapi->glBindVertexArray(0); + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/Shader.cpp b/BMEdit/Editor/Source/Render/Shader.cpp new file mode 100644 index 0000000..bf1db71 --- /dev/null +++ b/BMEdit/Editor/Source/Render/Shader.cpp @@ -0,0 +1,211 @@ +#include +#include + + +namespace render +{ + + void Shader::discard(QOpenGLFunctions_3_3_Core *gapi) + { + if (vertexProgramId != kInvalidResource) + { + gapi->glDeleteShader(vertexProgramId); + vertexProgramId = kInvalidResource; + } + + if (fragmentProgramId != kInvalidResource) + { + gapi->glDeleteShader(fragmentProgramId); + fragmentProgramId = kInvalidResource; + } + + if (programId != kInvalidResource) + { + gapi->glDeleteShader(programId); + programId = kInvalidResource; + } + } + + void Shader::bind(QOpenGLFunctions_3_3_Core *gapi) + { + if (programId != kInvalidResource) + { + gapi->glUseProgram(programId); + } + } + + void Shader::unbind(QOpenGLFunctions_3_3_Core *gapi) + { + gapi->glUseProgram(0); + } + + bool Shader::compile(QOpenGLFunctions_3_3_Core *gapi, const std::string &vertexProgram, const std::string &fragmentProgram, std::string &error) + { + // Allocate root program + programId = gapi->glCreateProgram(); + vertexProgramId = gapi->glCreateShader(GL_VERTEX_SHADER); + fragmentProgramId = gapi->glCreateShader(GL_FRAGMENT_SHADER); + + // Compile vertex program + if (!compileUnit(gapi, vertexProgramId, GL_VERTEX_SHADER, vertexProgram, error)) + { + qDebug() << "Failed to compile vertex shader program"; + assert(false && "Failed to compile vertex shader program"); + return false; + } + + gapi->glAttachShader(programId, vertexProgramId); + + // Compile fragment program + if (!compileUnit(gapi, fragmentProgramId, GL_FRAGMENT_SHADER, fragmentProgram, error)) + { + qDebug() << "Failed to compile vertex shader program"; + assert(false && "Failed to compile fragment shader program"); + return false; + } + + gapi->glAttachShader(programId, fragmentProgramId); + + // Linking + gapi->glLinkProgram(programId); + + // Check linking status + GLint linkingIsOK = GL_FALSE; + + gapi->glGetProgramiv(programId, GL_LINK_STATUS, &linkingIsOK); + if (linkingIsOK == GL_FALSE) + { + constexpr int kCompileLogSize = 512; + + char linkLog[kCompileLogSize] = { 0 }; + GLint length { 0 }; + + gapi->glGetProgramInfoLog(programId, kCompileLogSize, &length, &linkLog[0]); + + error = std::string(&linkLog[0], length); + qDebug() << "Failed to link shader program: " << QString::fromStdString(error); + + assert(false); + return false; + } + + // Done + return true; + } + + bool Shader::compileUnit(QOpenGLFunctions_3_3_Core *gapi, GLuint unitId, GLenum unitType, const std::string &unitSource, std::string &error) + { + const GLchar* glSrc = reinterpret_cast(unitSource.c_str()); + const GLint glLen = static_cast(unitSource.length()); + + gapi->glShaderSource(unitId, 1, &glSrc, &glLen); + gapi->glCompileShader(unitId); + + GLint isCompiled = 0; + gapi->glGetShaderiv(unitId, GL_COMPILE_STATUS, &isCompiled); + + if (isCompiled == GL_FALSE) + { + constexpr int kCompileLogSize = 512; + + char compileLog[kCompileLogSize] = { 0 }; + GLint length { 0 }; + + gapi->glGetShaderInfoLog(unitId, kCompileLogSize, &length, &compileLog[0]); + + error = std::string(&compileLog[0], length); + qDebug() << "Failed to compile shader program: " << QString::fromStdString(error); + + assert(false && "Failed to compile unit!"); + return false; + } + + return true; + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, float s) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform1f(location, s); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, std::int32_t s) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform1i(location, s); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec2& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform2fv(location, 1, glm::value_ptr(v)); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::ivec2& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform2iv(location, 1, glm::value_ptr(v)); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec3& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform3fv(location, 1, glm::value_ptr(v)); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec4& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniform4fv(location, 1, glm::value_ptr(v)); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat3& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniformMatrix3fv(location, 1, GL_FALSE, glm::value_ptr(v)); + } + + void Shader::setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat4& v) + { + GLint location = resolveLocation(gapi, id); + if (location == -1) + return; + + gapi->glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(v)); + } + + GLint Shader::resolveLocation(QOpenGLFunctions_3_3_Core* gapi, const std::string& id) + { + if (auto it = m_locationsCache.find(id); it == m_locationsCache.end()) + { + GLint result = gapi->glGetUniformLocation(programId, id.c_str()); + m_locationsCache[id] = result; + return result; + } + else + { + return it->second; + } + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/Texture.cpp b/BMEdit/Editor/Source/Render/Texture.cpp new file mode 100644 index 0000000..6a72ab0 --- /dev/null +++ b/BMEdit/Editor/Source/Render/Texture.cpp @@ -0,0 +1,30 @@ +#include + + +namespace render +{ + Texture::Texture() = default; + + void Texture::discard(QOpenGLFunctions_3_3_Core *gapi) + { + width = height = 0; + + if (texture != kInvalidResource) + { + gapi->glDeleteTextures(1, &texture); + } + } + + void Texture::bind(QOpenGLFunctions_3_3_Core* gapi) + { + if (texture != kInvalidResource) + { + gapi->glBindTexture(GL_TEXTURE_2D, texture); + } + } + + void Texture::unbind(QOpenGLFunctions_3_3_Core* gapi) + { + gapi->glBindTexture(GL_TEXTURE_2D, 0); + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/VertexFormatDescription.cpp b/BMEdit/Editor/Source/Render/VertexFormatDescription.cpp new file mode 100644 index 0000000..a25227c --- /dev/null +++ b/BMEdit/Editor/Source/Render/VertexFormatDescription.cpp @@ -0,0 +1,94 @@ +// +// Code from this file is based on BestByte Framework created by DronCode +// +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace render +{ + static constexpr uint32_t g_typeSizeMap[] = { + 0, // None + sizeof(bool), + sizeof(int32_t), + sizeof(uint32_t), + sizeof(float), + sizeof(glm::vec2), + sizeof(glm::vec3), + sizeof(glm::vec4), + sizeof(glm::ivec2), + sizeof(glm::ivec3), + sizeof(glm::ivec4), + sizeof(glm::mat3x3), + sizeof(glm::mat4x4) + }; + + VertexFormatDescription::VertexFormatDescription() = default; + + VertexFormatDescription& VertexFormatDescription::addField(uint32_t index, VertexDescriptionEntryType type, bool normalized) + { + if (type == VertexDescriptionEntryType::VDE_None) + { + assert(false); + return *this; + } + + auto& ent = m_entries.emplace_back(); + ent.type = type; + ent.size = g_typeSizeMap[static_cast(type)]; + ent.normalized = normalized; + ent.index = index; + + m_isOrdered = false; + m_stride += ent.size; + + return *this; + } + + const VertexFormatDescription::Entries& VertexFormatDescription::getEntries() const + { + updateOrder(); + + return m_entries; + } + + void VertexFormatDescription::visit(const render::VertexFormatDescription::Visitor &visitor) const + { + updateOrder(); + + for (const auto& [index, offset, size, type, normalized] : m_entries) + { + visitor(index, offset, size, m_stride, type, normalized); + } + } + + uint32_t VertexFormatDescription::getStride() const + { + return m_stride; + } + + void VertexFormatDescription::updateOrder() const + { + if (!m_isOrdered) + { + m_isOrdered = true; + + std::sort(m_entries.begin(), m_entries.end(), [](const VertexDescriptionEntry& a, const VertexDescriptionEntry& b) { + return a.index < b.index; + }); + + size_t offset = 0; + for (auto& entry : m_entries) + { + entry.offset = offset; + offset += entry.size; + } + } + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 59b78c4..a79f241 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -12,301 +12,22 @@ #include #include #include + +#include +#include +#include +#include +#include +#include #include namespace widgets { + using namespace render; + struct SceneRenderWidget::GLResources { - static constexpr GLuint kInvalidResource = 0xFDEADC0D; - - struct Mesh - { - GLuint vao { kInvalidResource }; - GLuint vbo { kInvalidResource }; - GLuint ibo { kInvalidResource }; - GLuint glTextureId { kInvalidResource }; /// Render OpenGL texture resource handle - - uint16_t materialId { 0 }; /// Id of material from Glacier mesh (just copy) - - int trianglesCount { 0 }; - - void discard(QOpenGLFunctions_3_3_Core* gapi) - { - if (vao != kInvalidResource) - { - gapi->glDeleteVertexArrays(1, &vao); - vao = kInvalidResource; - } - - if (vbo != kInvalidResource) - { - gapi->glDeleteBuffers(1, &vbo); - vbo = kInvalidResource; - } - - if (ibo != kInvalidResource) - { - gapi->glDeleteBuffers(1, &ibo); - ibo = kInvalidResource; - } - } - }; - - struct Model - { - std::vector meshes {}; - gamelib::BoundingBox boundingBox {}; - [[maybe_unused]] uint32_t chunkId {0u}; - - void discardAll(QOpenGLFunctions_3_3_Core* gapi) - { - for (auto& mesh : meshes) - { - mesh.discard(gapi); - } - - meshes.clear(); - } - }; - - struct Texture - { - uint16_t width { 0 }; - uint16_t height { 0 }; - GLuint texture { kInvalidResource }; - std::optional index {}; /// Index of texture from TEX container - std::optional texPath {}; /// [Optional] Path to texture in TEX container (path may not be defined in TEX!) - - void discard(QOpenGLFunctions_3_3_Core* gapi) - { - width = height = 0; - - if (texture != kInvalidResource) - { - gapi->glDeleteTextures(1, &texture); - } - } - }; - - struct Shader - { - GLuint vertexProgramId { kInvalidResource }; - GLuint fragmentProgramId { kInvalidResource }; - GLuint programId { kInvalidResource }; - - Shader() = default; - - void discard(QOpenGLFunctions_3_3_Core* gapi) - { - if (vertexProgramId != kInvalidResource) - { - gapi->glDeleteShader(vertexProgramId); - vertexProgramId = kInvalidResource; - } - - if (fragmentProgramId != kInvalidResource) - { - gapi->glDeleteShader(fragmentProgramId); - fragmentProgramId = kInvalidResource; - } - - if (programId != kInvalidResource) - { - gapi->glDeleteShader(programId); - programId = kInvalidResource; - } - } - - void bind(QOpenGLFunctions_3_3_Core* gapi) - { - if (programId != kInvalidResource) - { - gapi->glUseProgram(programId); - } - } - - void unbind(QOpenGLFunctions_3_3_Core* gapi) - { - gapi->glUseProgram(0); - } - - bool compile(QOpenGLFunctions_3_3_Core* gapi, const std::string& vertexProgram, const std::string& fragmentProgram, std::string& error) - { - // Allocate root program - programId = gapi->glCreateProgram(); - vertexProgramId = gapi->glCreateShader(GL_VERTEX_SHADER); - fragmentProgramId = gapi->glCreateShader(GL_FRAGMENT_SHADER); - - // Compile vertex program - if (!compileUnit(gapi, vertexProgramId, GL_VERTEX_SHADER, vertexProgram, error)) - { - qDebug() << "Failed to compile vertex shader program"; - assert(false && "Failed to compile vertex shader program"); - return false; - } - - gapi->glAttachShader(programId, vertexProgramId); - - // Compile fragment program - if (!compileUnit(gapi, fragmentProgramId, GL_FRAGMENT_SHADER, fragmentProgram, error)) - { - qDebug() << "Failed to compile vertex shader program"; - assert(false && "Failed to compile fragment shader program"); - return false; - } - - gapi->glAttachShader(programId, fragmentProgramId); - - // Linking - gapi->glLinkProgram(programId); - - // Check linking status - GLint linkingIsOK = GL_FALSE; - - gapi->glGetProgramiv(programId, GL_LINK_STATUS, &linkingIsOK); - if (linkingIsOK == GL_FALSE) - { - constexpr int kCompileLogSize = 512; - - char linkLog[kCompileLogSize] = { 0 }; - GLint length { 0 }; - - gapi->glGetProgramInfoLog(programId, kCompileLogSize, &length, &linkLog[0]); - - error = std::string(&linkLog[0], length); - qDebug() << "Failed to link shader program: " << QString::fromStdString(error); - - assert(false); - return false; - } - - // Done - return true; - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, float s) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform1f(location, s); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, std::int32_t s) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform1i(location, s); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec2& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform2fv(location, 1, glm::value_ptr(v)); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::ivec2& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform2iv(location, 1, glm::value_ptr(v)); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec3& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform3fv(location, 1, glm::value_ptr(v)); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::vec4& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniform4fv(location, 1, glm::value_ptr(v)); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat3& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniformMatrix3fv(location, 1, GL_FALSE, glm::value_ptr(v)); - } - - void setUniform(QOpenGLFunctions_3_3_Core* gapi, const std::string& id, const glm::mat4& v) - { - GLint location = resolveLocation(gapi, id); - if (location == -1) - return; - - gapi->glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(v)); - } - - GLint resolveLocation(QOpenGLFunctions_3_3_Core* gapi, const std::string& id) const - { - if (auto it = m_locationsCache.find(id); it == m_locationsCache.end()) - { - GLint result = gapi->glGetUniformLocation(programId, id.c_str()); - m_locationsCache[id] = result; - return result; - } - else - { - return it->second; - } - } - - private: - bool compileUnit(QOpenGLFunctions_3_3_Core* gapi, GLuint unitId, GLenum unitType, const std::string& unitSource, std::string& error) - { - const GLchar* glSrc = reinterpret_cast(unitSource.c_str()); - const GLint glLen = static_cast(unitSource.length()); - - gapi->glShaderSource(unitId, 1, &glSrc, &glLen); - gapi->glCompileShader(unitId); - - GLint isCompiled = 0; - gapi->glGetShaderiv(unitId, GL_COMPILE_STATUS, &isCompiled); - - if (isCompiled == GL_FALSE) - { - constexpr int kCompileLogSize = 512; - - char compileLog[kCompileLogSize] = { 0 }; - GLint length { 0 }; - - gapi->glGetShaderInfoLog(unitId, kCompileLogSize, &length, &compileLog[0]); - - error = std::string(&compileLog[0], length); - qDebug() << "Failed to compile shader program: " << QString::fromStdString(error); - - assert(false && "Failed to compile unit!"); - return false; - } - - return true; - } - - private: - mutable std::map m_locationsCache; - }; - std::vector m_textures {}; std::vector m_shaders {}; std::vector m_models {}; @@ -346,7 +67,7 @@ namespace widgets { for (auto& model : m_models) { - model.discardAll(gapi); + model.discard(gapi); } m_models.clear(); @@ -369,12 +90,6 @@ namespace widgets } }; - struct GlacierVertex - { - glm::vec3 vPos {}; - glm::vec2 vUV {}; - }; - SceneRenderWidget::SceneRenderWidget(QWidget *parent, Qt::WindowFlags f) : QOpenGLWidget(parent, f) { QSurfaceFormat format; @@ -488,22 +203,22 @@ namespace widgets if (event->key() == Qt::Key_W) { - m_camera.processKeyboard(renderer::Camera_Movement::FORWARD, kBaseDt, kSpeedUp); + m_camera.processKeyboard(render::Camera_Movement::FORWARD, kBaseDt, kSpeedUp); bMoved = true; } else if (event->key() == Qt::Key_S) { - m_camera.processKeyboard(renderer::Camera_Movement::BACKWARD, kBaseDt, kSpeedUp); + m_camera.processKeyboard(render::Camera_Movement::BACKWARD, kBaseDt, kSpeedUp); bMoved = true; } else if (event->key() == Qt::Key_A) { - m_camera.processKeyboard(renderer::Camera_Movement::LEFT, kBaseDt, kSpeedUp); + m_camera.processKeyboard(render::Camera_Movement::LEFT, kBaseDt, kSpeedUp); bMoved = true; } else if (event->key() == Qt::Key_D) { - m_camera.processKeyboard(renderer::Camera_Movement::RIGHT, kBaseDt, kSpeedUp); + m_camera.processKeyboard(render::Camera_Movement::RIGHT, kBaseDt, kSpeedUp); bMoved = true; } @@ -676,7 +391,7 @@ namespace widgets } // Ok, texture is ok - load it - GLResources::Texture newTexture; + Texture newTexture; std::unique_ptr decompressedMemBlk = editor::TextureProcessor::decompressRGBA(texture, newTexture.width, newTexture.height, 0); // if (!decompressedMemBlk) @@ -778,7 +493,7 @@ namespace widgets continue; } - GLResources::Model& glModel = m_resources->m_models.emplace_back(); + Model& glModel = m_resources->m_models.emplace_back(); glModel.chunkId = model.chunk; glModel.boundingBox = gamelib::BoundingBox(model.boundingBox.vMin, model.boundingBox.vMax); @@ -827,42 +542,16 @@ namespace widgets } // And upload it to GPU - GLResources::Mesh& glMesh = glModel.meshes.emplace_back(); + Mesh& glMesh = glModel.meshes.emplace_back(); glMesh.trianglesCount = mesh.trianglesCount; - // Allocate VAO, VBO & IBO stuff - glFunctions->glGenVertexArrays(1, &glMesh.vao); - glFunctions->glGenBuffers(1, &glMesh.vbo); - if (!indices.empty()) - { - glFunctions->glGenBuffers(1, &glMesh.ibo); - } - - // Attach VAO - glFunctions->glBindVertexArray(glMesh.vao); - glFunctions->glBindBuffer(GL_ARRAY_BUFFER, glMesh.vbo); - - // Upload vertices - glFunctions->glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GlacierVertex), &vertices[0], GL_STATIC_DRAW); - - // Upload indices - if (!indices.empty()) + if (!glMesh.setup(glFunctions, GlacierVertex::g_FormatDescription, vertices, indices, false)) { - glFunctions->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glMesh.ibo); - glFunctions->glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(std::uint16_t), &indices[0], GL_STATIC_DRAW); + qWarning() << "Failed to upload mesh #" << meshIdx << " of model at chunk " << model.chunk << ". Reason: failed to upload resource to GPU!"; + ++meshIdx; + continue; } - // Setup vertex format - const GLintptr vertexCoordinateOffset = 0 * sizeof(float); - const GLintptr UVCoordinateOffset = 3 * sizeof(float); - const GLsizei stride = 5 * sizeof(float); - - glFunctions->glEnableVertexAttribArray(0); - glFunctions->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (const GLvoid*)vertexCoordinateOffset); - - glFunctions->glEnableVertexAttribArray(1); - glFunctions->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (const GLvoid*)UVCoordinateOffset); - // Precache color texture glMesh.materialId = mesh.material_id; @@ -877,7 +566,7 @@ namespace widgets if (const auto& parentName = matInstance.getParentName(); parentName == "StaticShadow" || parentName == "StaticShadowTextureShadow" || matInstance.getName().find("AlwaysInShadow") != std::string::npos) { // Shadows - do not use texturing (and don't show for now) - glMesh.glTextureId = GLResources::kInvalidResource; + glMesh.glTextureId = kInvalidResource; } else if (parentName == "Bad") { @@ -955,7 +644,7 @@ namespace widgets } // For debug only -// if (glMesh.glTextureId == GLResources::kInvalidResource) +// if (glMesh.glTextureId == kInvalidResource) // { // glMesh.glTextureId = m_resources->m_iGLMissingTexture; // } @@ -1032,7 +721,7 @@ namespace widgets // Compile shaders std::string compileError; { - GLResources::Shader texturedShader; + Shader texturedShader; if (!texturedShader.compile(glFunctions, texturedEntityVertexShaderSource, texturedEntityFragmentShaderSource, compileError)) { @@ -1048,7 +737,7 @@ namespace widgets } { - GLResources::Shader gizmoShader; + Shader gizmoShader; if (!gizmoShader.compile(glFunctions, coloredEntityVertexShaderSource, coloredEntityFragmentShaderSource, compileError)) { m_pLevel = nullptr; @@ -1189,14 +878,14 @@ namespace widgets // RenderGod if (m_resources->m_modelsCache.contains(primId)) { - const GLResources::Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; // Render all meshes for (const auto& mesh : model.meshes) { // Render single mesh // 0. Check that we able to draw it - if (mesh.glTextureId == GLResources::kInvalidResource) + if (mesh.glTextureId == kInvalidResource) { // Draw "error" bounding box // And continue @@ -1204,48 +893,33 @@ namespace widgets } // 1. Activate default shader - GLResources::Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; + Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; texturedShader.bind(glFunctions); // 2. Submit uniforms - texturedShader.setUniform(glFunctions, "i_uTransform.model", mModelMatrix); - texturedShader.setUniform(glFunctions, "i_uCamera.proj", m_matProjection); - texturedShader.setUniform(glFunctions, "i_uCamera.view", m_camera.getViewMatrix()); - texturedShader.setUniform(glFunctions, "i_uCamera.resolution", viewResolution); + texturedShader.setUniform(glFunctions, ShaderConstants::kModelTransform, mModelMatrix); + texturedShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); + texturedShader.setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); + texturedShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); // 3. Bind texture - if (mesh.glTextureId != GLResources::kInvalidResource) + if (mesh.glTextureId != kInvalidResource) { glFunctions->glBindTexture(GL_TEXTURE_2D, mesh.glTextureId); } - // 3. Activate VAO - glFunctions->glBindVertexArray(mesh.vao); - - auto doSubmitGPUCommands = [glFunctions, mesh]() { - if (mesh.ibo != GLResources::kInvalidResource) - { - // Draw indexed - glFunctions->glDrawElements(GL_TRIANGLES, (mesh.trianglesCount * 3), GL_UNSIGNED_SHORT, nullptr); - } - else - { - // Draw elements - glFunctions->glDrawArrays(GL_TRIANGLES, 0, mesh.trianglesCount); - } - }; - + // 3. Render mesh if (m_renderMode & RenderMode::RM_TEXTURE) { // normal draw - doSubmitGPUCommands(); + mesh.render(glFunctions); } if (m_renderMode & RenderMode::RM_WIREFRAME) { glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - doSubmitGPUCommands(); + mesh.render(glFunctions); // reset back glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -1254,7 +928,7 @@ namespace widgets // 6. Unbind texture and shader (expected to switch between materials, but not now) glFunctions->glBindTexture(GL_TEXTURE_2D, 0); - m_resources->m_shaders[0].unbind(glFunctions); + texturedShader.unbind(glFunctions); } } // otherwise draw red bbox! diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index 35d2590..388d430 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -164,7 +164,6 @@ namespace gamelib::prm // Read UVs glm::vec2& uv = mesh.uvs.emplace_back(); vertexReader.read(glm::value_ptr(uv), 2); - uv.y = 1.f - uv.y; } } break; @@ -182,7 +181,6 @@ namespace gamelib::prm // Read UVs glm::vec2& uv = mesh.uvs.emplace_back(); vertexReader.read(glm::value_ptr(uv), 2); - uv.y = 1.f - uv.y; // Another seek // TODO: Fix this! @@ -200,7 +198,6 @@ namespace gamelib::prm glm::vec2& uv = mesh.uvs.emplace_back(); vertexReader.read(glm::value_ptr(uv), 2); - uv.y = 1.f - uv.y; // TODO: Fix this ZBioHelpers::seekBy(&vertexReader, 0x8); From 3ea52f75b4a6c8d3d54cdfcbf9d9b5b7b82c3c88 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 11 Oct 2023 17:49:06 +0300 Subject: [PATCH 21/80] Added support of bounding box rendering (rendering disabled, will enable it later) --- BMEdit/Editor/Include/Render/GlacierVertex.h | 10 ++++++ BMEdit/Editor/Include/Render/Model.h | 4 +++ .../Editor/Include/Render/ShaderConstants.h | 1 + BMEdit/Editor/Source/Render/GlacierVertex.cpp | 4 +++ BMEdit/Editor/Source/Render/Model.cpp | 34 +++++++++++++++++++ .../Source/Widgets/SceneRenderWidget.cpp | 28 ++++++++++++--- 6 files changed, 77 insertions(+), 4 deletions(-) diff --git a/BMEdit/Editor/Include/Render/GlacierVertex.h b/BMEdit/Editor/Include/Render/GlacierVertex.h index 2bea620..adcecde 100644 --- a/BMEdit/Editor/Include/Render/GlacierVertex.h +++ b/BMEdit/Editor/Include/Render/GlacierVertex.h @@ -14,4 +14,14 @@ namespace render static const VertexFormatDescription g_FormatDescription; }; + + struct SimpleVertex + { + glm::vec3 vPos {}; + + SimpleVertex(); + SimpleVertex(const glm::vec3& v1) : vPos(v1) {} + + static const VertexFormatDescription g_FormatDescription; + }; } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Model.h b/BMEdit/Editor/Include/Render/Model.h index 710e1f6..2fde37b 100644 --- a/BMEdit/Editor/Include/Render/Model.h +++ b/BMEdit/Editor/Include/Render/Model.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -133,9 +134,12 @@ namespace render struct Model { std::vector meshes {}; + std::optional boundingBoxMesh {}; // mesh with bounding box gamelib::BoundingBox boundingBox {}; [[maybe_unused]] uint32_t chunkId {0u}; void discard(QOpenGLFunctions_3_3_Core* gapi); + + bool setupBoundingBox(QOpenGLFunctions_3_3_Core* gapi); }; } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/ShaderConstants.h b/BMEdit/Editor/Include/Render/ShaderConstants.h index 475c2bd..fbc2732 100644 --- a/BMEdit/Editor/Include/Render/ShaderConstants.h +++ b/BMEdit/Editor/Include/Render/ShaderConstants.h @@ -9,5 +9,6 @@ namespace render static constexpr const char* kCameraProjection = "i_uCamera.proj"; static constexpr const char* kCameraView = "i_uCamera.view"; static constexpr const char* kCameraResolution = "i_uCamera.resolution"; + static constexpr const char* kColor = "i_Color"; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/GlacierVertex.cpp b/BMEdit/Editor/Source/Render/GlacierVertex.cpp index f35cadd..dc0fc9c 100644 --- a/BMEdit/Editor/Source/Render/GlacierVertex.cpp +++ b/BMEdit/Editor/Source/Render/GlacierVertex.cpp @@ -7,4 +7,8 @@ namespace render VertexFormatDescription() .addField(0, VertexDescriptionEntryType::VDE_Vec3, false) .addField(1, VertexDescriptionEntryType::VDE_Vec2, false); + + const VertexFormatDescription SimpleVertex::g_FormatDescription = + VertexFormatDescription() + .addField(0, VertexDescriptionEntryType::VDE_Vec3, false); } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Render/Model.cpp b/BMEdit/Editor/Source/Render/Model.cpp index 650ef79..de366cf 100644 --- a/BMEdit/Editor/Source/Render/Model.cpp +++ b/BMEdit/Editor/Source/Render/Model.cpp @@ -1,3 +1,4 @@ +#include #include @@ -115,6 +116,12 @@ namespace render void Model::discard(QOpenGLFunctions_3_3_Core *gapi) { + if (boundingBoxMesh.has_value()) + { + boundingBoxMesh.value().discard(gapi); + boundingBoxMesh = std::nullopt; + } + for (auto& mesh : meshes) { mesh.discard(gapi); @@ -123,6 +130,33 @@ namespace render meshes.clear(); } + bool Model::setupBoundingBox(QOpenGLFunctions_3_3_Core *gapi) + { + VertexFormatDescription vertexFormat{}; + vertexFormat.addField(0, VertexDescriptionEntryType::VDE_Vec3, false); + + const glm::vec3& vMin = boundingBox.min; + const glm::vec3& vMax = boundingBox.max; + + std::vector vertices { + glm::vec3{vMin.x, vMin.y, vMin.z}, glm::vec3{vMin.x, vMin.y, vMax.z}, + glm::vec3{vMin.x, vMax.y, vMin.z}, glm::vec3{vMin.x, vMax.y, vMax.z}, + glm::vec3{vMax.x, vMin.y, vMin.z}, glm::vec3{vMax.x, vMin.y, vMax.z}, + glm::vec3{vMax.x, vMax.y, vMin.z}, glm::vec3{vMax.x, vMax.y, vMax.z} + }; + + std::vector indices { + 0, 1, 1, 3, 3, 2, 2, 0, + 4, 5, 5, 7, 7, 6, 6, 4, + 0, 4, 1, 5, 2, 6, 3, 7 + }; + + Mesh& mesh = boundingBoxMesh.emplace(); + mesh.glTextureId = 0u; + mesh.materialId = 0u; + return mesh.setup(gapi, vertexFormat, vertices, indices, false); + } + void Mesh::render(QOpenGLFunctions_3_3_Core* gapi, render::RenderTopology topology) const { if (vao == kInvalidResource) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index a79f241..df59321 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -497,6 +497,9 @@ namespace widgets glModel.chunkId = model.chunk; glModel.boundingBox = gamelib::BoundingBox(model.boundingBox.vMin, model.boundingBox.vMax); + // And create mesh for bounding box + glModel.setupBoundingBox(glFunctions); + // Store cache m_resources->m_modelsCache[model.chunk] = m_resources->m_models.size() - 1; @@ -880,6 +883,25 @@ namespace widgets { const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + // Render bounding box (every time?) +#if 0 + if (model.boundingBoxMesh.has_value()) + { + Shader& gizmoShader = m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + gizmoShader.bind(glFunctions); + + gizmoShader.setUniform(glFunctions, ShaderConstants::kModelTransform, mModelMatrix); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); + gizmoShader.setUniform(glFunctions, ShaderConstants::kColor, glm::vec4(0.f, 0.f, 1.f, 1.f)); + + model.boundingBoxMesh.value().render(glFunctions, RenderTopology::RT_LINES); + + gizmoShader.unbind(glFunctions); + } +#endif + // Render all meshes for (const auto& mesh : model.meshes) { @@ -909,7 +931,7 @@ namespace widgets glFunctions->glBindTexture(GL_TEXTURE_2D, mesh.glTextureId); } - // 3. Render mesh + // 4. Render mesh if (m_renderMode & RenderMode::RM_TEXTURE) { // normal draw @@ -924,9 +946,7 @@ namespace widgets glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } - // 5. Draw bounding box - - // 6. Unbind texture and shader (expected to switch between materials, but not now) + // 5. Unbind texture and shader (expected to switch between materials, but not now) glFunctions->glBindTexture(GL_TEXTURE_2D, 0); texturedShader.unbind(glFunctions); } From db1b0945a13ea9bef878aa576c824e6ea38d63bd Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 11 Oct 2023 18:33:46 +0300 Subject: [PATCH 22/80] Implemented render list based renderer with sorting. But not all issues with alpha blending solved. --- BMEdit/Editor/Include/Render/Camera.h | 2 +- .../Include/Widgets/SceneRenderWidget.h | 24 ++- .../Source/Widgets/SceneRenderWidget.cpp | 182 ++++++++++-------- 3 files changed, 124 insertions(+), 84 deletions(-) diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index 573a00f..e17de27 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -71,7 +71,7 @@ namespace render } // returns the view matrix calculated using Euler Angles and the LookAt Matrix - glm::mat4 getViewMatrix() + glm::mat4 getViewMatrix() const { return glm::lookAt(Position, Position + Front, Up); } diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index faef6d8..37838b9 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -78,12 +79,29 @@ namespace widgets private: void updateProjectionMatrix(int w, int h); - void doRenderScene(QOpenGLFunctions_3_3_Core* glFunctions); void doLoadTextures(QOpenGLFunctions_3_3_Core* glFunctions); void doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions); void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); void doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions); - void doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility = false); + + /** + * @brief Entry which will be rendered in render loop + */ + struct RenderEntry + { + const gamelib::scene::SceneObject* pGeom { nullptr }; + glm::mat4 mModelMatrix { 1.f }; + glm::vec3 vPosition { .0f }; + uint32_t iPrimId { 0u }; + }; + + using RenderList = std::list; + + void doCollectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility); + void doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility); + void doPerformDrawOfRenderList(QOpenGLFunctions_3_3_Core* glFunctions, const RenderList& renderList, const render::Camera& camera); + + void invalidateRenderList(); private: // Data @@ -123,6 +141,8 @@ namespace widgets EViewMode m_eViewMode { EViewMode::VM_WORLD_VIEW }; gamelib::scene::SceneObject* m_pSceneObjectToView {}; + RenderList m_renderList {}; + struct GLResources; std::unique_ptr m_resources; }; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index df59321..da8ead9 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -19,6 +19,8 @@ #include #include #include + +#include #include @@ -128,6 +130,11 @@ namespace widgets funcs->glEnable(GL_BLEND); funcs->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (m_bDirtyProj) + { + updateProjectionMatrix(QWidget::width(), QWidget::height()); + } + // NOTE: Before render anything we need to look at material and check MATRenderState. // If it's applied we need to setup OpenGL into correct state to make perfect rendering switch (m_eState) @@ -163,18 +170,34 @@ namespace widgets break; case ELevelState::LS_READY: { + gamelib::scene::SceneObject* pRoot = nullptr; + bool bIgnoreVisibility = false; + if (m_eViewMode == EViewMode::VM_WORLD_VIEW) { - doRenderScene(funcs); + pRoot = m_pLevel->getSceneObjects()[0].get(); } else if (m_eViewMode == EViewMode::VM_GEOM_PREVIEW) { if (m_pSceneObjectToView) { - // Render object & ignore Invisible flag - doRenderGeom(funcs, m_pSceneObjectToView, true); + bIgnoreVisibility = true; + pRoot = m_pSceneObjectToView; } } + + if (!pRoot) + return; + + if (m_renderList.empty()) + { + doCollectRenderList(m_camera, pRoot, m_renderList, bIgnoreVisibility); + } + + if (!m_renderList.empty()) + { + doPerformDrawOfRenderList(funcs, m_renderList, m_camera); + } } break; } @@ -185,8 +208,11 @@ namespace widgets Q_UNUSED(w); Q_UNUSED(h); - // Will recalc on next frame - m_bDirtyProj = true; + // Update projection + updateProjectionMatrix(w, h); + + // Because our list of visible objects could be changed here + invalidateRenderList(); } void SceneRenderWidget::keyPressEvent(QKeyEvent* event) @@ -223,7 +249,10 @@ namespace widgets } if (bMoved) + { + invalidateRenderList(); repaint(); + } } QOpenGLWidget::keyPressEvent(event); @@ -248,14 +277,18 @@ namespace widgets return; } - float xoffset = xpos - m_mouseLastPosition.x(); - float yoffset = m_mouseLastPosition.y() - ypos; + float xOffset = static_cast(xpos - m_mouseLastPosition.x()); + float yOffset = static_cast(m_mouseLastPosition.y() - ypos); m_mouseLastPosition = event->pos(); // Update camera - qDebug() << "Mouse moved (" << xoffset << ";" << yoffset << ")"; - m_camera.processMouseMovement(xoffset, yoffset); + const float kMinMovement = 0.001f; + if (std::fabsf(xOffset - kMinMovement) > std::numeric_limits::epsilon() || std::fabsf(yOffset - kMinMovement) > std::numeric_limits::epsilon()) + { + invalidateRenderList(); + m_camera.processMouseMovement(xOffset, yOffset); + } } repaint(); @@ -288,6 +321,7 @@ namespace widgets m_eState = ELevelState::LS_NONE; m_pLevel = pLevel; m_bFirstMouseQuery = true; + invalidateRenderList(); resetViewMode(); resetRenderMode(); } @@ -300,6 +334,7 @@ namespace widgets m_eState = ELevelState::LS_NONE; m_pLevel = nullptr; m_bFirstMouseQuery = true; + invalidateRenderList(); resetViewMode(); resetRenderMode(); repaint(); @@ -310,10 +345,11 @@ namespace widgets { assert(sceneObject != nullptr); - if (sceneObject) + if (sceneObject != m_pSceneObjectToView) { m_eViewMode = EViewMode::VM_GEOM_PREVIEW; m_pSceneObjectToView = sceneObject; + invalidateRenderList(); repaint(); } } @@ -322,6 +358,7 @@ namespace widgets { m_eViewMode = EViewMode::VM_WORLD_VIEW; m_pSceneObjectToView = nullptr; + invalidateRenderList(); repaint(); } @@ -786,53 +823,20 @@ namespace widgets repaint(); // call to force jump into next state } - void SceneRenderWidget::doRenderScene(QOpenGLFunctions_3_3_Core* glFunctions) + void SceneRenderWidget::doCollectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility) { - LEVEL_SAFE_CHECK() - - // Update projection - if (m_bDirtyProj) - { - updateProjectionMatrix(QWidget::width(), QWidget::height()); - } - - // First of all we need to find ZBackdrop and render scene from this geom - m_pLevel->forEachObjectOfType("ZBackdrop", [this, glFunctions](const gamelib::scene::SceneObject::Ptr& sceneObject) -> bool { - doRenderGeom(glFunctions, sceneObject.get()); - - // Render only 1 ZBackdrop - return false; - }); - - // Then we need to find our 'current room' - // How to find current room? Idk, let's find all 'rooms'? - std::vector roomsToRender; - roomsToRender.reserve(16); + doVisitGeomToCollectIntoRenderList(pRootGeom, renderList, bIgnoreVisibility); - m_pLevel->forEachObjectOfTypeWithInheritance("ZROOM", [&roomsToRender](const gamelib::scene::SceneObject::Ptr& sceneObject) -> bool { - if (sceneObject->getName() != "ROOT" && sceneObject->getType()->getName() != "ZBackdrop") - { - // Save room - roomsToRender.emplace_back(sceneObject); - } + renderList.sort([&camera](const RenderEntry& a, const RenderEntry& b) -> bool { + const float fADistanceToCamera = glm::length(camera.Position - a.vPosition); + const float fBDistanceToCamera = glm::length(camera.Position - b.vPosition); - // Render every room - return true; + return fADistanceToCamera > fBDistanceToCamera; }); - - // Ok, we have rooms to draw, let's draw 'em all - for (const auto& room : roomsToRender) - { - doRenderGeom(glFunctions, room.get()); - } } - void SceneRenderWidget::doRenderGeom(QOpenGLFunctions_3_3_Core* glFunctions, const gamelib::scene::SceneObject* geom, bool bIgnoreVisibility) // NOLINT(*-no-recursion) + void SceneRenderWidget::doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* geom, RenderList& renderList, bool bIgnoreVisibility) { - // Save "scene" resolution - const glm::ivec2 viewResolution { QWidget::width(), QWidget::height() }; - - // Take params const auto primId = geom->getProperties().getObject("PrimId", 0); const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); @@ -845,7 +849,7 @@ namespace widgets // TODO: Check for culling here (object visible or not) // Check that object could be rendered by any way - if (primId != 0) + if (primId != 0 && m_resources->m_modelsCache.contains(primId)) { // Extract matrix from properties glm::mat4 mTransform = glm::mat4(1.f); @@ -878,35 +882,56 @@ namespace widgets glm::mat4 mTranslate = glm::translate(glm::mat4(1.f), vPosition); glm::mat4 mModelMatrix = mTranslate * mTransform; - // RenderGod - if (m_resources->m_modelsCache.contains(primId)) + // Store into render list + RenderEntry& entry = renderList.emplace_back(); + entry.vPosition = vPosition; + entry.mModelMatrix = mModelMatrix; + entry.pGeom = geom; + entry.iPrimId = primId; + } + + // Visit others + for (const auto& child : geom->getChildren()) + { + if (auto g = child.lock()) { - const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + doVisitGeomToCollectIntoRenderList(g.get(), renderList, bIgnoreVisibility); + } + } + } - // Render bounding box (every time?) -#if 0 - if (model.boundingBoxMesh.has_value()) - { - Shader& gizmoShader = m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; - gizmoShader.bind(glFunctions); + void SceneRenderWidget::doPerformDrawOfRenderList(QOpenGLFunctions_3_3_Core* glFunctions, const RenderList& renderList, const render::Camera& camera) + { + glm::ivec2 viewResolution { QWidget::width(), QWidget::height() }; - gizmoShader.setUniform(glFunctions, ShaderConstants::kModelTransform, mModelMatrix); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); - gizmoShader.setUniform(glFunctions, ShaderConstants::kColor, glm::vec4(0.f, 0.f, 1.f, 1.f)); + for (const auto& entry : renderList) + { + const Model& model = m_resources->m_models[m_resources->m_modelsCache[entry.iPrimId]]; - model.boundingBoxMesh.value().render(glFunctions, RenderTopology::RT_LINES); + // Render bounding box + if (model.boundingBoxMesh.has_value()) + { + Shader& gizmoShader = m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + gizmoShader.bind(glFunctions); - gizmoShader.unbind(glFunctions); - } -#endif + gizmoShader.setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mModelMatrix); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraView, camera.getViewMatrix()); + gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); + gizmoShader.setUniform(glFunctions, ShaderConstants::kColor, glm::vec4(0.f, 0.f, 1.f, 1.f)); + + model.boundingBoxMesh.value().render(glFunctions, RenderTopology::RT_LINES); + + gizmoShader.unbind(glFunctions); + } - // Render all meshes + // Render all meshes + if (m_resources->m_modelsCache.contains(entry.iPrimId)) + { for (const auto& mesh : model.meshes) { // Render single mesh - // 0. Check that we able to draw it + // 0. Check that we've able to draw it if (mesh.glTextureId == kInvalidResource) { // Draw "error" bounding box @@ -920,7 +945,7 @@ namespace widgets texturedShader.bind(glFunctions); // 2. Submit uniforms - texturedShader.setUniform(glFunctions, ShaderConstants::kModelTransform, mModelMatrix); + texturedShader.setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mModelMatrix); texturedShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); texturedShader.setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); texturedShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); @@ -951,16 +976,11 @@ namespace widgets texturedShader.unbind(glFunctions); } } - // otherwise draw red bbox! } + } - // Draw children - for (const auto& childRef : geom->getChildren()) - { - if (auto child = childRef.lock()) - { - doRenderGeom(glFunctions, child.get()); - } - } + void SceneRenderWidget::invalidateRenderList() + { + m_renderList.clear(); } } \ No newline at end of file From c45b1b9620f644e3e1700cef4f22808ffda9ae0d Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 11 Oct 2023 18:52:05 +0300 Subject: [PATCH 23/80] Object selection from SceneTree widget --- .../Include/Widgets/SceneRenderWidget.h | 4 +++ .../Source/Widgets/SceneRenderWidget.cpp | 29 ++++++++++++++++++- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 4 +++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 37838b9..4fdf578 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -53,6 +53,9 @@ namespace widgets void setWorldViewMode(); void resetViewMode(); + void setSelectedObject(gamelib::scene::SceneObject* sceneObject); + void resetSelectedObject(); + [[nodiscard]] RenderModeFlags getRenderMode() const; void setRenderMode(RenderModeFlags renderMode); void resetRenderMode(); @@ -140,6 +143,7 @@ namespace widgets EViewMode m_eViewMode { EViewMode::VM_WORLD_VIEW }; gamelib::scene::SceneObject* m_pSceneObjectToView {}; + gamelib::scene::SceneObject* m_pSelectedSceneObject { nullptr }; RenderList m_renderList {}; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index da8ead9..a280a60 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -367,6 +367,33 @@ namespace widgets setWorldViewMode(); } + void SceneRenderWidget::setSelectedObject(gamelib::scene::SceneObject* sceneObject) + { + if (!m_pLevel) + return; + + if (m_pSelectedSceneObject != sceneObject && sceneObject != nullptr) + { + m_pSelectedSceneObject = sceneObject; + invalidateRenderList(); + repaint(); + } + } + + void SceneRenderWidget::resetSelectedObject() + { + if (m_pSelectedSceneObject != nullptr) + { + m_pSelectedSceneObject = nullptr; + + if (m_pLevel) + { + invalidateRenderList(); + repaint(); + } + } + } + RenderModeFlags SceneRenderWidget::getRenderMode() const { return m_renderMode; @@ -909,7 +936,7 @@ namespace widgets const Model& model = m_resources->m_models[m_resources->m_modelsCache[entry.iPrimId]]; // Render bounding box - if (model.boundingBoxMesh.has_value()) + if (model.boundingBoxMesh.has_value() && entry.pGeom == m_pSelectedSceneObject) { Shader& gizmoShader = m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; gizmoShader.bind(glFunctions); diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 6a911da..d0b233c 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -329,6 +329,8 @@ void BMEditMainWindow::onSelectedSceneObject(const gamelib::scene::SceneObject* ui->sceneObjectTypeCombo->setEnabled(true); ui->sceneObjectTypeCombo->setCurrentText(QString::fromStdString(selectedSceneObject->getType()->getName())); + ui->sceneGLView->setSelectedObject(const_cast(selectedSceneObject)); + m_sceneObjectPropertiesModel->setGeom(const_cast(selectedSceneObject)); ui->geomControllers->setGeom(const_cast(selectedSceneObject)); @@ -337,6 +339,8 @@ void BMEditMainWindow::onSelectedSceneObject(const gamelib::scene::SceneObject* void BMEditMainWindow::onDeselectedSceneObject() { + ui->sceneGLView->resetSelectedObject(); + if (!m_sceneObjectPropertiesModel) { return; From 128b3832707d33d8982280b0c8dc8a2897658246 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 08:55:53 +0300 Subject: [PATCH 24/80] Fix some issues with alpha blending --- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index a280a60..412b4d9 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -124,7 +124,6 @@ namespace widgets // Z-Buffer testing funcs->glEnable(GL_DEPTH_TEST); - funcs->glDepthFunc(GL_LESS); // Blending funcs->glEnable(GL_BLEND); From 3922e10e944b295bfea69fdb8e22d324142042c6 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 10:03:07 +0300 Subject: [PATCH 25/80] Add bounding boxes into RenderEntry to use it in object picking. Added camera movement to player position by default. --- .../Include/Widgets/SceneRenderWidget.h | 2 + .../Source/Widgets/SceneRenderWidget.cpp | 86 +++++++++---------- .../Include/GameLib/Scene/SceneObject.h | 8 ++ .../Source/GameLib/Scene/SceneObject.cpp | 43 ++++++++++ 4 files changed, 94 insertions(+), 45 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 4fdf578..ca928e5 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -93,6 +94,7 @@ namespace widgets struct RenderEntry { const gamelib::scene::SceneObject* pGeom { nullptr }; + gamelib::BoundingBox sBoundingBox {}; glm::mat4 mModelMatrix { 1.f }; glm::vec3 vPosition { .0f }; uint32_t iPrimId { 0u }; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 412b4d9..efee3b4 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -119,6 +119,7 @@ namespace widgets } // Begin frame + funcs->glViewport(0, 0, QWidget::width(), QWidget::height()); funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); funcs->glClearColor(0.15f, 0.2f, 0.45f, 1.0f); @@ -266,8 +267,8 @@ namespace widgets { if (m_pLevel) { - float xpos = event->pos().x(); - float ypos = event->pos().y(); + float xpos = static_cast(event->pos().x()); + float ypos = static_cast(event->pos().y()); if (m_bFirstMouseQuery) { @@ -826,23 +827,43 @@ namespace widgets { LEVEL_SAFE_CHECK() - // Take scene and find first camera (ZCAMERA instance I guess) - glm::vec3 cameraPosition { .0f }; + // ---------------------------------------------------------- + // Ok, first of all let's try to find where located ZPlayer of ZHitman3 object + gamelib::scene::SceneObject::Ptr player = nullptr; - for (const auto& sceneObject : m_pLevel->getSceneObjects()) + m_pLevel->forEachObjectOfType("ZHitman3", [&player](const gamelib::scene::SceneObject::Ptr& sceneObject) -> bool { + player = sceneObject; + return true; + }); + + if (player) { - // Need to fix this code. Most 'ZCAMERA' objects refs to 2D scene view, need to find another better way to find 'start' camera. - // At least we can try to find ZPlayer/ZHitman3 object to put camera near to player - if (sceneObject->getType()->getName() == "ZCAMERA") + // Ok, level contains player. Let's take his room and move camera to player + const auto iPrimId = player->getProperties().getObject("PrimId", 0u); + const auto vPlayerPosition = player->getParent().lock()->getProperties().getObject("Position", glm::vec3(0.f)); + glm::vec3 vCameraPosition = vPlayerPosition; + + // In theory, we need to put camera around player, not in player. So we need to have bounding box of player to correct camera position + if (iPrimId != 0 && m_resources->m_modelsCache.contains(iPrimId)) { - // Nice, camera found! - cameraPosition = sceneObject->getProperties().getObject("Position", glm::vec3(.0f)); - break; + const auto& sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; + glm::vec3 vCenter = sBoundingBox.getCenter(); + vCenter.y += 1.5f * vCenter.y; + + vCameraPosition += vCenter; } - } - m_camera.setPosition(cameraPosition); // TODO: Need teleport camera to player and put under player's head + m_camera.setPosition(vCameraPosition); + qDebug() << "Move camera to object " << player->getName() << " at (" << vCameraPosition.x << ';' << vCameraPosition.y << ';' << vCameraPosition.z << ")"; + } + else + { + // Bad for us, player not found. Need to put camera somewhere else + qDebug() << "No player on scene. Camera moved to (0;0;0)"; + m_camera.setPosition(glm::vec3(0.f)); + } + // ---------------------------------------------------------- emit resourcesReady(); m_eState = ELevelState::LS_READY; // Done! @@ -861,12 +882,11 @@ namespace widgets }); } - void SceneRenderWidget::doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* geom, RenderList& renderList, bool bIgnoreVisibility) + void SceneRenderWidget::doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* geom, RenderList& renderList, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { const auto primId = geom->getProperties().getObject("PrimId", 0); const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); - const auto mMatrix = geom->getProperties().getObject("Matrix", glm::mat3(1.f)); // Don't draw invisible things if (bInvisible && !bIgnoreVisibility) @@ -877,36 +897,7 @@ namespace widgets // Check that object could be rendered by any way if (primId != 0 && m_resources->m_modelsCache.contains(primId)) { - // Extract matrix from properties - glm::mat4 mTransform = glm::mat4(1.f); - - /** - * Convert from DX9 to OpenGL - * - * | m00 m10 m20 | - * mSrc = | m01 m11 m21 | - * | m02 m12 m22 | - * - * | m02 m01 m00 | - * mDst = | m12 m11 m21 | - * | m22 m21 m20 | - */ - mTransform[0][0] = mMatrix[0][2]; - mTransform[1][0] = mMatrix[0][1]; - mTransform[2][0] = mMatrix[0][0]; - - mTransform[0][1] = mMatrix[1][2]; - mTransform[1][1] = mMatrix[1][1]; - mTransform[2][1] = mMatrix[2][1]; - - mTransform[0][2] = mMatrix[2][2]; - mTransform[1][2] = mMatrix[2][1]; - mTransform[2][2] = mMatrix[2][0]; - - mTransform[3][3] = 1.f; - - glm::mat4 mTranslate = glm::translate(glm::mat4(1.f), vPosition); - glm::mat4 mModelMatrix = mTranslate * mTransform; + glm::mat4 mModelMatrix = geom->getLocalTransform(); // Store into render list RenderEntry& entry = renderList.emplace_back(); @@ -914,6 +905,11 @@ namespace widgets entry.mModelMatrix = mModelMatrix; entry.pGeom = geom; entry.iPrimId = primId; + + // Store bounding box + const Model& model = m_resources->m_models[m_resources->m_modelsCache[entry.iPrimId]]; + entry.sBoundingBox.min = model.boundingBox.min; + entry.sBoundingBox.max = model.boundingBox.max; } // Visit others diff --git a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h index c6d30e0..92c03fb 100644 --- a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h +++ b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -56,6 +57,13 @@ namespace gamelib::scene [[nodiscard]] const std::vector &getChildren() const; [[nodiscard]] std::vector &getChildren(); + /** + * @brief Calculate transform matrix for OpenGL and other render API buddies + * @note This function calculates matrix at runtime. So, it's not huge operation but avoid of frequency calling of this function, please. + * @return model matrix (scale, rotation and translate operations applied) + */ + [[nodiscard]] glm::mat4 getLocalTransform() const; + private: std::string m_name {}; ///< Name of geom uint32_t m_typeId { 0u }; ///< Type ID of geom diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index e7bd299..4d5a68a 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -1,4 +1,8 @@ #include +#include +#include +#include +#include #include @@ -110,4 +114,43 @@ namespace gamelib::scene { return m_children; } + + glm::mat4 SceneObject::getLocalTransform() const + { + const auto vPosition = getProperties().getObject("Position", glm::vec3(0.f)); + const auto mMatrix = getProperties().getObject("Matrix", glm::mat3(1.f)); + + // Extract matrix from properties + glm::mat4 mTransform = glm::mat4(1.f); + + /** + * Convert from DX9 to OpenGL + * + * | m00 m10 m20 | + * mSrc = | m01 m11 m21 | + * | m02 m12 m22 | + * + * | m02 m01 m00 | + * mDst = | m12 m11 m21 | + * | m22 m21 m20 | + */ + mTransform[0][0] = mMatrix[0][2]; + mTransform[1][0] = mMatrix[0][1]; + mTransform[2][0] = mMatrix[0][0]; + + mTransform[0][1] = mMatrix[1][2]; + mTransform[1][1] = mMatrix[1][1]; + mTransform[2][1] = mMatrix[2][1]; + + mTransform[0][2] = mMatrix[2][2]; + mTransform[1][2] = mMatrix[2][1]; + mTransform[2][2] = mMatrix[2][0]; + + mTransform[3][3] = 1.f; + + glm::mat4 mTranslate = glm::translate(glm::mat4(1.f), vPosition); + glm::mat4 mModelMatrix = mTranslate * mTransform; + + return mModelMatrix; + } } \ No newline at end of file From 8851b547b9671842a322e51d5c3dae8ed1964dde Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 11:00:49 +0300 Subject: [PATCH 26/80] Fixed PRM reader of VF_34 format. Also, trying to fix build on CI. --- .../Include/Widgets/SceneRenderWidget.h | 9 ++++++--- .../Source/Widgets/SceneRenderWidget.cpp | 2 +- .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index ca928e5..c7e49d4 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -1,17 +1,20 @@ #pragma once #include +#include +#include +#include + #include #include #include + #include #include #include + #include #include -#include -#include -#include namespace renderer diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index efee3b4..979243f 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -854,7 +854,7 @@ namespace widgets } m_camera.setPosition(vCameraPosition); - qDebug() << "Move camera to object " << player->getName() << " at (" << vCameraPosition.x << ';' << vCameraPosition.y << ';' << vCameraPosition.z << ")"; + qDebug() << "Move camera to object " << QString::fromStdString(player->getName()) << " at (" << vCameraPosition.x << ';' << vCameraPosition.y << ';' << vCameraPosition.z << ")"; } else { diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index 388d430..7d02a1c 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -190,17 +190,20 @@ namespace gamelib::prm break; case VertexFormat::VF_34: { - glm::vec3& vertex = mesh.vertices.emplace_back(); - vertexReader.read(glm::value_ptr(vertex), 3); + for (uint32_t j = 0; j < vertexCount; j++) + { + glm::vec3& vertex = mesh.vertices.emplace_back(); + vertexReader.read(glm::value_ptr(vertex), 3); - // TODO: Fix this! - ZBioHelpers::seekBy(&vertexReader, 0x18); + // TODO: Fix this! + ZBioHelpers::seekBy(&vertexReader, 0x18); - glm::vec2& uv = mesh.uvs.emplace_back(); - vertexReader.read(glm::value_ptr(uv), 2); + glm::vec2& uv = mesh.uvs.emplace_back(); + vertexReader.read(glm::value_ptr(uv), 2); - // TODO: Fix this - ZBioHelpers::seekBy(&vertexReader, 0x8); + // TODO: Fix this + ZBioHelpers::seekBy(&vertexReader, 0x8); + } } break; default: From 3d73b7b9976d200b4d2a8fedd4c60d2582e53a34 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 13:48:19 +0300 Subject: [PATCH 27/80] Transform cache. Added support of variations and variation filtering. --- .../Models/SceneObjectPropertiesModel.h | 3 ++ BMEdit/Editor/Include/Render/Model.h | 1 + .../Include/Widgets/SceneRenderWidget.h | 4 +- .../Models/SceneObjectPropertiesModel.cpp | 2 + .../Source/Widgets/SceneRenderWidget.cpp | 49 ++++++++++++++++--- BMEdit/Editor/UI/Include/BMEditMainWindow.h | 1 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 7 +++ .../GameLib/Include/GameLib/PRM/PRMEntries.h | 1 + .../Include/GameLib/Scene/SceneObject.h | 19 +++++++ .../GameLib/Source/GameLib/PRM/PRMEntries.cpp | 7 ++- .../Source/GameLib/Scene/SceneObject.cpp | 25 ++++++++++ 11 files changed, 108 insertions(+), 11 deletions(-) diff --git a/BMEdit/Editor/Include/Models/SceneObjectPropertiesModel.h b/BMEdit/Editor/Include/Models/SceneObjectPropertiesModel.h index 7b9745f..8464273 100644 --- a/BMEdit/Editor/Include/Models/SceneObjectPropertiesModel.h +++ b/BMEdit/Editor/Include/Models/SceneObjectPropertiesModel.h @@ -23,6 +23,9 @@ namespace models void resetLevel(); void resetGeom(); + signals: + void objectPropertiesChanged(const gamelib::scene::SceneObject*); + private slots: void onValueChanged(); diff --git a/BMEdit/Editor/Include/Render/Model.h b/BMEdit/Editor/Include/Render/Model.h index 2fde37b..f224147 100644 --- a/BMEdit/Editor/Include/Render/Model.h +++ b/BMEdit/Editor/Include/Render/Model.h @@ -26,6 +26,7 @@ namespace render GLuint ibo { kInvalidResource }; GLuint glTextureId { kInvalidResource }; /// Render OpenGL texture resource handle uint16_t materialId { 0 }; /// Id of material from Glacier mesh (just copy) + uint8_t variationId { 0 }; // Id of variation (some meshes could be attached to abstract 'variation' so each variation could be interpreted as 'group of meshes') int trianglesCount { 0 }; diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index c7e49d4..c10625b 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -73,8 +73,10 @@ namespace widgets public slots: void onRedrawRequested(); + // Use when object properties changed and his 'world transform' could be changed. + void onObjectMoved(gamelib::scene::SceneObject* sceneObject); + protected: - void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; diff --git a/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp index 02f1a16..654143e 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp @@ -77,6 +77,8 @@ namespace models if (value.value() != m_geom->getProperties()) { m_geom->getProperties() = value.value(); + + emit objectPropertiesChanged(m_geom); } } } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 979243f..0ba155a 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ namespace widgets std::vector m_shaders {}; std::vector m_models {}; std::unordered_map m_modelsCache {}; /// primitive index to model index in m_models + std::unordered_map m_modelTransformCache {}; /// transformations cache GLuint m_iGLDebugTexture { 0 }; GLuint m_iGLMissingTexture { 0 }; GLuint m_iGLUnsupportedMaterialTexture { 0 }; @@ -104,12 +106,6 @@ namespace widgets SceneRenderWidget::~SceneRenderWidget() noexcept = default; - void SceneRenderWidget::initializeGL() - { - // Create resource holder - m_resources = std::make_unique(); - } - void SceneRenderWidget::paintGL() { auto funcs = QOpenGLVersionFunctionsFactory::get(QOpenGLContext::currentContext()); @@ -142,6 +138,11 @@ namespace widgets case ELevelState::LS_NONE: { if (m_pLevel) { + // Create base for resources + assert(m_resources == nullptr && "Leaked resources"); + m_resources = std::make_unique(); + + // Run process m_eState = ELevelState::LS_LOAD_TEXTURES; } else if (m_resources && m_resources->hasResources()) { m_resources->discard(funcs); @@ -318,6 +319,7 @@ namespace widgets { if (m_pLevel != pLevel) { + m_resources = nullptr; m_eState = ELevelState::LS_NONE; m_pLevel = pLevel; m_bFirstMouseQuery = true; @@ -331,6 +333,7 @@ namespace widgets { if (m_pLevel != nullptr) { + m_resources = nullptr; m_eState = ELevelState::LS_NONE; m_pLevel = nullptr; m_bFirstMouseQuery = true; @@ -426,6 +429,15 @@ namespace widgets repaint(); } + void SceneRenderWidget::onObjectMoved(gamelib::scene::SceneObject* sceneObject) + { + if (!sceneObject || !m_pLevel || !m_resources) + return; + + m_resources->m_modelTransformCache[sceneObject] = sceneObject->getWorldTransform(); + repaint(); + } + #define LEVEL_SAFE_CHECK() \ if (!m_pLevel) \ { \ @@ -611,6 +623,7 @@ namespace widgets // And upload it to GPU Mesh& glMesh = glModel.meshes.emplace_back(); glMesh.trianglesCount = mesh.trianglesCount; + glMesh.variationId = mesh.variationId; if (!glMesh.setup(glFunctions, GlacierVertex::g_FormatDescription, vertices, indices, false)) { @@ -897,12 +910,22 @@ namespace widgets // Check that object could be rendered by any way if (primId != 0 && m_resources->m_modelsCache.contains(primId)) { - glm::mat4 mModelMatrix = geom->getLocalTransform(); + glm::mat4 mWorldTransform = glm::mat4(1.f); + + if (auto it = m_resources->m_modelTransformCache.find(const_cast(geom)); it != m_resources->m_modelTransformCache.end()) + { + mWorldTransform = it->second; + } + else + { + mWorldTransform = geom->getWorldTransform(); + m_resources->m_modelTransformCache[const_cast(geom)] = mWorldTransform; + } // Store into render list RenderEntry& entry = renderList.emplace_back(); entry.vPosition = vPosition; - entry.mModelMatrix = mModelMatrix; + entry.mModelMatrix = mWorldTransform; entry.pGeom = geom; entry.iPrimId = primId; @@ -961,6 +984,16 @@ namespace widgets continue; } + // Filter mesh by 'variation id' + if (const auto& properties = entry.pGeom->getProperties(); properties.hasProperty("MeshVariantId")) + { + const auto requiredVariationId = properties.getObject("MeshVariantId", 0); + if (requiredVariationId != mesh.variationId) + { + continue; + } + } + // 1. Activate default shader Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 0445825..3ee5375 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -82,6 +82,7 @@ public slots: void onContextMenuRequestedForSceneTreeNode(const QPoint& point); void onLevelAssetsLoaded(); void onLevelAssetsLoadFailed(const QString& reason); + void onSceneObjectPropertyChanged(const gamelib::scene::SceneObject* geom); private: // UI diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index d0b233c..ea39e21 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -518,6 +518,11 @@ void BMEditMainWindow::onLevelAssetsLoadFailed(const QString& reason) QMessageBox::critical(this, QString("Scene render failed :("), QString("An error occurred while loading scene assets:\n%1").arg(reason)); } +void BMEditMainWindow::onSceneObjectPropertyChanged(const gamelib::scene::SceneObject* geom) +{ + ui->sceneGLView->onObjectMoved(const_cast(geom)); +} + void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); @@ -668,6 +673,8 @@ void BMEditMainWindow::initProperties() ui->propertiesView->setItemDelegateForColumn(1, m_typePropertyItemDelegate); ui->propertiesView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); ui->propertiesView->verticalHeader()->setSectionResizeMode(QHeaderView::Interactive); + + connect(m_sceneObjectPropertiesModel, &models::SceneObjectPropertiesModel::objectPropertiesChanged, this, &BMEditMainWindow::onSceneObjectPropertyChanged); } void BMEditMainWindow::initSceneProperties() diff --git a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h index d19b639..c5a1a4e 100644 --- a/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h +++ b/BMEdit/GameLib/Include/GameLib/PRM/PRMEntries.h @@ -59,6 +59,7 @@ namespace gamelib::prm uint8_t unkD = 0; uint8_t lod = 0; uint16_t material_id = 0; + uint8_t variationId = 0; int32_t diffuse_id = 0; int32_t normal_id = 0; int32_t specular_id = 0; diff --git a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h index 92c03fb..38e82a8 100644 --- a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h +++ b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h @@ -64,6 +64,25 @@ namespace gamelib::scene */ [[nodiscard]] glm::mat4 getLocalTransform() const; + /** + * @return Position object from ZGEOM or (0;0;0) + */ + [[nodiscard]] glm::vec3 getPosition() const; + + /** + * @note Unlike getLocalTransform this matrix created for DX9 renderer. Do not use this matrix without preparation! + * @note If object does not contains Matrix object this method will return identity matrix for OpenGL (specific of glm). To avoid of bugs use hasProperty before! + * @return Matrix object from ZGEOM or identity matrix + */ + [[nodiscard]] glm::mat3 getOriginalTransform() const; + + /** + * @brief Calculate world transform of object + * @note This method iterates over all parents and multiply all matrices into one combined matrix. It may take a while so use external caches (because SceneObject does not make any caches itself) + * @return World model matrix of object + */ + [[nodiscard]] glm::mat4 getWorldTransform() const; + private: std::string m_name {}; ///< Name of geom uint32_t m_typeId { 0u }; ///< Type ID of geom diff --git a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp index 7d02a1c..591b262 100644 --- a/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRM/PRMEntries.cpp @@ -42,8 +42,11 @@ namespace gamelib::prm if (mesh.lod & (uint8_t)1 == (uint8_t)1) { - // Seed another 3 bytes? - ZBioHelpers::seekBy(binaryReader, 0x3); + // Read mesh variation index + mesh.variationId = binaryReader->read(); + + // Seed another 2 bytes? + ZBioHelpers::seekBy(binaryReader, 0x2); // Read material id mesh.material_id = binaryReader->read(); diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index 4d5a68a..49fa74f 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -153,4 +153,29 @@ namespace gamelib::scene return mModelMatrix; } + + glm::vec3 SceneObject::getPosition() const + { + return getProperties().getObject("Position", glm::vec3(0.f)); + } + + glm::mat3 SceneObject::getOriginalTransform() const + { + return getProperties().getObject("Matrix", glm::mat3(1.f)); + } + + glm::mat4 SceneObject::getWorldTransform() const + { + const SceneObject* current = this; + glm::mat4 mWorldMatrix = glm::mat4(1.f); + + while (current) + { + mWorldMatrix = mWorldMatrix * current->getLocalTransform(); + + current = current->getParent().expired() ? nullptr : current->getParent().lock().get(); + } + + return mWorldMatrix; + } } \ No newline at end of file From e18e47cd71e585c5e3196b8d4649041eed0d1a19 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 17:54:28 +0300 Subject: [PATCH 28/80] Added ZGEOMREF path resolver. Fixed ZItem rendering. Reversed field rItemTemplate from ZItem.json --- Assets/g1/ZItem.json | 2 +- .../Source/Widgets/SceneRenderWidget.cpp | 36 ++++++++++++++-- BMEdit/GameLib/Include/GameLib/Level.h | 2 + .../Include/GameLib/PRP/PRPInstruction.h | 1 + .../Include/GameLib/PRP/PRPMathTypes.h | 3 +- .../Include/GameLib/Scene/SceneObject.h | 6 +++ BMEdit/GameLib/Source/GameLib/Level.cpp | 43 +++++++++++++++++++ .../Source/GameLib/Scene/SceneObject.cpp | 39 +++++++++++------ 8 files changed, 113 insertions(+), 19 deletions(-) diff --git a/Assets/g1/ZItem.json b/Assets/g1/ZItem.json index cc0398a..313963d 100644 --- a/Assets/g1/ZItem.json +++ b/Assets/g1/ZItem.json @@ -8,7 +8,7 @@ "parent": "ZGROUP", "properties": [ { - "name": "property_4C", + "name": "rItemTemplate", "offset": 76, "typename": "ZGEOMREF" }, diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0ba155a..0ffea47 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -853,7 +854,7 @@ namespace widgets { // Ok, level contains player. Let's take his room and move camera to player const auto iPrimId = player->getProperties().getObject("PrimId", 0u); - const auto vPlayerPosition = player->getParent().lock()->getProperties().getObject("Position", glm::vec3(0.f)); + const auto vPlayerPosition = player->getParent().lock()->getPosition(); glm::vec3 vCameraPosition = vPlayerPosition; // In theory, we need to put camera around player, not in player. So we need to have bounding box of player to correct camera position @@ -897,14 +898,43 @@ namespace widgets void SceneRenderWidget::doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* geom, RenderList& renderList, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { - const auto primId = geom->getProperties().getObject("PrimId", 0); const bool bInvisible = geom->getProperties().getObject("Invisible", false); - const auto vPosition = geom->getProperties().getObject("Position", glm::vec3(0.f)); + const auto vPosition = geom->getPosition(); + auto primId = geom->getProperties().getObject("PrimId", 0); // Don't draw invisible things if (bInvisible && !bIgnoreVisibility) return; + // NOTE: Need to refactor this place and move it into separated area + if (geom->isInheritedOf("ZItem")) + { + //ZItems has no PrimId. Instead of this they are refs to another geom by path + auto rItemTemplatePath = geom->getProperties().getObject("rItemTemplate"); + const auto pItemTemplate = m_pLevel->getSceneObjectByGEOMREF(rItemTemplatePath); + + if (pItemTemplate) + { + gamelib::scene::SceneObject::Ptr pItem = nullptr; + + // Item found by path. That's cool! But this is not an item, for item need to ask Ground... object inside + for (const auto& childRef : pItemTemplate->getChildren()) + { + if (auto child = childRef.lock(); child && child->getName().starts_with("Ground")) + { + pItem = child; + break; + } + } + + if (pItem) + { + // Nice! Now we ready to replace primId + primId = pItem->getProperties().getObject("PrimId"); + } + } + } + // TODO: Check for culling here (object visible or not) // Check that object could be rendered by any way diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 3320ecf..abc8ac8 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -88,6 +88,8 @@ namespace gamelib [[nodiscard]] const std::vector& getSceneObjects() const; + [[nodiscard]] scene::SceneObject::Ptr getSceneObjectByGEOMREF(const std::string& path) const; + void dumpAsset(io::AssetKind assetKind, std::vector &outBuffer) const; void forEachObjectOfType(const std::string& objectTypeName, const std::function& pred) const; diff --git a/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h b/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h index a51ebb4..c07fbf8 100644 --- a/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h +++ b/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h @@ -64,6 +64,7 @@ namespace gamelib::prp template <> int32_t get() const { return trivial.i32; } template <> float get() const { return trivial.f32; } template <> double get() const { return trivial.f64; } + template <> std::string_view get() const { return str; } template <> const std::string& get() const { return str; } template <> const RawData& get() const { return raw; } template <> const StringArray& get() const { return stringArray; } diff --git a/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h b/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h index 69b0042..73f5241 100644 --- a/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h +++ b/BMEdit/GameLib/Include/GameLib/PRP/PRPMathTypes.h @@ -92,7 +92,8 @@ namespace gamelib { if (instructions.size() == 1) { - return instructions[0].getOperand().get(); + const std::string& v = instructions[0].getOperand().get(); + return v; } assert(false && "Bad object"); diff --git a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h index 38e82a8..eb982b4 100644 --- a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h +++ b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h @@ -57,6 +57,12 @@ namespace gamelib::scene [[nodiscard]] const std::vector &getChildren() const; [[nodiscard]] std::vector &getChildren(); + /** + * @param baseType + * @return return true if game object inherited of baseType + */ + [[nodiscard]] bool isInheritedOf(const std::string& baseType) const; + /** * @brief Calculate transform matrix for OpenGL and other render API buddies * @note This function calculates matrix at runtime. So, it's not huge operation but avoid of frequency calling of this function, please. diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 056b62e..98cd1d3 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -10,6 +10,10 @@ #include #include +#include +#include +#include + namespace gamelib { @@ -145,6 +149,45 @@ namespace gamelib return m_sceneObjects; } + [[nodiscard]] scene::SceneObject::Ptr Level::getSceneObjectByGEOMREF(const std::string& path) const + { + std::stringstream pathStream { path }; + std::string segment; + std::vector dividedPath {}; + + while(std::getline(pathStream, segment, '\\')) + { + dividedPath.push_back(segment); + } + + scene::SceneObject::Ptr object = m_sceneObjects[0]; + + for (const auto& pathBlock : dividedPath) + { + if (pathBlock == "ROOT") + continue; + + bool bFound = false; + + for (const auto& childrenRef : object->getChildren()) + { + if (auto child = childrenRef.lock(); child && child->getName() == pathBlock) + { + bFound = true; + object = child; + break; + } + } + + if (!bFound) + { + return nullptr; + } + } + + return object; + } + void Level::dumpAsset(io::AssetKind assetKind, std::vector &outBuffer) const { if (assetKind == io::AssetKind::PROPERTIES) diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index 49fa74f..1465fa8 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include #include @@ -115,32 +117,41 @@ namespace gamelib::scene return m_children; } + bool SceneObject::isInheritedOf(const std::string& baseType) const + { + const auto* type = getType(); + + if (!type) return false; + + while (type) + { + if (type->getName() == baseType) + return true; + + if (type->getKind() != TypeKind::COMPLEX) + return false; + + type = static_cast(type)->getParent(); // NOLINT(*-pro-type-static-cast-downcast) + } + + return false; + } + glm::mat4 SceneObject::getLocalTransform() const { - const auto vPosition = getProperties().getObject("Position", glm::vec3(0.f)); - const auto mMatrix = getProperties().getObject("Matrix", glm::mat3(1.f)); + const auto vPosition = getPosition(); + const auto mMatrix = getOriginalTransform(); // Extract matrix from properties glm::mat4 mTransform = glm::mat4(1.f); - /** - * Convert from DX9 to OpenGL - * - * | m00 m10 m20 | - * mSrc = | m01 m11 m21 | - * | m02 m12 m22 | - * - * | m02 m01 m00 | - * mDst = | m12 m11 m21 | - * | m22 m21 m20 | - */ mTransform[0][0] = mMatrix[0][2]; mTransform[1][0] = mMatrix[0][1]; mTransform[2][0] = mMatrix[0][0]; mTransform[0][1] = mMatrix[1][2]; mTransform[1][1] = mMatrix[1][1]; - mTransform[2][1] = mMatrix[2][1]; + mTransform[2][1] = mMatrix[1][0]; mTransform[0][2] = mMatrix[2][2]; mTransform[1][2] = mMatrix[2][1]; From e9e21233572bc6fb90ff0f3c77b2ef17aff70d38 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 12 Oct 2023 20:49:05 +0300 Subject: [PATCH 29/80] Sync of textures between TEX and game preview. --- BMEdit/Editor/Include/Render/Texture.h | 3 + .../Include/Widgets/SceneRenderWidget.h | 10 +- BMEdit/Editor/Source/Render/Texture.cpp | 39 ++++++++ .../Source/Widgets/SceneRenderWidget.cpp | 91 ++++++++++++++----- BMEdit/Editor/UI/Include/BMEditMainWindow.h | 1 + BMEdit/Editor/UI/Include/ViewTexturesDialog.h | 3 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 7 ++ .../Editor/UI/Source/ViewTexturesDialog.cpp | 3 + 8 files changed, 127 insertions(+), 30 deletions(-) diff --git a/BMEdit/Editor/Include/Render/Texture.h b/BMEdit/Editor/Include/Render/Texture.h index 16cb914..5cfd8aa 100644 --- a/BMEdit/Editor/Include/Render/Texture.h +++ b/BMEdit/Editor/Include/Render/Texture.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -19,6 +20,8 @@ namespace render Texture(); + bool setup(QOpenGLFunctions_3_3_Core* gapi, const gamelib::tex::TEXEntry& gTex); + void discard(QOpenGLFunctions_3_3_Core* gapi); void bind(QOpenGLFunctions_3_3_Core* gapi); diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index c10625b..222d810 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -17,11 +18,6 @@ #include -namespace renderer -{ - -} - class QOpenGLFunctions_3_3_Core; namespace widgets @@ -66,6 +62,8 @@ namespace widgets void moveCameraTo(const glm::vec3& position); + void reloadTexture(uint32_t textureIndex); + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); @@ -92,6 +90,8 @@ namespace widgets void doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions); void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); void doResetCameraState(QOpenGLFunctions_3_3_Core* glFunctions); + void doPrepareInvalidatedResources(QOpenGLFunctions_3_3_Core* glFunctions); + [[nodiscard]] glm::ivec2 getViewportSize() const; /** * @brief Entry which will be rendered in render loop diff --git a/BMEdit/Editor/Source/Render/Texture.cpp b/BMEdit/Editor/Source/Render/Texture.cpp index 6a72ab0..5e3f943 100644 --- a/BMEdit/Editor/Source/Render/Texture.cpp +++ b/BMEdit/Editor/Source/Render/Texture.cpp @@ -1,3 +1,4 @@ +#include #include @@ -5,6 +6,43 @@ namespace render { Texture::Texture() = default; + bool Texture::setup(QOpenGLFunctions_3_3_Core* gapi, const gamelib::tex::TEXEntry& gTex) + { + if (texture != kInvalidResource) + { + assert(false && "Resource must be not initialised here!"); + return false; + } + + uint16_t w { 0 }, h { 0 }; + std::unique_ptr decompressedMemBlk = editor::TextureProcessor::decompressRGBA(gTex, w, h, 0); // + if (!decompressedMemBlk) + { + return false; + } + + // Store texture index from TEX container + index = std::make_optional(gTex.m_index); + texPath = gTex.m_fileName; // just copy file name from tex (if it defined!) + width = w; + height = h; + + // Create GL resource + gapi->glGenTextures(1, &texture); + gapi->glBindTexture(GL_TEXTURE_2D, texture); + gapi->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, decompressedMemBlk.get()); + + gapi->glGenerateMipmap(GL_TEXTURE_2D); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + gapi->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + gapi->glBindTexture(GL_TEXTURE_2D, 0); + + return true; + } + void Texture::discard(QOpenGLFunctions_3_3_Core *gapi) { width = height = 0; @@ -12,6 +50,7 @@ namespace render if (texture != kInvalidResource) { gapi->glDeleteTextures(1, &texture); + texture = kInvalidResource; } } diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0ffea47..e1ac15b 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include +#include #include #include @@ -37,6 +37,7 @@ namespace widgets std::vector m_models {}; std::unordered_map m_modelsCache {}; /// primitive index to model index in m_models std::unordered_map m_modelTransformCache {}; /// transformations cache + std::unordered_set m_invalidatedTextures; /// Set of textures who need to be reloaded on next frame GLuint m_iGLDebugTexture { 0 }; GLuint m_iGLMissingTexture { 0 }; GLuint m_iGLUnsupportedMaterialTexture { 0 }; @@ -116,7 +117,8 @@ namespace widgets } // Begin frame - funcs->glViewport(0, 0, QWidget::width(), QWidget::height()); + const auto vp = getViewportSize(); + funcs->glViewport(0, 0, vp.x, vp.y); funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); funcs->glClearColor(0.15f, 0.2f, 0.45f, 1.0f); @@ -129,7 +131,7 @@ namespace widgets if (m_bDirtyProj) { - updateProjectionMatrix(QWidget::width(), QWidget::height()); + updateProjectionMatrix(vp.x, vp.y); } // NOTE: Before render anything we need to look at material and check MATRenderState. @@ -172,6 +174,10 @@ namespace widgets break; case ELevelState::LS_READY: { + // Prepare invalidated stuff + doPrepareInvalidatedResources(funcs); + + // Render scene gamelib::scene::SceneObject* pRoot = nullptr; bool bIgnoreVisibility = false; @@ -424,6 +430,14 @@ namespace widgets repaint(); } + void SceneRenderWidget::reloadTexture(uint32_t textureIndex) + { + if (!m_pLevel) + return; + + m_resources->m_invalidatedTextures.insert(textureIndex); + } + void SceneRenderWidget::onRedrawRequested() { if (m_pLevel) @@ -468,33 +482,15 @@ namespace widgets } // Ok, texture is ok - load it - Texture newTexture; + Texture newTexture {}; - std::unique_ptr decompressedMemBlk = editor::TextureProcessor::decompressRGBA(texture, newTexture.width, newTexture.height, 0); // - if (!decompressedMemBlk) + if (!newTexture.setup(glFunctions, texture)) { m_resources->m_textures.emplace_back(); - qWarning() << "Failed to decompress texture #" << texture.m_index << " to RGBA sequence"; + qWarning() << "Failed to load texture #" << texture.m_index << ". Reason: setup failed"; continue; } - // Store texture index from TEX container - newTexture.index = std::make_optional(texture.m_index); - newTexture.texPath = texture.m_fileName; // just copy file name from tex (if it defined!) - - // Create GL resource - glFunctions->glGenTextures(1, &newTexture.texture); - glFunctions->glBindTexture(GL_TEXTURE_2D, newTexture.texture); - glFunctions->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTexture.width, newTexture.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, decompressedMemBlk.get()); - - glFunctions->glGenerateMipmap(GL_TEXTURE_2D); - glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glFunctions->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glFunctions->glBindTexture(GL_TEXTURE_2D, 0); - // Precache debug texture if it's not precached yet static constexpr const char* kGlacierMissingTex = "_Glacier/Missing_01"; static constexpr const char* kWorldColiTex = "_TEST/Worldcoli"; @@ -884,6 +880,51 @@ namespace widgets repaint(); // call to force jump into next state } + void SceneRenderWidget::doPrepareInvalidatedResources(QOpenGLFunctions_3_3_Core* glFunctions) + { + LEVEL_SAFE_CHECK() + + if (!m_resources->m_invalidatedTextures.empty()) + { + for (auto& texture : m_resources->m_textures) + { + if (texture.index.has_value() && m_resources->m_invalidatedTextures.contains(texture.index.value())) + { + const uint32_t textureIndex = texture.index.value(); + + // Unload texture + texture.discard(glFunctions); + + // Load texture (need to find actual entry in global textures pool... bruh) + const auto& allTextures = m_pLevel->getSceneTextures()->entries; + auto it = std::find_if(allTextures.begin(), allTextures.end(), [textureIndex](const gamelib::tex::TEXEntry& ent) -> bool { + return ent.m_index == textureIndex; + }); + + if (it != allTextures.end()) + { + if (texture.setup(glFunctions, *it)) + { + qDebug() << "Texture #" << textureIndex << " reloaded!"; + } + else + { + qWarning() << "Failed to update texture #" << textureIndex; + } + } + + // Validated + m_resources->m_invalidatedTextures.erase(textureIndex); + } + } + } + } + + glm::ivec2 SceneRenderWidget::getViewportSize() const + { + return { QWidget::width(), QWidget::height() }; + } + void SceneRenderWidget::doCollectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility) { doVisitGeomToCollectIntoRenderList(pRootGeom, renderList, bIgnoreVisibility); @@ -977,7 +1018,7 @@ namespace widgets void SceneRenderWidget::doPerformDrawOfRenderList(QOpenGLFunctions_3_3_Core* glFunctions, const RenderList& renderList, const render::Camera& camera) { - glm::ivec2 viewResolution { QWidget::width(), QWidget::height() }; + glm::ivec2 viewResolution = getViewportSize(); for (const auto& entry : renderList) { diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 3ee5375..84f7d7f 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -83,6 +83,7 @@ public slots: void onLevelAssetsLoaded(); void onLevelAssetsLoadFailed(const QString& reason); void onSceneObjectPropertyChanged(const gamelib::scene::SceneObject* geom); + void onTextureChanged(uint32_t textureIndex); private: // UI diff --git a/BMEdit/Editor/UI/Include/ViewTexturesDialog.h b/BMEdit/Editor/UI/Include/ViewTexturesDialog.h index c400768..3521962 100644 --- a/BMEdit/Editor/UI/Include/ViewTexturesDialog.h +++ b/BMEdit/Editor/UI/Include/ViewTexturesDialog.h @@ -31,6 +31,9 @@ class ViewTexturesDialog : public QDialog void setTexturesSource(models::SceneTexturesModel *model); +signals: + void textureChanged(uint32_t textureIndex); + protected: void showEvent(QShowEvent *event) override; diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index ea39e21..d096e88 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -523,6 +523,11 @@ void BMEditMainWindow::onSceneObjectPropertyChanged(const gamelib::scene::SceneO ui->sceneGLView->onObjectMoved(const_cast(geom)); } +void BMEditMainWindow::onTextureChanged(uint32_t textureIndex) +{ + ui->sceneGLView->reloadTexture(textureIndex); +} + void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); @@ -706,4 +711,6 @@ void BMEditMainWindow::initViewTexturesDialog() m_sceneTexturesModel.reset(new models::SceneTexturesModel(this)); m_viewTexturesDialog.setModal(true); m_viewTexturesDialog.setTexturesSource(m_sceneTexturesModel.get()); + + connect(&m_viewTexturesDialog, &ViewTexturesDialog::textureChanged, this, &BMEditMainWindow::onTextureChanged); } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp b/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp index f0670cc..c2052eb 100644 --- a/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp +++ b/BMEdit/Editor/UI/Source/ViewTexturesDialog.cpp @@ -313,6 +313,9 @@ void ViewTexturesDialog::onTextureToImportSpecified() resetPreview(); setPreview(textureIndex, std::nullopt); resetAvailableMIPs(textureIndex); + + // Notify everybody about changes + emit textureChanged(textureREF.textureIndex); } void ViewTexturesDialog::setPreview(uint32_t textureIndex, const std::optional& mipLevel) From 4c6a1f9184e397c3e4efd8d030bfab7b4ad76b56 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 15 Oct 2023 20:30:14 +0300 Subject: [PATCH 30/80] Reversed PostMissionPF controller type. Fixed crash when no RMI/RMC tree data on level (postmission as example). --- Assets/g1/PostMissionPF.json | 7 +++++ .../GameLib/Source/GameLib/OCT/OCTReader.cpp | 31 ++++++++++--------- 2 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 Assets/g1/PostMissionPF.json diff --git a/Assets/g1/PostMissionPF.json b/Assets/g1/PostMissionPF.json new file mode 100644 index 0000000..b020594 --- /dev/null +++ b/Assets/g1/PostMissionPF.json @@ -0,0 +1,7 @@ +{ + "typename": "PostMissionPF", + "parent": "ZEventBase", + "kind": "TypeKind.COMPLEX", + "properties": [ + ] +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp index b74ff8a..abf92b1 100644 --- a/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp @@ -44,24 +44,27 @@ namespace gamelib::oct } // Because previously totalObjects was index, not a count - ++totalObjects; + if (totalObjects > 0) + { + ++totalObjects; - // Read objects - // Seek to begin - octReader.seek(m_header.objectsOffset); - m_objects.resize(totalObjects); + // Read objects + // Seek to begin + octReader.seek(m_header.objectsOffset); + m_objects.resize(totalObjects); - for (int i = 0; i < totalObjects; i++) - { - OCTObject::deserialize(m_objects[i], &octReader); - } + for (int i = 0; i < totalObjects; i++) + { + OCTObject::deserialize(m_objects[i], &octReader); + } - // And last block... - m_unknownBlocks.resize(totalObjects); + // And last block... + m_unknownBlocks.resize(totalObjects); - for (int i = 0; i < totalObjects; i++) - { - OCTUnknownBlock::deserialize(m_unknownBlocks[i], &octReader); + for (int i = 0; i < totalObjects; i++) + { + OCTUnknownBlock::deserialize(m_unknownBlocks[i], &octReader); + } } return true; From e99666f2bd5947e6cdca1368caf4bf1e8ec76f39 Mon Sep 17 00:00:00 2001 From: DronCode Date: Mon, 16 Oct 2023 21:58:19 +0300 Subject: [PATCH 31/80] Removed material tab from editor. Added setup of render state from material. Small refactoring in TexturedEntity_GL33.fsh --- .../Editor/Include/Render/ShaderConstants.h | 1 + .../Resources/Shaders/TexturedEntity_GL33.fsh | 27 ++++- .../Source/Widgets/SceneRenderWidget.cpp | 103 ++++++++++++++++++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 49 --------- 4 files changed, 128 insertions(+), 52 deletions(-) diff --git a/BMEdit/Editor/Include/Render/ShaderConstants.h b/BMEdit/Editor/Include/Render/ShaderConstants.h index fbc2732..a744e79 100644 --- a/BMEdit/Editor/Include/Render/ShaderConstants.h +++ b/BMEdit/Editor/Include/Render/ShaderConstants.h @@ -10,5 +10,6 @@ namespace render static constexpr const char* kCameraView = "i_uCamera.view"; static constexpr const char* kCameraResolution = "i_uCamera.resolution"; static constexpr const char* kColor = "i_Color"; + static constexpr const char* kMaterial = "i_uMaterial"; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh index de6e6a1..5f8f482 100644 --- a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh @@ -3,14 +3,35 @@ // This file is a part of BMEdit project // Description: Basic shader to render textured entity // - -uniform sampler2D i_ActiveTexture; in vec2 g_TexCoord; +struct Material +{ + // See Common.fx for details + // Common uniforms + vec4 v4DiffuseColor; + vec4 gm_vZBiasOffset; + vec4 v4Opacity; + vec4 v4Bias; + float fAlphaREF; + + // Textures + sampler2D mapDiffuse; + sampler2D mapSpecularMask; + sampler2D mapEnvironment; + sampler2D mapReflectionMask; + sampler2D mapReflectionFallOff; + sampler2D mapIllumination; + sampler2D mapTranslucency; +}; + +uniform Material i_uMaterial; + + // Out out vec4 o_FragColor; void main() { - o_FragColor = texture(i_ActiveTexture, g_TexCoord); + o_FragColor = texture(i_uMaterial.mapDiffuse, g_TexCoord); } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index e1ac15b..6e926fd 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1047,6 +1047,102 @@ namespace widgets for (const auto& mesh : model.meshes) { // Render single mesh + if (mesh.materialId == 0) + continue; + + const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; + const auto& classes = m_pLevel->getLevelMaterials()->materialClasses; + const auto& matInstance = instances[mesh.materialId - 1]; + + if (matInstance.getBinders().empty()) + { + // Unable to render mesh. Skip + continue; + } + + const auto& defaultBinder = matInstance.getBinders()[0]; + if (defaultBinder.renderStates.empty()) + { + // Unable to render mesh. Skip + continue; + } + + const gamelib::mat::MATRenderState& renderState = defaultBinder.renderStates[0]; + + /** + * @copyright chatGPT 3.5 + */ + auto applyRenderState = [](QOpenGLFunctions_3_3_Core* gapi, const gamelib::mat::MATRenderState& renderState) + { + // Enable or disable blending + if (renderState.isBlendEnabled()) { + gapi->glEnable(GL_BLEND); + } else { + gapi->glDisable(GL_BLEND); + } + + // Set blend mode based on your enum values + switch (renderState.getBlendMode()) + { + case gamelib::mat::MATBlendMode::BM_TRANS: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + default: + // Do nothing + break; + } + + // Enable or disable alpha testing + if (renderState.isAlphaTestEnabled()) { + gapi->glEnable(GL_ALPHA_TEST); + } else { + gapi->glDisable(GL_ALPHA_TEST); + } + + // Enable or disable fog + if (renderState.isFogEnabled()) { + gapi->glEnable(GL_FOG); + } else { + gapi->glDisable(GL_FOG); + } + + // Enable or disable depth offset (Z bias) + if (renderState.hasZBias()) { + gapi->glEnable(GL_POLYGON_OFFSET_FILL); + gapi->glPolygonOffset(1.0f, renderState.getZOffset()); + } else { + gapi->glDisable(GL_POLYGON_OFFSET_FILL); + } + + // Set cull mode based on your enum values + switch (renderState.getCullMode()) + { + case gamelib::mat::MATCullMode::CM_OneSided: + gapi->glCullFace(GL_BACK); + break; + case gamelib::mat::MATCullMode::CM_DontCare: + case gamelib::mat::MATCullMode::CM_TwoSided: + // please complete + gapi->glDisable(GL_CULL_FACE); + break; + } + }; + // 0. Check that we've able to draw it if (mesh.glTextureId == kInvalidResource) { @@ -1065,6 +1161,9 @@ namespace widgets } } + // Enable render state + applyRenderState(glFunctions, renderState); + // 1. Activate default shader Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; @@ -1079,7 +1178,11 @@ namespace widgets // 3. Bind texture if (mesh.glTextureId != kInvalidResource) { + glFunctions->glActiveTexture(GL_TEXTURE0 + 0); glFunctions->glBindTexture(GL_TEXTURE_2D, mesh.glTextureId); + + // Use texture + texturedShader.setUniform(glFunctions, "i_uMaterial.mapDiffuse", 0); } // 4. Render mesh diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index 12c68ee..ba8f82f 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -299,55 +299,6 @@ - - - Material - - - - - - - - Material ID: - - - - - - - N/A - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - Material Name: - - - - - - - N/A - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - From 6fe8ad54ec4e2201b2f12aef9e602479f049721c Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 18 Oct 2023 21:08:32 +0300 Subject: [PATCH 32/80] [EXPERIMENTAL] New render, trying to fix alpha blending. Need to optimize renderer. Fixed few bugs from editor side. If you have bad performance - use previous commits. Current commit may be unstable! --- BMEdit/Editor/Include/Render/RenderEntry.h | 76 +++ .../Include/Widgets/SceneRenderWidget.h | 36 +- .../Resources/Shaders/ColoredEntity_GL33.fsh | 24 +- .../Resources/Shaders/TexturedEntity_GL33.fsh | 2 +- .../Source/Widgets/SceneRenderWidget.cpp | 471 +++++++++++------- .../Include/GameLib/MAT/MATRenderState.h | 2 + 6 files changed, 404 insertions(+), 207 deletions(-) create mode 100644 BMEdit/Editor/Include/Render/RenderEntry.h diff --git a/BMEdit/Editor/Include/Render/RenderEntry.h b/BMEdit/Editor/Include/Render/RenderEntry.h new file mode 100644 index 0000000..1b3be78 --- /dev/null +++ b/BMEdit/Editor/Include/Render/RenderEntry.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace render +{ + enum TextureSlotId : uint32_t + { + kMapDiffuse = 0, + kMapSpecularMask, + kMapEnvironment, + kMapReflectionMask, + kMapReflectionFallOff, + kMapIllumination, + kMapTranslucency, + + // should be last + kMaxTextureSlot + }; + + struct RenderEntry + { + // Render params + uint32_t iPrimitiveId { 0 }; // ID of primitive from PRM + uint32_t iMeshIndex { 0 }; // Index of mesh from PRM primitive + uint32_t iTrianglesNr { 0 }; // Count of triangles or elements + RenderTopology renderTopology { RenderTopology::RT_TRIANGLES }; // Topology of geometry buffer + + // World params + glm::vec3 vPosition { .0f }; // World position + glm::mat4 mWorldTransform { 1.f }; // Converted and translated matrix (ready to use in OpenGL) + glm::mat3 mLocalOriginalTransform { 1.f }; // Original local matrix + + // Mesh instance + render::Mesh* pMesh { nullptr }; + + struct Material + { + uint16_t id { 0 }; // Id of material from MAT file + + std::string sBaseMatClass {}; // Name of base material class + std::string sInstanceMatName {}; // Name of material instance + + // Parameters + glm::vec4 vDiffuseColor { 1.f }; + glm::vec4 gm_vZBiasOffset { .0f }; + glm::vec4 v4Opacity { 1.f }; + glm::vec4 v4Bias { .0f }; + int32_t iAlphaREF { 255 }; + + // Render State + gamelib::mat::MATRenderState renderState {}; + + // Textures + std::array textures { 0 }; + + // Shader program + render::Shader* pShader { nullptr }; + } material; + }; + + using RenderEntriesList = std::list; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 222d810..f9353c6 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -26,11 +28,15 @@ namespace widgets enum RenderMode : RenderModeFlags { - RM_TEXTURE = 1 << 0, - RM_WIREFRAME = 1 << 1, + RM_TEXTURE = 1 << 0, ///< Render objects with textures + RM_WIREFRAME = 1 << 1, ///< Render object in wireframe mode + + RM_NON_ALPHA_OBJECTS = 1 << 3, ///< Render only non transparent objects + RM_ALPHA_OBJECTS = 1 << 4, ///< Render only transparent objects - RM_ALL = RM_TEXTURE | RM_WIREFRAME, - RM_DEFAULT = RM_TEXTURE + // Common + RM_ALL = RM_TEXTURE | RM_WIREFRAME | RM_NON_ALPHA_OBJECTS | RM_ALPHA_OBJECTS, ///< Render anything + RM_DEFAULT = RM_TEXTURE | RM_NON_ALPHA_OBJECTS | RM_ALPHA_OBJECTS, ///< Render in texture mode with alpha/non-alpha objects }; class SceneRenderWidget : public QOpenGLWidget @@ -93,23 +99,9 @@ namespace widgets void doPrepareInvalidatedResources(QOpenGLFunctions_3_3_Core* glFunctions); [[nodiscard]] glm::ivec2 getViewportSize() const; - /** - * @brief Entry which will be rendered in render loop - */ - struct RenderEntry - { - const gamelib::scene::SceneObject* pGeom { nullptr }; - gamelib::BoundingBox sBoundingBox {}; - glm::mat4 mModelMatrix { 1.f }; - glm::vec3 vPosition { .0f }; - uint32_t iPrimId { 0u }; - }; - - using RenderList = std::list; - - void doCollectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility); - void doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility); - void doPerformDrawOfRenderList(QOpenGLFunctions_3_3_Core* glFunctions, const RenderList& renderList, const render::Camera& camera); + void collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility); + void collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility); + void performRender(QOpenGLFunctions_3_3_Core* glFunctions, const render::RenderEntriesList& entries, const render::Camera& camera, const std::function& filter); void invalidateRenderList(); @@ -152,7 +144,7 @@ namespace widgets gamelib::scene::SceneObject* m_pSceneObjectToView {}; gamelib::scene::SceneObject* m_pSelectedSceneObject { nullptr }; - RenderList m_renderList {}; + render::RenderEntriesList m_renderList {}; struct GLResources; std::unique_ptr m_resources; diff --git a/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh index 4fbbac2..4c01a9f 100644 --- a/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh +++ b/BMEdit/Editor/Resources/Shaders/ColoredEntity_GL33.fsh @@ -4,12 +4,32 @@ // Description: Basic shader to render color filled entity & gizmo // -uniform vec4 i_Color; +struct Material +{ + // See Common.fx for details + // Common uniforms + vec4 v4DiffuseColor; + vec4 gm_vZBiasOffset; + vec4 v4Opacity; + vec4 v4Bias; + int alphaREF; + + // Textures + sampler2D mapDiffuse; + sampler2D mapSpecularMask; + sampler2D mapEnvironment; + sampler2D mapReflectionMask; + sampler2D mapReflectionFallOff; + sampler2D mapIllumination; + sampler2D mapTranslucency; +}; + +uniform Material i_uMaterial; // Out out vec4 o_FragColor; void main() { - o_FragColor = i_Color; + o_FragColor = i_uMaterial.v4DiffuseColor; } \ No newline at end of file diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh index 5f8f482..062bb60 100644 --- a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh @@ -13,7 +13,7 @@ struct Material vec4 gm_vZBiasOffset; vec4 v4Opacity; vec4 v4Bias; - float fAlphaREF; + int alphaREF; // Textures sampler2D mapDiffuse; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 6e926fd..823ebe6 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -199,12 +199,25 @@ namespace widgets if (m_renderList.empty()) { - doCollectRenderList(m_camera, pRoot, m_renderList, bIgnoreVisibility); + collectRenderList(m_camera, pRoot, m_renderList, bIgnoreVisibility); } if (!m_renderList.empty()) { - doPerformDrawOfRenderList(funcs, m_renderList, m_camera); + auto onlyNonAlpha = [](const render::RenderEntry& entry) -> bool { return !entry.material.renderState.isAlphaTestEnabled(); }; + auto onlyAlpha = [](const render::RenderEntry& entry) -> bool { return entry.material.renderState.isAlphaTestEnabled(); }; + + // 2 pass rendering: first render only non-alpha objects + if (m_renderMode & RenderMode::RM_NON_ALPHA_OBJECTS) + { + performRender(funcs, m_renderList, m_camera, onlyNonAlpha); + } + + // then render only alpha objects + if (m_renderMode & RenderMode::RM_ALPHA_OBJECTS) + { + performRender(funcs, m_renderList, m_camera, onlyAlpha); + } } } break; @@ -213,8 +226,8 @@ namespace widgets void SceneRenderWidget::resizeGL(int w, int h) { - Q_UNUSED(w); - Q_UNUSED(h); + Q_UNUSED(w) + Q_UNUSED(h) // Update projection updateProjectionMatrix(w, h); @@ -450,6 +463,7 @@ namespace widgets return; m_resources->m_modelTransformCache[sceneObject] = sceneObject->getWorldTransform(); + invalidateRenderList(); //TODO: Need invalidate only object, not whole list! repaint(); } @@ -925,11 +939,13 @@ namespace widgets return { QWidget::width(), QWidget::height() }; } - void SceneRenderWidget::doCollectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, RenderList& renderList, bool bIgnoreVisibility) + void SceneRenderWidget::collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility) { - doVisitGeomToCollectIntoRenderList(pRootGeom, renderList, bIgnoreVisibility); + collectRenderEntriesIntoRenderList(pRootGeom, entries, bIgnoreVisibility); - renderList.sort([&camera](const RenderEntry& a, const RenderEntry& b) -> bool { + // Post sorting + entries.sort([&camera](const render::RenderEntry& a, const render::RenderEntry& b) -> bool { + // Check distance to camera const float fADistanceToCamera = glm::length(camera.Position - a.vPosition); const float fBDistanceToCamera = glm::length(camera.Position - b.vPosition); @@ -937,20 +953,27 @@ namespace widgets }); } - void SceneRenderWidget::doVisitGeomToCollectIntoRenderList(const gamelib::scene::SceneObject* geom, RenderList& renderList, bool bIgnoreVisibility) // NOLINT(*-no-recursion) + void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, bool bIgnoreVisibility) { const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getPosition(); auto primId = geom->getProperties().getObject("PrimId", 0); + if (const auto& n = geom->getType()->getName(); n == "ZSHADOWMESHOBJ" || n == "ZBOUND" || n == "ZLIGHT" || n == "ZENVIRONMENT" || n == "ZOMNILIGHT" || n == "ZSPOTLIGHT" || n == "ZSPOTLIGHTSQUARE") + { + // Do not draw us & our children + return; + } + // Don't draw invisible things if (bInvisible && !bIgnoreVisibility) return; // NOTE: Need to refactor this place and move it into separated area + // TODO: Move this hack into another place! if (geom->isInheritedOf("ZItem")) { - //ZItems has no PrimId. Instead of this they are refs to another geom by path + //ZItems has no PrimId. Instead of this they are refs to another geom by path auto rItemTemplatePath = geom->getProperties().getObject("rItemTemplate"); const auto pItemTemplate = m_pLevel->getSceneObjectByGEOMREF(rItemTemplatePath); @@ -993,218 +1016,302 @@ namespace widgets m_resources->m_modelTransformCache[const_cast(geom)] = mWorldTransform; } - // Store into render list - RenderEntry& entry = renderList.emplace_back(); - entry.vPosition = vPosition; - entry.mModelMatrix = mWorldTransform; - entry.pGeom = geom; - entry.iPrimId = primId; - - // Store bounding box - const Model& model = m_resources->m_models[m_resources->m_modelsCache[entry.iPrimId]]; - entry.sBoundingBox.min = model.boundingBox.min; - entry.sBoundingBox.max = model.boundingBox.max; - } + // Get model + const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; - // Visit others - for (const auto& child : geom->getChildren()) - { - if (auto g = child.lock()) + // Add bounding box to render list + if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { - doVisitGeomToCollectIntoRenderList(g.get(), renderList, bIgnoreVisibility); + // Need to add mesh + render::RenderEntry& boundingBoxEntry = entries.emplace_back(); + + // Render params + boundingBoxEntry.iPrimitiveId = 0; + boundingBoxEntry.iMeshIndex = 0; + boundingBoxEntry.iTrianglesNr = 0; + boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; + + // World params + boundingBoxEntry.vPosition = vPosition; + boundingBoxEntry.mWorldTransform = mWorldTransform; + boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); + boundingBoxEntry.pMesh = const_cast(&model.boundingBoxMesh.value()); + + // Material + render::RenderEntry::Material& material = boundingBoxEntry.material; + material.vDiffuseColor = glm::vec4(0.f, 0.f, 1.f, 1.f); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; } - } - } - void SceneRenderWidget::doPerformDrawOfRenderList(QOpenGLFunctions_3_3_Core* glFunctions, const RenderList& renderList, const render::Camera& camera) - { - glm::ivec2 viewResolution = getViewportSize(); + // Add each 'mesh' into render list + for (int iMeshIdx = 0; iMeshIdx < model.meshes.size(); iMeshIdx++) + { + const auto& mesh = model.meshes[iMeshIdx]; - for (const auto& entry : renderList) - { - const Model& model = m_resources->m_models[m_resources->m_modelsCache[entry.iPrimId]]; + if (mesh.materialId == 0) + continue; // Unable to render (ZWINPIC!) - // Render bounding box - if (model.boundingBoxMesh.has_value() && entry.pGeom == m_pSelectedSceneObject) - { - Shader& gizmoShader = m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; - gizmoShader.bind(glFunctions); + // Filter by 'MeshVariantId' + const auto requiredVariationId = geom->getProperties().getObject("MeshVariantId", 0); + if (requiredVariationId != mesh.variationId) + { + continue; + } - gizmoShader.setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mModelMatrix); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraView, camera.getViewMatrix()); - gizmoShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); - gizmoShader.setUniform(glFunctions, ShaderConstants::kColor, glm::vec4(0.f, 0.f, 1.f, 1.f)); + // And store entry to renderer + render::RenderEntry renderEntry = {}; - model.boundingBoxMesh.value().render(glFunctions, RenderTopology::RT_LINES); + // Render params + renderEntry.iPrimitiveId = primId; + renderEntry.iMeshIndex = iMeshIdx; + renderEntry.iTrianglesNr = mesh.trianglesCount; + renderEntry.renderTopology = render::RenderTopology::RT_TRIANGLES; - gizmoShader.unbind(glFunctions); - } + // World params + renderEntry.vPosition = vPosition; + renderEntry.mWorldTransform = mWorldTransform; + renderEntry.mLocalOriginalTransform = geom->getOriginalTransform(); + renderEntry.pMesh = const_cast(&mesh); - // Render all meshes - if (m_resources->m_modelsCache.contains(entry.iPrimId)) - { - for (const auto& mesh : model.meshes) - { - // Render single mesh - if (mesh.materialId == 0) - continue; + // Material + render::RenderEntry::Material& material = renderEntry.material; - const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; - const auto& classes = m_pLevel->getLevelMaterials()->materialClasses; - const auto& matInstance = instances[mesh.materialId - 1]; + const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; + const auto& matInstance = instances[mesh.materialId - 1]; - if (matInstance.getBinders().empty()) - { - // Unable to render mesh. Skip - continue; - } + // Store parameters + material.id = mesh.materialId; + material.sInstanceMatName = matInstance.getName(); + material.sBaseMatClass = matInstance.getParentName(); + + if (!matInstance.getBinders().empty()) + { + const auto& binder = matInstance.getBinders()[0]; // NOTE: In future I'll rewrite this place, but for now it's enough - const auto& defaultBinder = matInstance.getBinders()[0]; - if (defaultBinder.renderStates.empty()) + // Store parameters + // TODO: Need collect all parameters here + + // Store render state + if (!binder.renderStates.empty()) { - // Unable to render mesh. Skip - continue; + // TODO: In future we need to learn how to use multiple render states (if there are able to be 'multiple') + material.renderState = binder.renderStates[0]; } - const gamelib::mat::MATRenderState& renderState = defaultBinder.renderStates[0]; + // Resolve & store textures + std::fill(material.textures.begin(), material.textures.end(), kInvalidResource); - /** - * @copyright chatGPT 3.5 - */ - auto applyRenderState = [](QOpenGLFunctions_3_3_Core* gapi, const gamelib::mat::MATRenderState& renderState) + for (const auto& texture : binder.textures) { - // Enable or disable blending - if (renderState.isBlendEnabled()) { - gapi->glEnable(GL_BLEND); - } else { - gapi->glDisable(GL_BLEND); - } + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_NOTHING) + continue; // No texture at all - // Set blend mode based on your enum values - switch (renderState.getBlendMode()) + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID_AND_PATH) { - case gamelib::mat::MATBlendMode::BM_TRANS: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - default: - // Do nothing - break; - } - - // Enable or disable alpha testing - if (renderState.isAlphaTestEnabled()) { - gapi->glEnable(GL_ALPHA_TEST); - } else { - gapi->glDisable(GL_ALPHA_TEST); + assert(false && "Idk how to handle this"); + continue; } - // Enable or disable fog - if (renderState.isFogEnabled()) { - gapi->glEnable(GL_FOG); - } else { - gapi->glDisable(GL_FOG); - } + const auto& kind = texture.getName(); - // Enable or disable depth offset (Z bias) - if (renderState.hasZBias()) { - gapi->glEnable(GL_POLYGON_OFFSET_FILL); - gapi->glPolygonOffset(1.0f, renderState.getZOffset()); - } else { - gapi->glDisable(GL_POLYGON_OFFSET_FILL); - } + int textureSlotId = render::TextureSlotId::kMaxTextureSlot; - // Set cull mode based on your enum values - switch (renderState.getCullMode()) - { - case gamelib::mat::MATCullMode::CM_OneSided: - gapi->glCullFace(GL_BACK); - break; - case gamelib::mat::MATCullMode::CM_DontCare: - case gamelib::mat::MATCullMode::CM_TwoSided: - // please complete - gapi->glDisable(GL_CULL_FACE); - break; - } - }; +#define MATCH_TEXTURE_KIND(mode, modeName) if (kind == modeName) { textureSlotId = mode; } + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapDiffuse, "mapDiffuse") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapSpecularMask, "mapSpecularMask") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapEnvironment, "mapEnvironment") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionMask, "mapReflectionMask") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionFallOff, "mapReflectionFallOff") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapIllumination, "mapIllumination") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapTranslucency, "mapTranslucency") +#undef MATCH_TEXTURE_KIND - // 0. Check that we've able to draw it - if (mesh.glTextureId == kInvalidResource) - { - // Draw "error" bounding box - // And continue - continue; - } + if (textureSlotId == render::TextureSlotId::kMaxTextureSlot) + continue; - // Filter mesh by 'variation id' - if (const auto& properties = entry.pGeom->getProperties(); properties.hasProperty("MeshVariantId")) - { - const auto requiredVariationId = properties.getObject("MeshVariantId", 0); - if (requiredVariationId != mesh.variationId) + // Now we need to find texture instance and associate it + for (const auto& g1tex : m_resources->m_textures) { - continue; + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID && g1tex.index.has_value() && g1tex.index.value() == texture.getTextureId()) + { + material.textures[textureSlotId] = g1tex.texture; + break; + } + + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH && g1tex.texPath.has_value() && g1tex.texPath.value() == texture.getTexturePath()) + { + material.textures[textureSlotId] = g1tex.texture; + break; + } } } + } - // Enable render state - applyRenderState(glFunctions, renderState); + // Store shader + material.pShader = &m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; - // 1. Activate default shader - Shader& texturedShader = m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; + // Push or not? + if (!std::all_of(material.textures.begin(), material.textures.end(), [](const auto& v) -> bool { return v == kInvalidResource; })) + { + entries.emplace_back(renderEntry); + } + } + } - texturedShader.bind(glFunctions); + // Visit others + for (const auto& child : geom->getChildren()) + { + if (auto g = child.lock()) + { + collectRenderEntriesIntoRenderList(g.get(), entries, bIgnoreVisibility); + } + } + } - // 2. Submit uniforms - texturedShader.setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mModelMatrix); - texturedShader.setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); - texturedShader.setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); - texturedShader.setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); + void SceneRenderWidget::performRender(QOpenGLFunctions_3_3_Core* glFunctions, const render::RenderEntriesList& entries, const render::Camera& camera, const std::function& filter) + { + glm::ivec2 viewResolution = getViewportSize(); - // 3. Bind texture - if (mesh.glTextureId != kInvalidResource) - { - glFunctions->glActiveTexture(GL_TEXTURE0 + 0); - glFunctions->glBindTexture(GL_TEXTURE_2D, mesh.glTextureId); + auto applyRenderState = [](QOpenGLFunctions_3_3_Core* gapi, const gamelib::mat::MATRenderState& renderState) + { + // Enable or disable blending + if (renderState.isBlendEnabled()) { + gapi->glEnable(GL_BLEND); + } else { + gapi->glDisable(GL_BLEND); + } - // Use texture - texturedShader.setUniform(glFunctions, "i_uMaterial.mapDiffuse", 0); - } + // Set blend mode based on your enum values + switch (renderState.getBlendMode()) + { + case gamelib::mat::MATBlendMode::BM_TRANS: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + default: + // Do nothing + break; + } - // 4. Render mesh - if (m_renderMode & RenderMode::RM_TEXTURE) - { - // normal draw - mesh.render(glFunctions); - } + // Enable or disable alpha testing + if (renderState.isAlphaTestEnabled()) { + gapi->glEnable(GL_ALPHA_TEST); + } else { + gapi->glDisable(GL_ALPHA_TEST); + } - if (m_renderMode & RenderMode::RM_WIREFRAME) - { - glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - mesh.render(glFunctions); - // reset back - glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } + // Enable or disable fog + if (renderState.isFogEnabled()) { + gapi->glEnable(GL_FOG); + } else { + gapi->glDisable(GL_FOG); + } - // 5. Unbind texture and shader (expected to switch between materials, but not now) - glFunctions->glBindTexture(GL_TEXTURE_2D, 0); - texturedShader.unbind(glFunctions); - } + // Enable or disable depth offset (Z bias) + if (renderState.hasZBias()) { + gapi->glEnable(GL_POLYGON_OFFSET_FILL); + gapi->glPolygonOffset(1.0f, renderState.getZOffset()); + } else { + gapi->glDisable(GL_POLYGON_OFFSET_FILL); + } + + // Set cull mode based on your enum values + switch (renderState.getCullMode()) + { + case gamelib::mat::MATCullMode::CM_OneSided: + gapi->glCullFace(GL_BACK); + break; + case gamelib::mat::MATCullMode::CM_DontCare: + case gamelib::mat::MATCullMode::CM_TwoSided: + // please complete + gapi->glDisable(GL_CULL_FACE); + break; } + }; + + static constexpr std::array g_aTextureKindToLocation { + "i_uMaterial.mapDiffuse", + "i_uMaterial.mapSpecularMask", + "i_uMaterial.mapEnvironment", + "i_uMaterial.mapReflectionMask", + "i_uMaterial.mapReflectionFallOff", + "i_uMaterial.mapIllumination", + "i_uMaterial.mapTranslucency" + }; + + for (const auto& entry : entries) + { + if (!filter(entry)) + continue; // skipped by filter + + // Switch render state + applyRenderState(glFunctions, entry.material.renderState); + + // Activate material & setup parameters + render::Shader* shader = entry.material.pShader; + shader->bind(glFunctions); + + // Setup parameters (common) + shader->setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mWorldTransform); + shader->setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); + shader->setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); + shader->setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); + + // TODO: Need to move into constants + shader->setUniform(glFunctions, "i_uMaterial.v4DiffuseColor", entry.material.vDiffuseColor); + shader->setUniform(glFunctions, "i_uMaterial.gm_vZBiasOffset", entry.material.gm_vZBiasOffset); + shader->setUniform(glFunctions, "i_uMaterial.v4Opacity", entry.material.v4Opacity); + shader->setUniform(glFunctions, "i_uMaterial.v4Bias", entry.material.v4Bias); + shader->setUniform(glFunctions, "i_uMaterial.alphaREF", std::clamp(entry.material.iAlphaREF, 0, 255)); + + // Bind textures + for (int slotIdx = render::TextureSlotId::kMapDiffuse; slotIdx < render::TextureSlotId::kMaxTextureSlot; slotIdx++) + { + const auto& glTexture = entry.material.textures[slotIdx]; + + if (glTexture == kInvalidResource) + continue; + + glFunctions->glActiveTexture(GL_TEXTURE0 + slotIdx); + glFunctions->glBindTexture(GL_TEXTURE_2D, glTexture); + shader->setUniform(glFunctions, std::string(g_aTextureKindToLocation[slotIdx]), slotIdx); + } + + if (m_renderMode & RenderMode::RM_TEXTURE) + { + entry.pMesh->render(glFunctions, entry.renderTopology); + } + + if (m_renderMode & RenderMode::RM_WIREFRAME) + { + glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + entry.pMesh->render(glFunctions, entry.renderTopology); + glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + + // Release stuff + for (int slotIdx = render::TextureSlotId::kMapDiffuse; slotIdx < render::TextureSlotId::kMaxTextureSlot; slotIdx++) + { + glFunctions->glActiveTexture(GL_TEXTURE0 + slotIdx); + glFunctions->glBindTexture(GL_TEXTURE_2D, 0); + } + + // And it's done + shader->unbind(glFunctions); } } diff --git a/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h index 0f17b03..ba24c41 100644 --- a/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h +++ b/BMEdit/GameLib/Include/GameLib/MAT/MATRenderState.h @@ -16,6 +16,8 @@ namespace gamelib::mat class MATRenderState { public: + MATRenderState() = default; + MATRenderState(std::string name, bool bEnabled, bool bBlendEnabled, bool bAlphaTest, bool bFogEnabled, bool bZBias, float fOpacity, float fZOffset, From 6a482d34e9927c767cef79a7af1c6618323f7a0c Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 19 Oct 2023 19:07:03 +0300 Subject: [PATCH 33/80] Added texture cache for fast texture lookup. --- .../Source/Widgets/SceneRenderWidget.cpp | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 823ebe6..b67ff69 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -37,6 +37,8 @@ namespace widgets std::vector m_models {}; std::unordered_map m_modelsCache {}; /// primitive index to model index in m_models std::unordered_map m_modelTransformCache {}; /// transformations cache + std::unordered_map m_textureNameToGL {}; /// name of texture to it's OpenGL resource id + std::unordered_map m_textureIndexToGL {}; /// index of texture to it's OpenGL resource id std::unordered_set m_invalidatedTextures; /// Set of textures who need to be reloaded on next frame GLuint m_iGLDebugTexture { 0 }; GLuint m_iGLMissingTexture { 0 }; @@ -81,6 +83,10 @@ namespace widgets // Empty cache m_modelsCache.clear(); + m_modelTransformCache.clear(); + m_textureNameToGL.clear(); + m_textureIndexToGL.clear(); + m_invalidatedTextures.clear(); // Release refs m_iGLDebugTexture = 0u; @@ -514,6 +520,17 @@ namespace widgets m_resources->m_iGLDebugTexture = newTexture.texture; } + // Update cache + if (newTexture.texPath.has_value()) + { + m_resources->m_textureNameToGL[newTexture.texPath.value()] = newTexture.texture; + } + + if (newTexture.index.has_value()) + { + m_resources->m_textureIndexToGL[newTexture.index.value()] = newTexture.texture; + } + // Save texture m_resources->m_textures.emplace_back(newTexture); } @@ -917,8 +934,28 @@ namespace widgets if (it != allTextures.end()) { + // Erase cache + if (it->m_fileName.has_value()) + { + m_resources->m_textureNameToGL.erase(it->m_fileName.value()); + } + m_resources->m_textureIndexToGL.erase(it->m_index); + + // Reload if (texture.setup(glFunctions, *it)) { + // Update cache + if (texture.texPath.has_value()) + { + m_resources->m_textureNameToGL[texture.texPath.value()] = texture.texture; + } + + if (texture.index.has_value()) + { + m_resources->m_textureIndexToGL[texture.index.value()] = texture.texture; + } + + // Done qDebug() << "Texture #" << textureIndex << " reloaded!"; } else @@ -1130,18 +1167,23 @@ namespace widgets continue; // Now we need to find texture instance and associate it - for (const auto& g1tex : m_resources->m_textures) + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID) { - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID && g1tex.index.has_value() && g1tex.index.value() == texture.getTextureId()) + // Lookup in cache by texture id + if (auto it = m_resources->m_textureIndexToGL.find(texture.getTextureId()); it != m_resources->m_textureIndexToGL.end()) { - material.textures[textureSlotId] = g1tex.texture; - break; + material.textures[textureSlotId] = it->second; + break; } + } - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH && g1tex.texPath.has_value() && g1tex.texPath.value() == texture.getTexturePath()) + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH) + { + // Lookup in cache by texture path + if (auto it = m_resources->m_textureNameToGL.find(texture.getTexturePath()); it != m_resources->m_textureNameToGL.end()) { - material.textures[textureSlotId] = g1tex.texture; - break; + material.textures[textureSlotId] = it->second; + break; } } } From 9c6504c85af560a147cd4a88a530c7fe4bfd50e8 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 21 Oct 2023 11:48:39 +0300 Subject: [PATCH 34/80] Fixed transform calculation code. --- .../Source/GameLib/Scene/SceneObject.cpp | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index 1465fa8..70e27e8 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -142,27 +143,31 @@ namespace gamelib::scene const auto vPosition = getPosition(); const auto mMatrix = getOriginalTransform(); - // Extract matrix from properties - glm::mat4 mTransform = glm::mat4(1.f); - - mTransform[0][0] = mMatrix[0][2]; - mTransform[1][0] = mMatrix[0][1]; - mTransform[2][0] = mMatrix[0][0]; - - mTransform[0][1] = mMatrix[1][2]; - mTransform[1][1] = mMatrix[1][1]; - mTransform[2][1] = mMatrix[1][0]; - - mTransform[0][2] = mMatrix[2][2]; - mTransform[1][2] = mMatrix[2][1]; - mTransform[2][2] = mMatrix[2][0]; - - mTransform[3][3] = 1.f; - - glm::mat4 mTranslate = glm::translate(glm::mat4(1.f), vPosition); - glm::mat4 mModelMatrix = mTranslate * mTransform; - - return mModelMatrix; + glm::mat4 mResult = glm::mat4(1.f); + glm::mat3 mMatrixT = glm::transpose(mMatrix); + + const float* pSrcMatrix = glm::value_ptr(mMatrix); + float* pDstMatrix = glm::value_ptr(mResult); + + // Result of reverse engineering of sub_489740 (void __cdecl Transform3x3To4x4Matrix(Mat4x4 *pDstMtx, Mat3x3 *pSrcMtx, Vec3F *pVPosition)) + pDstMatrix[0] = pSrcMatrix[6]; + pDstMatrix[1] = pSrcMatrix[7]; + pDstMatrix[2] = pSrcMatrix[8]; + pDstMatrix[3] = 0.0f; + pDstMatrix[4] = pSrcMatrix[3]; + pDstMatrix[5] = pSrcMatrix[4]; + pDstMatrix[6] = pSrcMatrix[5]; + pDstMatrix[7] = 0.0f; + pDstMatrix[8] = pSrcMatrix[0]; + pDstMatrix[9] = pSrcMatrix[1]; + pDstMatrix[10] = pSrcMatrix[2]; + pDstMatrix[11] = 0.0f; + pDstMatrix[12] = vPosition.x; + pDstMatrix[13] = vPosition.y; + pDstMatrix[14] = vPosition.z; + pDstMatrix[15] = 1.0f; + + return mResult; } glm::vec3 SceneObject::getPosition() const From 0936f05a94745ce603c18cc3fa7fb368bb0e4688 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 21 Dec 2023 19:28:48 +0300 Subject: [PATCH 35/80] Trying to optimize rendering. Added ZROOM cache, ZROOM bounding box lookup. Added controllers editor (add, remove, change). Added Glacier properties system default initializer (now we've able to produce and runtime type, tested on controllers). Added visitor for SceneObject. Reversed few properties. --- Assets/g1/CCheckInside.json | 8 +- Assets/g1/ZHM3ItemTemplateWeapon.json | 6 +- Assets/g1/ZHM3LevelControlM13.json | 4 +- Assets/g1/ZItemTemplateWeapon.json | 6 +- .../Include/Models/GeomControllerListModel.h | 2 + BMEdit/Editor/Include/Render/Camera.h | 14 + .../Include/Widgets/GeomControllersWidget.h | 12 + .../Include/Widgets/SceneRenderWidget.h | 13 + .../Source/Models/GeomControllerListModel.cpp | 16 +- .../Models/SceneObjectControllerModel.cpp | 3 +- .../Source/Widgets/GeomControllersWidget.cpp | 163 ++++++- .../Source/Widgets/SceneRenderWidget.cpp | 436 +++++++++++++----- BMEdit/Editor/UI/UI/GeomControllersWidget.ui | 123 ++++- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 3 + .../Include/GameLib/PRP/PRPInstruction.h | 5 + .../Include/GameLib/Scene/SceneObject.h | 17 + BMEdit/GameLib/Include/GameLib/Type.h | 6 + BMEdit/GameLib/Include/GameLib/TypeAlias.h | 1 + BMEdit/GameLib/Include/GameLib/TypeArray.h | 1 + BMEdit/GameLib/Include/GameLib/TypeBitfield.h | 1 + BMEdit/GameLib/Include/GameLib/TypeComplex.h | 1 + .../GameLib/Include/GameLib/TypeContainer.h | 1 + BMEdit/GameLib/Include/GameLib/TypeEnum.h | 1 + BMEdit/GameLib/Include/GameLib/TypeRawData.h | 1 + BMEdit/GameLib/Source/GameLib/BoundingBox.cpp | 18 + .../Source/GameLib/PRP/PRPInstruction.cpp | 2 + .../Source/GameLib/Scene/SceneObject.cpp | 35 ++ BMEdit/GameLib/Source/GameLib/Type.cpp | 5 + BMEdit/GameLib/Source/GameLib/TypeAlias.cpp | 17 + BMEdit/GameLib/Source/GameLib/TypeArray.cpp | 18 + .../GameLib/Source/GameLib/TypeBitfield.cpp | 7 + BMEdit/GameLib/Source/GameLib/TypeComplex.cpp | 41 ++ .../GameLib/Source/GameLib/TypeContainer.cpp | 7 + BMEdit/GameLib/Source/GameLib/TypeEnum.cpp | 16 + BMEdit/GameLib/Source/GameLib/TypeRawData.cpp | 7 + 35 files changed, 851 insertions(+), 166 deletions(-) diff --git a/Assets/g1/CCheckInside.json b/Assets/g1/CCheckInside.json index 207692a..7206569 100644 --- a/Assets/g1/CCheckInside.json +++ b/Assets/g1/CCheckInside.json @@ -3,10 +3,10 @@ "parent": "ZEventBase", "kind": "TypeKind.COMPLEX", "properties": [ - { "name": "property_2C", "typename": "ZGEOMREF", "offset": 44 }, - { "name": "property_30", "typename": "ZGEOMREF", ",offset": 48 }, - { "name": "property_34", "typename": "ZMsg", "offset": 52 }, - { "name": "property_36", "typename": "ZMsg", "offset": 54 }, + { "name": "rTarget", "typename": "ZGEOMREF", "offset": 44 }, + { "name": "rObserverActor", "typename": "ZGEOMREF", ",offset": 48 }, + { "name": "OnEnterMSG", "typename": "ZMsg", "offset": 52 }, + { "name": "OnLeaveMSG", "typename": "ZMsg", "offset": 54 }, { "name": "property_38", "typename": "PRPOpCode.Bool", "offset": 56 }, { "name": "property_39", "typename": "PRPOpCode.Bool", "offset": 57 }, { "name": "property_3A", "typename": "PRPOpCode.Bool", "offset": 58 }, diff --git a/Assets/g1/ZHM3ItemTemplateWeapon.json b/Assets/g1/ZHM3ItemTemplateWeapon.json index 437e1e1..3b7c4c0 100644 --- a/Assets/g1/ZHM3ItemTemplateWeapon.json +++ b/Assets/g1/ZHM3ItemTemplateWeapon.json @@ -28,7 +28,7 @@ "typename": "ZHM3ItemTemplateWeapon.EHM3WeaponScope" }, { - "name": "property_178", + "name": "rReloadAnim", "offset": 372, "typename": "PRPOpCode.String" }, @@ -38,12 +38,12 @@ "typename": "PRPOpCode.String" }, { - "name": "property_180", + "name": "rHoldAnim", "offset": 380, "typename": "PRPOpCode.String" }, { - "name": "property_184", + "name": "rRecoilAnim", "offset": 384, "typename": "PRPOpCode.String" }, diff --git a/Assets/g1/ZHM3LevelControlM13.json b/Assets/g1/ZHM3LevelControlM13.json index f63c671..9764928 100644 --- a/Assets/g1/ZHM3LevelControlM13.json +++ b/Assets/g1/ZHM3LevelControlM13.json @@ -16,7 +16,7 @@ { "name": "rJournalistActor", "typename": "ZGEOMREF", "offset": 1540 }, { "name": "property_608", "typename": "ZGEOMREF", "offset": 1544 }, { "name": "rPriestActor", "typename": "ZGEOMREF", "offset": 1548 }, - { "name": "property_610", "typename": "ZGEOMREF", "offset": 1552 }, - { "name": "property_614", "typename": "ZGEOMREF", "offset": 1556 } + { "name": "rHitmanDieCutSequence", "typename": "ZGEOMREF", "offset": 1552 }, + { "name": "rMoviePlayer", "typename": "ZGEOMREF", "offset": 1556 } ] } \ No newline at end of file diff --git a/Assets/g1/ZItemTemplateWeapon.json b/Assets/g1/ZItemTemplateWeapon.json index 1003159..aab91db 100644 --- a/Assets/g1/ZItemTemplateWeapon.json +++ b/Assets/g1/ZItemTemplateWeapon.json @@ -23,7 +23,7 @@ "typename": "ZItemTemplateWeapon.WEAPONTYPE" }, { - "name": "property_A4", + "name": "rAmmoTemplate", "offset": 164, "typename": "ZREFTAB32" }, @@ -78,12 +78,12 @@ "typename": "PRPOpCode.Bool" }, { - "name": "property_E0", + "name": "rModelStates", "offset": 224, "typename": "ZREFTAB32" }, { - "name": "property_FC", + "name": "rFlash", "offset": 252, "typename": "ZGEOMREF" }, diff --git a/BMEdit/Editor/Include/Models/GeomControllerListModel.h b/BMEdit/Editor/Include/Models/GeomControllerListModel.h index 6fb7ee1..1092e17 100644 --- a/BMEdit/Editor/Include/Models/GeomControllerListModel.h +++ b/BMEdit/Editor/Include/Models/GeomControllerListModel.h @@ -27,6 +27,8 @@ namespace models void setGeom(gamelib::scene::SceneObject *sceneObject); void resetGeom(); + void updateControllersList(); + private: [[nodiscard]] bool isReady() const; diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index e17de27..9eabdb3 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -122,6 +123,19 @@ namespace render Zoom = 45.0f; } + // return true if object represented by vMin & vMax is visible + [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax, const glm::mat4& mProj) const + { + return true; + /* + * // still buggy shit + const bool bCenter = glm::dot(Front, Position - ((vMax - vMin) * .5f)) > .0f; + const bool bMin = glm::dot(Front, Position - vMin) > .0f; + const bool bMax = glm::dot(Front, Position - vMax) > .0f; + return bCenter || bMin || bMax; + */ + } + private: // calculates the front vector from the Camera's (updated) Euler Angles void updateCameraVectors() diff --git a/BMEdit/Editor/Include/Widgets/GeomControllersWidget.h b/BMEdit/Editor/Include/Widgets/GeomControllersWidget.h index 75f5531..2e88a5a 100644 --- a/BMEdit/Editor/Include/Widgets/GeomControllersWidget.h +++ b/BMEdit/Editor/Include/Widgets/GeomControllersWidget.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include @@ -44,8 +47,14 @@ namespace widgets void geomReset(); void editControllers(); + private: + static QStringList getAllPossibleControllerNamesFromTypesDb(); + private: void setup(); + void updateAvailableControllersList(); + void addControllerToGeom(const QString& controllerName); + void removeCurrentController(); private: Ui::GeomControllersWidget *m_ui { nullptr }; @@ -54,5 +63,8 @@ namespace widgets models::GeomControllerListModel *m_geomControllersListModel { nullptr }; models::SceneObjectControllerModel *m_controllerPropertiesModel{ nullptr }; delegates::TypePropertyItemDelegate *m_controllerEditorDelegate{ nullptr }; + + QScopedPointer m_availableToAddControllersModel { nullptr }; // All possible controller classes + QScopedPointer m_availableToAddControllersProxyModel { nullptr }; // Filtered by user input controller classes }; } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index f9353c6..7222011 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -105,6 +105,8 @@ namespace widgets void invalidateRenderList(); + void buildRoomCache(); + private: // Data gamelib::Level* m_pLevel { nullptr }; @@ -148,5 +150,16 @@ namespace widgets struct GLResources; std::unique_ptr m_resources; + + struct RoomDef + { + gamelib::scene::SceneObject::Ref rRoom {}; + gamelib::BoundingBox vBoundingBox {}; + }; + + std::list m_rooms {}; + + private: + void computeRoomBoundingBox(RoomDef& d); }; } diff --git a/BMEdit/Editor/Source/Models/GeomControllerListModel.cpp b/BMEdit/Editor/Source/Models/GeomControllerListModel.cpp index c9f673d..55b69a1 100644 --- a/BMEdit/Editor/Source/Models/GeomControllerListModel.cpp +++ b/BMEdit/Editor/Source/Models/GeomControllerListModel.cpp @@ -34,7 +34,10 @@ namespace models } if (role == Qt::DisplayRole) { - return QString::fromStdString(m_sceneObject->getControllers().at(index.row()).name); + if (const auto& controllers = m_sceneObject->getControllers(); controllers.size() > index.row()) + { + return QString::fromStdString(controllers.at(index.row()).name); + } } return {}; @@ -98,6 +101,17 @@ namespace models endResetModel(); } + void GeomControllerListModel::updateControllersList() + { + if (!m_sceneObject) { + return; + } + + beginResetModel(); + // Geom unchanged) + endResetModel(); + } + bool GeomControllerListModel::isReady() const { return m_sceneObject != nullptr; diff --git a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp index f8fa53f..e357cc6 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp @@ -44,7 +44,8 @@ void SceneObjectControllerModel::setControllerIndex(int controllerIndex) m_currentControllerIndex = controllerIndex; endResetModel(); - setValue(m_geom->getControllers().at(controllerIndex).properties); + const auto& controller = m_geom->getControllers().at(controllerIndex); + setValue(controller.properties); } void SceneObjectControllerModel::resetController() diff --git a/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp b/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp index 608329c..255b91c 100644 --- a/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp @@ -3,6 +3,11 @@ #include #include #include +#include +#include +#include +#include + using namespace widgets; @@ -35,25 +40,32 @@ void GeomControllersWidget::setGeom(gamelib::scene::SceneObject *sceneObject) if (sceneObject && sceneObject != m_sceneObject) { - if (!sceneObject->getControllers().empty()) { + // Filter allTypes QSignalBlocker blocker(m_ui->controllerSelector); // Load possible controllers m_geomControllersListModel->setGeom(sceneObject); - // Select controller - m_ui->controllerSelector->setEnabled(true); - } - else - { - m_geomControllersListModel->resetGeom(); - m_ui->controllerSelector->setEnabled(false); + // + if (!sceneObject->getControllers().empty()) + { + // Select controller + m_ui->controllerSelector->setEnabled(true); + m_ui->removeControllerButton->setEnabled(true); + } + else + { + m_ui->controllerSelector->setEnabled(false); + m_ui->removeControllerButton->setEnabled(false); + } } m_controllerPropertiesModel->setGeom(sceneObject); m_sceneObject = sceneObject; + updateAvailableControllersList(); + emit geomChanged(); } } @@ -62,6 +74,16 @@ void GeomControllersWidget::resetGeom() { switchToDefaults(); + // Discard geom + m_geomControllersListModel->resetGeom(); + m_controllerPropertiesModel->resetGeom(); + + // Discard current geom + m_sceneObject = nullptr; + + // Clear available controller type names + m_availableToAddControllersModel->setStringList({}); + emit geomReset(); } @@ -88,10 +110,7 @@ void GeomControllersWidget::setController(int controllerIndex) void GeomControllersWidget::switchToDefaults() { m_controllerPropertiesModel->resetValue(); - m_geomControllersListModel->resetGeom(); - m_sceneObject = nullptr; - m_ui->editControllerListButton->setEnabled(false); m_ui->controllerSelector->setEnabled(false); m_ui->controllerSelector->clear(); } @@ -115,7 +134,28 @@ void GeomControllersWidget::setup() m_ui->controllerProperties->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_ui->controllerProperties->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Interactive); - connect(m_ui->controllerSelector, &QComboBox::currentIndexChanged, [=](int newIndex) { + // new controllers menu + m_availableToAddControllersModel.reset(new QStringListModel(this)); + + m_availableToAddControllersProxyModel.reset(new QSortFilterProxyModel(this)); + m_availableToAddControllersProxyModel->setSourceModel(m_availableToAddControllersModel.get()); + + m_ui->allPossibleControllersList->setModel(m_availableToAddControllersProxyModel.get()); + + connect(m_ui->addSelectedControllerButton, &QPushButton::clicked, [=]() { + const auto selectedRows = m_ui->allPossibleControllersList->selectionModel()->selectedRows(); + if (selectedRows.isEmpty()) + return; + + addControllerToGeom(m_ui->allPossibleControllersList->model()->data(selectedRows[0], Qt::DisplayRole).value()); + }); + + connect(m_ui->searchEdit, &QLineEdit::textChanged, [this](const QString& newQuery) { + m_availableToAddControllersProxyModel->setFilterCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + m_availableToAddControllersProxyModel->setFilterFixedString(newQuery); + }); + + connect(m_ui->controllerSelector, &QComboBox::currentIndexChanged, [this](int newIndex) { if (newIndex < 0) { switchToDefaults(); @@ -125,4 +165,103 @@ void GeomControllersWidget::setup() setController(newIndex); } }); + + connect(m_ui->removeControllerButton, &QPushButton::clicked, [this]() { + removeCurrentController(); + }); +} + +void GeomControllersWidget::updateAvailableControllersList() +{ + auto allTypes = getAllPossibleControllerNamesFromTypesDb(); + m_availableToAddControllersModel->setStringList(allTypes); +} + +void GeomControllersWidget::addControllerToGeom(const QString& controllerName) +{ + if (auto pType = gamelib::TypeRegistry::getInstance().findTypeByShortName(controllerName.toStdString())) + { + auto serializeControllerName = [](const std::string& controllerName) -> std::string + { + if (controllerName.empty()) return controllerName; + + std::string newName { controllerName }; + // See TypeRegistry::findTypeByShortName for details + if (newName[0] == 'Z' || newName[0] == 'C') + { + newName.erase(newName.begin(), newName.begin() + 1); + } + + if (newName.starts_with("HM3")) + { + newName.erase(newName.begin(), newName.begin() + 3); + } + + if (auto it = newName.find("Event"); it != std::string::npos) + { + newName.erase(it, 5); + } + + return newName; + }; + + const bool bAddedFirstController = m_sceneObject->getControllers().empty(); + + // Register a new controller + gamelib::scene::SceneObject::Controller& newController = m_sceneObject->getControllers().emplace_back(); + newController.name = serializeControllerName(pType->getName()); + newController.properties = pType->makeDefaultPropertiesPack(); + + // Update UI + updateAvailableControllersList(); + + // Make it selectable + m_ui->controllerSelector->setEnabled(true); + m_ui->removeControllerButton->setEnabled(true); + + // Load possible controllers + m_geomControllersListModel->updateControllersList(); + + if (bAddedFirstController) + { + switchToFirstController(); + } + } +} + +void GeomControllersWidget::removeCurrentController() +{ + const int indexToRemove = m_ui->controllerSelector->currentIndex(); + if (indexToRemove < 0) + return; + + // Remove current controller + m_sceneObject->getControllers().erase(m_sceneObject->getControllers().begin() + indexToRemove); + + // update UI + m_ui->controllerSelector->setEnabled(!m_sceneObject->getControllers().empty()); + m_ui->removeControllerButton->setEnabled(!m_sceneObject->getControllers().empty()); + + // Discard current controller + m_controllerPropertiesModel->resetController(); + + // Remove controller from controllers list of current geom + m_geomControllersListModel->updateControllersList(); +} + +QStringList GeomControllersWidget::getAllPossibleControllerNamesFromTypesDb() +{ + QStringList result; + gamelib::TypeRegistry::getInstance().forEachType([&result](const gamelib::Type* pType) { + if (pType->getKind() == gamelib::TypeKind::COMPLEX) + { + const auto pAsComplex = static_cast(pType); + if (pAsComplex->isInheritedOf("ZEventBase")) + { + result.emplace_back(QString::fromStdString(pAsComplex->getName())); + } + } + }); + + return result; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index b67ff69..a5ce232 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -22,12 +22,19 @@ #include #include +#include #include #include +#include namespace widgets { + // Here stored geom names (common) where editor should avoid any rendering (it's too expensive and unnecessary for us) + static const std::set g_bannedObjectIds { + "AdditionalResources", "AllLevels/mainsceneincludes.zip", "AllLevels/equipment.zip" + }; + using namespace render; struct SceneRenderWidget::GLResources @@ -777,6 +784,9 @@ namespace widgets } } + // Then load rooms cache + buildRoomCache(); + qDebug() << "All models (" << m_pLevel->getLevelGeometry()->primitives.models.size() << ") are loaded & ready to use!"; m_eState = ELevelState::LS_COMPILE_SHADERS; repaint(); // call to force jump into next state @@ -880,7 +890,7 @@ namespace widgets if (player) { // Ok, level contains player. Let's take his room and move camera to player - const auto iPrimId = player->getProperties().getObject("PrimId", 0u); + const auto iPrimId = player->getProperties().getObject("PrimId", 0); const auto vPlayerPosition = player->getParent().lock()->getPosition(); glm::vec3 vCameraPosition = vPlayerPosition; @@ -978,7 +988,54 @@ namespace widgets void SceneRenderWidget::collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility) { - collectRenderEntriesIntoRenderList(pRootGeom, entries, bIgnoreVisibility); + if (m_pLevel->getSceneObjects().empty()) + return; + + if (pRootGeom != m_pLevel->getSceneObjects()[0].get() || m_rooms.empty() /* on some levels m_rooms cache could be not presented! */) + { + // Render from specific node + collectRenderEntriesIntoRenderList(pRootGeom, entries, bIgnoreVisibility); + } + else + { + std::list acceptedRooms {}; + + // Render static + for (const auto& sRoomDef : m_rooms) + { + if (sRoomDef.vBoundingBox.contains(m_camera.Position) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max, m_matProjection)) + { + if (auto pRoom = sRoomDef.rRoom.lock()) + { + collectRenderEntriesIntoRenderList(pRoom.get(), entries, bIgnoreVisibility); + } + + acceptedRooms.emplace_back(sRoomDef); + } + } + + // Render dynamic + const auto& vObjects = m_pLevel->getSceneObjects(); + auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { + return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); + }); + + if (it != vObjects.end()) + { + // Add this 'ROOM' as another thing to visit + const gamelib::scene::SceneObject* pDynRoot = it->get(); + + using R = gamelib::scene::SceneObject::EVisitResult; + pDynRoot->visitChildren([&entries, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (pObject->getName().ends_with("_LOCATIONS.zip")) + return R::VR_NEXT; + + // Collect everything inside + collectRenderEntriesIntoRenderList(pObject.get(), entries, false); + return R::VR_NEXT; + }); + } + } // Post sorting entries.sort([&camera](const render::RenderEntry& a, const render::RenderEntry& b) -> bool { @@ -990,12 +1047,14 @@ namespace widgets }); } - void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, bool bIgnoreVisibility) + void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getPosition(); auto primId = geom->getProperties().getObject("PrimId", 0); + // Calculate object world space bounding box and check that this bbox is visible by out camera + if (const auto& n = geom->getType()->getName(); n == "ZSHADOWMESHOBJ" || n == "ZBOUND" || n == "ZLIGHT" || n == "ZENVIRONMENT" || n == "ZOMNILIGHT" || n == "ZSPOTLIGHT" || n == "ZSPOTLIGHTSQUARE") { // Do not draw us & our children @@ -1006,6 +1065,13 @@ namespace widgets if (bInvisible && !bIgnoreVisibility) return; + if (g_bannedObjectIds.contains(std::string_view{geom->getName()})) + return; + + // Check that our 'object' is not a collision box + if (auto parent = geom->getParent().lock(); parent && parent->getType()->getName() == "ZROOM" && parent->getName() == geom->getName()) + return; // Do not render collision meshes + // NOTE: Need to refactor this place and move it into separated area // TODO: Move this hack into another place! if (geom->isInheritedOf("ZItem")) @@ -1036,8 +1102,6 @@ namespace widgets } } - // TODO: Check for culling here (object visible or not) - // Check that object could be rendered by any way if (primId != 0 && m_resources->m_modelsCache.contains(primId)) { @@ -1056,146 +1120,142 @@ namespace widgets // Get model const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; - // Add bounding box to render list - if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) - { - // Need to add mesh - render::RenderEntry& boundingBoxEntry = entries.emplace_back(); - - // Render params - boundingBoxEntry.iPrimitiveId = 0; - boundingBoxEntry.iMeshIndex = 0; - boundingBoxEntry.iTrianglesNr = 0; - boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; - - // World params - boundingBoxEntry.vPosition = vPosition; - boundingBoxEntry.mWorldTransform = mWorldTransform; - boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); - boundingBoxEntry.pMesh = const_cast(&model.boundingBoxMesh.value()); - - // Material - render::RenderEntry::Material& material = boundingBoxEntry.material; - material.vDiffuseColor = glm::vec4(0.f, 0.f, 1.f, 1.f); - material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; - } + glm::vec4 vModelBboxMin { model.boundingBox.min, 1.0f }; + glm::vec4 vModelBboxMax { model.boundingBox.max, 1.0f }; + + vModelBboxMin = vModelBboxMin * mWorldTransform; + vModelBboxMax = vModelBboxMax * mWorldTransform; + + if (m_camera.canSeeObject(glm::vec3(vModelBboxMin), glm::vec3(vModelBboxMax), m_matProjection)) { + // Add bounding box to render list + if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { + // Need to add mesh + render::RenderEntry &boundingBoxEntry = entries.emplace_back(); + + // Render params + boundingBoxEntry.iPrimitiveId = 0; + boundingBoxEntry.iMeshIndex = 0; + boundingBoxEntry.iTrianglesNr = 0; + boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; + + // World params + boundingBoxEntry.vPosition = vPosition; + boundingBoxEntry.mWorldTransform = mWorldTransform; + boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); + boundingBoxEntry.pMesh = const_cast(&model.boundingBoxMesh.value()); + + // Material + render::RenderEntry::Material &material = boundingBoxEntry.material; + material.vDiffuseColor = glm::vec4(0.f, 0.f, 1.f, 1.f); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + } - // Add each 'mesh' into render list - for (int iMeshIdx = 0; iMeshIdx < model.meshes.size(); iMeshIdx++) - { - const auto& mesh = model.meshes[iMeshIdx]; + // Add each 'mesh' into render list + for (int iMeshIdx = 0; iMeshIdx < model.meshes.size(); iMeshIdx++) { + const auto &mesh = model.meshes[iMeshIdx]; - if (mesh.materialId == 0) - continue; // Unable to render (ZWINPIC!) + if (mesh.materialId == 0) + continue;// Unable to render (ZWINPIC!) - // Filter by 'MeshVariantId' - const auto requiredVariationId = geom->getProperties().getObject("MeshVariantId", 0); - if (requiredVariationId != mesh.variationId) - { - continue; - } + // Filter by 'MeshVariantId' + const auto requiredVariationId = geom->getProperties().getObject("MeshVariantId", 0); + if (requiredVariationId != mesh.variationId) { + continue; + } - // And store entry to renderer - render::RenderEntry renderEntry = {}; + // And store entry to renderer + render::RenderEntry renderEntry = {}; - // Render params - renderEntry.iPrimitiveId = primId; - renderEntry.iMeshIndex = iMeshIdx; - renderEntry.iTrianglesNr = mesh.trianglesCount; - renderEntry.renderTopology = render::RenderTopology::RT_TRIANGLES; + // Render params + renderEntry.iPrimitiveId = primId; + renderEntry.iMeshIndex = iMeshIdx; + renderEntry.iTrianglesNr = mesh.trianglesCount; + renderEntry.renderTopology = render::RenderTopology::RT_TRIANGLES; - // World params - renderEntry.vPosition = vPosition; - renderEntry.mWorldTransform = mWorldTransform; - renderEntry.mLocalOriginalTransform = geom->getOriginalTransform(); - renderEntry.pMesh = const_cast(&mesh); + // World params + renderEntry.vPosition = vPosition; + renderEntry.mWorldTransform = mWorldTransform; + renderEntry.mLocalOriginalTransform = geom->getOriginalTransform(); + renderEntry.pMesh = const_cast(&mesh); - // Material - render::RenderEntry::Material& material = renderEntry.material; + // Material + render::RenderEntry::Material &material = renderEntry.material; - const auto& instances = m_pLevel->getLevelMaterials()->materialInstances; - const auto& matInstance = instances[mesh.materialId - 1]; + const auto &instances = m_pLevel->getLevelMaterials()->materialInstances; + const auto &matInstance = instances[mesh.materialId - 1]; - // Store parameters - material.id = mesh.materialId; - material.sInstanceMatName = matInstance.getName(); - material.sBaseMatClass = matInstance.getParentName(); + // Store parameters + material.id = mesh.materialId; + material.sInstanceMatName = matInstance.getName(); + material.sBaseMatClass = matInstance.getParentName(); - if (!matInstance.getBinders().empty()) - { - const auto& binder = matInstance.getBinders()[0]; // NOTE: In future I'll rewrite this place, but for now it's enough + if (!matInstance.getBinders().empty()) { + const auto &binder = matInstance.getBinders()[0];// NOTE: In future I'll rewrite this place, but for now it's enough - // Store parameters - // TODO: Need collect all parameters here + // Store parameters + // TODO: Need collect all parameters here - // Store render state - if (!binder.renderStates.empty()) - { - // TODO: In future we need to learn how to use multiple render states (if there are able to be 'multiple') - material.renderState = binder.renderStates[0]; - } + // Store render state + if (!binder.renderStates.empty()) { + // TODO: In future we need to learn how to use multiple render states (if there are able to be 'multiple') + material.renderState = binder.renderStates[0]; + } - // Resolve & store textures - std::fill(material.textures.begin(), material.textures.end(), kInvalidResource); + // Resolve & store textures + std::fill(material.textures.begin(), material.textures.end(), kInvalidResource); - for (const auto& texture : binder.textures) - { - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_NOTHING) - continue; // No texture at all + for (const auto &texture : binder.textures) { + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_NOTHING) + continue;// No texture at all - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID_AND_PATH) - { - assert(false && "Idk how to handle this"); - continue; - } + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID_AND_PATH) { + assert(false && "Idk how to handle this"); + continue; + } - const auto& kind = texture.getName(); + const auto &kind = texture.getName(); - int textureSlotId = render::TextureSlotId::kMaxTextureSlot; + int textureSlotId = render::TextureSlotId::kMaxTextureSlot; #define MATCH_TEXTURE_KIND(mode, modeName) if (kind == modeName) { textureSlotId = mode; } - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapDiffuse, "mapDiffuse") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapSpecularMask, "mapSpecularMask") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapEnvironment, "mapEnvironment") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionMask, "mapReflectionMask") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionFallOff, "mapReflectionFallOff") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapIllumination, "mapIllumination") - MATCH_TEXTURE_KIND(render::TextureSlotId::kMapTranslucency, "mapTranslucency") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapDiffuse, "mapDiffuse") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapSpecularMask, "mapSpecularMask") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapEnvironment, "mapEnvironment") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionMask, "mapReflectionMask") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapReflectionFallOff, "mapReflectionFallOff") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapIllumination, "mapIllumination") + MATCH_TEXTURE_KIND(render::TextureSlotId::kMapTranslucency, "mapTranslucency") #undef MATCH_TEXTURE_KIND - if (textureSlotId == render::TextureSlotId::kMaxTextureSlot) - continue; + if (textureSlotId == render::TextureSlotId::kMaxTextureSlot) + continue; - // Now we need to find texture instance and associate it - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID) - { - // Lookup in cache by texture id - if (auto it = m_resources->m_textureIndexToGL.find(texture.getTextureId()); it != m_resources->m_textureIndexToGL.end()) - { - material.textures[textureSlotId] = it->second; - break; + // Now we need to find texture instance and associate it + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_ID) { + // Lookup in cache by texture id + if (auto it = m_resources->m_textureIndexToGL.find(texture.getTextureId()); it != m_resources->m_textureIndexToGL.end()) { + material.textures[textureSlotId] = it->second; + break; + } } - } - if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH) - { - // Lookup in cache by texture path - if (auto it = m_resources->m_textureNameToGL.find(texture.getTexturePath()); it != m_resources->m_textureNameToGL.end()) - { - material.textures[textureSlotId] = it->second; - break; + if (texture.getPresentedTextureSources() == gamelib::mat::PresentedTextureSource::PTS_TEXTURE_PATH) { + // Lookup in cache by texture path + if (auto it = m_resources->m_textureNameToGL.find(texture.getTexturePath()); it != m_resources->m_textureNameToGL.end()) { + material.textures[textureSlotId] = it->second; + break; + } } } } - } - // Store shader - material.pShader = &m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; + // Store shader + material.pShader = &m_resources->m_shaders[m_resources->m_iTexturedShaderIdx]; - // Push or not? - if (!std::all_of(material.textures.begin(), material.textures.end(), [](const auto& v) -> bool { return v == kInvalidResource; })) - { - entries.emplace_back(renderEntry); + // Push or not? + if (!std::all_of(material.textures.begin(), material.textures.end(), [](const auto &v) -> bool { return v == kInvalidResource; })) { + entries.emplace_back(renderEntry); + } } } } @@ -1240,9 +1300,11 @@ namespace widgets break; case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: gapi->glBlendFunc(GL_ONE, GL_ONE); + gapi->glEnable(GL_ALPHA_TEST); break; case gamelib::mat::MATBlendMode::BM_ADD: gapi->glBlendFunc(GL_ONE, GL_ONE); + gapi->glEnable(GL_ALPHA_TEST); break; default: // Do nothing @@ -1361,4 +1423,146 @@ namespace widgets { m_renderList.clear(); } + + void SceneRenderWidget::computeRoomBoundingBox(RoomDef& d) + { + using R = gamelib::scene::SceneObject::EVisitResult; + + if (auto pRoom = d.rRoom.lock()) + { + // First of all we need try to lookup for 'CollisionMesh'. It has same name to room and be an STDOBJ + const auto& children = pRoom->getChildren(); + gamelib::scene::SceneObject* pCollisionMesh = nullptr; + pRoom->visitChildren([&pCollisionMesh, sTargetName = pRoom->getName()](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (pObject->getName() == sTargetName && pObject->getType()->getName() == "ZSTDOBJ") + { + pCollisionMesh = pObject.get(); + return R::VR_STOP_ALL; + } + + return R::VR_NEXT; // Do not go deeper + }); + + if (pCollisionMesh) + { + auto iPrimId = pCollisionMesh->getProperties().getObject("PrimId", 0); + if (iPrimId != 0) + { + // Nice, collision mesh was found! Just use it as source for bbox of ZROOM + auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; + + glm::vec4 vMin { sBoundingBox.min, 1.0f }; + glm::vec4 vMax { sBoundingBox.max, 1.0f }; + glm::mat4 mWorldTransform = pCollisionMesh->getWorldTransform(); + + vMin = vMin * mWorldTransform; + vMax = vMax * mWorldTransform; + + d.vBoundingBox = gamelib::BoundingBox(glm::vec3(vMin), glm::vec3(vMax)); + return; + } + } + + bool bBboxInited = false; + + pRoom->visitChildren([&](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (!pObject) + return R::VR_NEXT; + + auto iPrimId = pObject->getProperties().getObject("PrimId", 0); + if (!iPrimId) + return R::VR_CONTINUE; // Go deeper + + // Find prim cache + if (m_resources->m_modelsCache.contains(iPrimId)) + { + auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; + + glm::vec4 vMin { sBoundingBox.min, 1.0f }; + glm::vec4 vMax { sBoundingBox.max, 1.0f }; + glm::mat4 mWorldTransform = pObject->getWorldTransform(); + + vMin = vMin * mWorldTransform; + vMax = vMax * mWorldTransform; + + gamelib::BoundingBox vWorldBoundingBox = { glm::vec3(vMin.x, vMin.y, vMin.z), glm::vec3(vMax.x, vMax.y, vMax.z) }; + + if (!bBboxInited) + { + bBboxInited = true; + d.vBoundingBox = vWorldBoundingBox; + } + else + { + d.vBoundingBox.expand(vWorldBoundingBox); + } + + // Optimisation: we've assumed that when we have an object with bbox we will use top bbox instead of compute sub-bboxes + return R::VR_NEXT; + } + + return R::VR_CONTINUE; + }); + } + } + + void SceneRenderWidget::buildRoomCache() + { + m_rooms.clear(); + + // Now we need to find ZGROUP who ends by _LOCATIONS and lookup from this ZGROUP inside + auto locationsIt = std::find_if( + m_pLevel->getSceneObjects().begin(), + m_pLevel->getSceneObjects().end(), + [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { + return pObject && pObject->getName().ends_with("_LOCATIONS.zip"); + }); + + if (locationsIt != m_pLevel->getSceneObjects().end()) + { + // we've able to use standard workflow + // Save backdrop + m_pLevel->forEachObjectOfType("ZBackdrop", [this](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { + if (pObject) + { + auto& room = m_rooms.emplace_back(); + room.rRoom = pObject; + // ZBackdrop always has maximum possible size to see it from any point of the world + constexpr float kMinPoint = std::numeric_limits::min(); + constexpr float kMaxPoint = std::numeric_limits::max(); + room.vBoundingBox = gamelib::BoundingBox(glm::vec3(kMinPoint), glm::vec3(kMaxPoint)); + + return false; + } + + return true; + }); + + // Find ZROOMs + const gamelib::scene::SceneObject::Ptr& pNewRoot = *locationsIt; + + using R = gamelib::scene::SceneObject::EVisitResult; + pNewRoot->visitChildren([this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (!pObject) + { + return R::VR_STOP_ALL; + } + + if (pObject->getType()->getName() == "ZROOM") + { + // Add and go next, do not go inside + auto& room = m_rooms.emplace_back(); + room.rRoom = pObject; + + // Compute room dimensions + computeRoomBoundingBox(room); + + return R::VR_NEXT; + } + + // Go deep inside + return R::VR_CONTINUE; + }); + } + } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/UI/GeomControllersWidget.ui b/BMEdit/Editor/UI/UI/GeomControllersWidget.ui index 4619a8d..db10951 100644 --- a/BMEdit/Editor/UI/UI/GeomControllersWidget.ui +++ b/BMEdit/Editor/UI/UI/GeomControllersWidget.ui @@ -6,36 +6,111 @@ 0 0 - 400 - 300 + 300 + 409 Form - + - - - - - - - - - 65 - 16777215 - - - - ... - - - - - - - + + + 0 + + + + Controllers + + + + + + + + + + + false + + + + 25 + 16777215 + + + + X + + + + + + + + + + + + + New Controller + + + + + + Search... + + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add controller + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index 5c663be..679210b 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -14,5 +14,8 @@ namespace gamelib BoundingBox(const glm::vec3 &vMin, const glm::vec3 &vMax); glm::vec3 getCenter() const; + + void expand(const BoundingBox& another); + bool contains(const glm::vec3& vPoint) const; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h b/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h index c07fbf8..98fddbc 100644 --- a/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h +++ b/BMEdit/GameLib/Include/GameLib/PRP/PRPInstruction.h @@ -68,6 +68,11 @@ namespace gamelib::prp template <> const std::string& get() const { return str; } template <> const RawData& get() const { return raw; } template <> const StringArray& get() const { return stringArray; } + + /** + * Initialised (isSet = true) empty operand. + */ + static const PRPOperandVal kInitedOperandValue; }; class PRPInstruction diff --git a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h index eb982b4..0f1b2bb 100644 --- a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h +++ b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -89,6 +90,22 @@ namespace gamelib::scene */ [[nodiscard]] glm::mat4 getWorldTransform() const; + enum class EVisitResult + { + VR_CONTINUE, // Continue iterations inside + VR_STOP_ALL, // Stop all iterations, finish visitor + VR_NEXT // Go to next node on this level, do not go inside + }; + + /** + * @brief Visit scene tree from this object deep inside + * @param pred - predicate func + */ + void visitChildren(const std::function& pred) const; + + private: + EVisitResult internalVisitChildObjects(const std::function& pred) const; + private: std::string m_name {}; ///< Name of geom uint32_t m_typeId { 0u }; ///< Type ID of geom diff --git a/BMEdit/GameLib/Include/GameLib/Type.h b/BMEdit/GameLib/Include/GameLib/Type.h index 3d43aef..6e7a21f 100644 --- a/BMEdit/GameLib/Include/GameLib/Type.h +++ b/BMEdit/GameLib/Include/GameLib/Type.h @@ -43,6 +43,12 @@ namespace gamelib */ [[nodiscard]] virtual DataMappingResult map(const Span &instructions) const; + /** + * @fn makeDefaultPropertiesPack + * @return Value with pack of default constructed properties + */ + [[nodiscard]] virtual Value makeDefaultPropertiesPack() const; + private: std::string m_name {}; TypeKind m_kind { TypeKind::NONE }; diff --git a/BMEdit/GameLib/Include/GameLib/TypeAlias.h b/BMEdit/GameLib/Include/GameLib/TypeAlias.h index 11c5464..f0938f9 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeAlias.h +++ b/BMEdit/GameLib/Include/GameLib/TypeAlias.h @@ -17,6 +17,7 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; [[nodiscard]] const Type* getFinalType() const; [[nodiscard]] prp::PRPOpCode getFinalOpCode() const; diff --git a/BMEdit/GameLib/Include/GameLib/TypeArray.h b/BMEdit/GameLib/Include/GameLib/TypeArray.h index c70d8e5..d6f488c 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeArray.h +++ b/BMEdit/GameLib/Include/GameLib/TypeArray.h @@ -20,6 +20,7 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; private: prp::PRPOpCode m_entryType { prp::PRPOpCode::ERR_UNKNOWN }; uint32_t m_requiredCapacity { 0u }; diff --git a/BMEdit/GameLib/Include/GameLib/TypeBitfield.h b/BMEdit/GameLib/Include/GameLib/TypeBitfield.h index 4fcb8e7..ec094d5 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeBitfield.h +++ b/BMEdit/GameLib/Include/GameLib/TypeBitfield.h @@ -18,6 +18,7 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; private: TypeBitfield::PossibleOptions m_possibleOptions; }; diff --git a/BMEdit/GameLib/Include/GameLib/TypeComplex.h b/BMEdit/GameLib/Include/GameLib/TypeComplex.h index dfc066f..9894d21 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeComplex.h +++ b/BMEdit/GameLib/Include/GameLib/TypeComplex.h @@ -31,6 +31,7 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; private: GeomBasedTypeInfo &createGeomInfo(); diff --git a/BMEdit/GameLib/Include/GameLib/TypeContainer.h b/BMEdit/GameLib/Include/GameLib/TypeContainer.h index d7a2307..5b32ece 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeContainer.h +++ b/BMEdit/GameLib/Include/GameLib/TypeContainer.h @@ -16,5 +16,6 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/TypeEnum.h b/BMEdit/GameLib/Include/GameLib/TypeEnum.h index 16ccf8d..89d8e84 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeEnum.h +++ b/BMEdit/GameLib/Include/GameLib/TypeEnum.h @@ -22,6 +22,7 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; [[nodiscard]] const Entries &getPossibleValues() const; private: diff --git a/BMEdit/GameLib/Include/GameLib/TypeRawData.h b/BMEdit/GameLib/Include/GameLib/TypeRawData.h index 1463fcd..074ca85 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeRawData.h +++ b/BMEdit/GameLib/Include/GameLib/TypeRawData.h @@ -12,5 +12,6 @@ namespace gamelib [[nodiscard]] VerificationResult verify(const Span& instructions) const override; [[nodiscard]] Type::DataMappingResult map(const Span &instructions) const override; + [[nodiscard]] Value makeDefaultPropertiesPack() const override; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp index b6e0d7c..e6a5e60 100644 --- a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp +++ b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp @@ -1,4 +1,5 @@ #include +#include using namespace gamelib; @@ -11,4 +12,21 @@ BoundingBox::BoundingBox(const glm::vec3 &vMin, const glm::vec3 &vMax) glm::vec3 BoundingBox::getCenter() const { return (min + max) / 2.f; +} + +void BoundingBox::expand(const BoundingBox& another) +{ + min.x = std::min(min.x, another.min.x); + min.y = std::min(min.y, another.min.y); + min.z = std::min(min.z, another.min.z); + max.x = std::max(max.x, another.max.x); + max.y = std::max(max.y, another.max.y); + max.z = std::max(max.z, another.max.z); +} + +bool BoundingBox::contains(const glm::vec3& vPoint) const +{ + return vPoint.x >= min.x && vPoint.x <= max.x && + vPoint.y >= min.y && vPoint.y <= max.y && + vPoint.z >= min.z && vPoint.z <= max.z; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/PRP/PRPInstruction.cpp b/BMEdit/GameLib/Source/GameLib/PRP/PRPInstruction.cpp index 2120a9f..17d0fee 100644 --- a/BMEdit/GameLib/Source/GameLib/PRP/PRPInstruction.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRP/PRPInstruction.cpp @@ -248,4 +248,6 @@ namespace gamelib::prp { return !operator==(other); } + + const PRPOperandVal PRPOperandVal::kInitedOperandValue { false }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index 70e27e8..5450969 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -194,4 +194,39 @@ namespace gamelib::scene return mWorldMatrix; } + + void SceneObject::visitChildren(const std::function& pred) const + { + if (!pred) + return; + + internalVisitChildObjects(pred); + } + + SceneObject::EVisitResult SceneObject::internalVisitChildObjects(const std::function& pred) const + { + for (const auto rChild : getChildren()) + { + if (auto pChild = rChild.lock()) + { + const auto predRes = pred(pChild); + + switch (predRes) + { + case EVisitResult::VR_CONTINUE: + { + auto predResInner = pChild->internalVisitChildObjects(pred); + if (predResInner == EVisitResult::VR_STOP_ALL) + return predResInner; + } + break; + + case EVisitResult::VR_STOP_ALL: return EVisitResult::VR_STOP_ALL; + case EVisitResult::VR_NEXT: continue; + } + } + } + + return EVisitResult::VR_CONTINUE; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Type.cpp b/BMEdit/GameLib/Source/GameLib/Type.cpp index 503a080..963d07c 100644 --- a/BMEdit/GameLib/Source/GameLib/Type.cpp +++ b/BMEdit/GameLib/Source/GameLib/Type.cpp @@ -32,4 +32,9 @@ namespace gamelib { throw NotImplemented("You must implement this method in your own class!"); } + + Value Type::makeDefaultPropertiesPack() const + { + throw NotImplemented("You must implement this method in your own class!"); + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp b/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp index 548e89b..15a0fa8 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp @@ -64,6 +64,23 @@ namespace gamelib throw std::runtime_error("TypeAlias::map() failed. Alias not inited yet!"); } + Value TypeAlias::makeDefaultPropertiesPack() const + { + if (auto typePtr = std::get_if(&m_resultTypeInfo); typePtr != nullptr) + { + // An alias to another type - jmp + return (*typePtr)->makeDefaultPropertiesPack(); + } + + if (auto typeOpCodeValue = std::get_if(&m_resultTypeInfo); typeOpCodeValue != nullptr) + { + // A holder of single instruction (like ZGEOMREF) + return { this, { prp::PRPInstruction(*typeOpCodeValue, prp::PRPOperandVal::kInitedOperandValue) } }; + } + + throw std::runtime_error("TypeAlias::makeDefaultPropertiesPack() failed. Alias not inited yet!"); + } + const Type* TypeAlias::getFinalType() const { if (auto typePtrPtr = std::get_if(&m_resultTypeInfo); typePtrPtr != nullptr) diff --git a/BMEdit/GameLib/Source/GameLib/TypeArray.cpp b/BMEdit/GameLib/Source/GameLib/TypeArray.cpp index 48d60e3..380c3b6 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeArray.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeArray.cpp @@ -124,4 +124,22 @@ namespace gamelib Value(this, std::move(data), views), instructions.slice(sliceSize, instructions.size() - sliceSize)); } + + Value TypeArray::makeDefaultPropertiesPack() const + { + // create an array. It presented via at least 3 instructions: BeginArray, [m_entryType opcode], EndArray + std::vector instructions{}; + instructions.resize(2 + m_requiredCapacity); + + instructions.front() = PRPInstruction(PRPOpCode::Array, PRPOperandVal(static_cast(m_requiredCapacity))); + instructions.back() = PRPInstruction(PRPOpCode::EndArray); + + // Fill by empty instructions. For float - 0.f, for int - 0, for bool - false, for string - "" + for (int i = 1; i < m_requiredCapacity + 1; i++) + { + instructions[i] = PRPInstruction(m_entryType, prp::PRPOperandVal::kInitedOperandValue); + } + + return Value(this, instructions); + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeBitfield.cpp b/BMEdit/GameLib/Source/GameLib/TypeBitfield.cpp index 055e6e0..c97c158 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeBitfield.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeBitfield.cpp @@ -51,4 +51,11 @@ namespace gamelib std::vector data { instructions[0] }; return Type::DataMappingResult(Value(this, std::move(data)), span); } + + Value TypeBitfield::makeDefaultPropertiesPack() const + { + // By default, we will produce empty StringArray opcode + constexpr int32_t kNoArgs = 0; + return Value(this, { PRPInstruction(PRPOpCode::StringArray, PRPOperandVal(kNoArgs)) }); + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeComplex.cpp b/BMEdit/GameLib/Source/GameLib/TypeComplex.cpp index 48783de..2a98b24 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeComplex.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeComplex.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -222,4 +223,44 @@ namespace gamelib // Done return Type::DataMappingResult(resultValue, ourSlice); } + + Value TypeComplex::makeDefaultPropertiesPack() const + { + // NOTE: Complex type with unexposed properties will produce potentially wrong object. We will produce only exposed properties, nothing more. + Value result {this, {}, {}}; + + // 1: Get pack of parent properties before + if (auto parent = getParent(); parent != nullptr) + { + // Really shitty code copypaste from ::map() method. What the hell was in my mind at that moment??? + auto parentPack = parent->makeDefaultPropertiesPack(); + for (const auto& [name, ip, views]: parentPack.getEntries()) + { + result += std::make_pair(name, Value(parentPack.getType(), Span(parentPack.getInstructions()).slice(ip).as>(), views)); + } + } + + // 2: Add our properties + for (const auto &view: m_instructionViews) + { + if (view.isTrivialType()) + { + // Trivial subject - just append + result += std::pair(view.getName(), Value(this, { PRPInstruction(view.getTrivialType(), PRPOperandVal::kInitedOperandValue) }, { view })); + } + else + { + // Ok, need to ask inner type properties pack + if (auto innerType = view.getType()) + { + // Just add inner type properties pack + auto pack = innerType->makeDefaultPropertiesPack(); + result += std::pair(view.getName(), Value(this, pack.getInstructions(), { view })); + } + else throw std::runtime_error("TypeComplex::makeDefaultPropertiesPack: unable to make default properties pack for " + view.getName()); + } + } + + return result; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeContainer.cpp b/BMEdit/GameLib/Source/GameLib/TypeContainer.cpp index 5924c82..ecb111e 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeContainer.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeContainer.cpp @@ -53,4 +53,11 @@ namespace gamelib return Type::DataMappingResult(Value(this, std::move(data)), instructions.slice(sliceSize, instructions.size() - sliceSize)); } + + Value TypeContainer::makeDefaultPropertiesPack() const + { + // Just produce an empty container + constexpr int32_t kEmptyContainerSize = 0; + return Value(this, { PRPInstruction(PRPOpCode::Container, PRPOperandVal(kEmptyContainerSize)) }); + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp b/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp index 9020a2e..f1c3643 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp @@ -57,6 +57,22 @@ namespace gamelib return Type::DataMappingResult(Value(this, std::move(valueData), { ValueView("Value", instructions[0].getOpCode(), this) }), newSlice); } + Value TypeEnum::makeDefaultPropertiesPack() const + { + // Here we need to select lowest possible value of presented and return it. When we have an empty array we should raise an exception because our behaviour is undefined in this case + if (m_possibleValues.empty()) + throw std::runtime_error("Invalid enum declaration! Unable to produce 'default' enum without any variations!"); + + if (m_possibleValues.size() == 1) + { + // just return this entry anyway + return Value(this, { PRPInstruction(PRPOpCode::StringOrArray_E, PRPOperandVal(m_possibleValues[0].name)) }); + } + + auto lowest = std::min_element(m_possibleValues.begin(), m_possibleValues.end(), [](const Entry& a, const Entry& b) { return a.value < b.value; }); + return Value(this, { PRPInstruction(PRPOpCode::StringOrArray_E, PRPOperandVal(lowest->name)) }); + } + const TypeEnum::Entries &TypeEnum::getPossibleValues() const { return m_possibleValues; diff --git a/BMEdit/GameLib/Source/GameLib/TypeRawData.cpp b/BMEdit/GameLib/Source/GameLib/TypeRawData.cpp index aebfa1a..42c7577 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeRawData.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeRawData.cpp @@ -61,4 +61,11 @@ namespace gamelib std::vector data { instructions[0], instructions[1] }; return Type::DataMappingResult (Value(this, std::move(data)), instructions.slice(2, instructions.size() - 2)); } + + Value TypeRawData::makeDefaultPropertiesPack() const + { + // Produce an empty Container opcode + constexpr int32_t kEmptyContainerSize = 0; + return Value(this, { PRPInstruction(PRPOpCode::Container, PRPOperandVal(kEmptyContainerSize)) }); + } } \ No newline at end of file From c358260a0a655b760bb4081cdbf90bfbd594202b Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 21 Dec 2023 19:43:16 +0300 Subject: [PATCH 36/80] Fix missing include --- BMEdit/GameLib/Source/GameLib/TypeEnum.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp b/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp index f1c3643..96c103d 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeEnum.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace gamelib From 2e58a9718af4f33bc7364ad9b2a70c5ba9dab063 Mon Sep 17 00:00:00 2001 From: DronCode Date: Fri, 22 Dec 2023 19:56:01 +0300 Subject: [PATCH 37/80] Refactored camera class. Added Frustum class to make frustum culling (it's a little bit buggy now) --- BMEdit/Editor/Include/Render/Camera.h | 201 ++++++------------ BMEdit/Editor/Include/Render/Frustum.h | 45 ++++ .../Include/Widgets/SceneRenderWidget.h | 10 +- BMEdit/Editor/Source/Render/Camera.cpp | 115 ++++++++++ .../Source/Widgets/SceneRenderWidget.cpp | 70 +++--- .../Source/GameLib/Scene/SceneObject.cpp | 22 +- 6 files changed, 272 insertions(+), 191 deletions(-) create mode 100644 BMEdit/Editor/Include/Render/Frustum.h create mode 100644 BMEdit/Editor/Source/Render/Camera.cpp diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index 9eabdb3..9b9c891 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -7,148 +9,81 @@ #include #include - namespace render { - // Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods - enum Camera_Movement { - FORWARD, - BACKWARD, - LEFT, - RIGHT - }; + using CameraMovementMask = uint8_t; - // Default camera values - const float YAW = -90.0f; - const float PITCH = 0.0f; - const float SPEED = 2.5f; - const float SENSITIVITY = 0.1f; - const float ZOOM = 45.0f; + enum CameraMovementMaskValues : CameraMovementMask + { + CM_FORWARD = 1 << 0, + CM_BACKWARD = 1 << 1, + CM_LEFT = 1 << 2, + CM_RIGHT = 1 << 3, + + CM_SPEEDUP_MOD = 1 << 7 + }; - /** - * @credits https://learnopengl.com/Getting-started/Camera - */ class Camera { public: - // camera Attributes - glm::vec3 Position; - glm::vec3 Front; - glm::vec3 Up; - glm::vec3 Right; - glm::vec3 WorldUp; - // euler Angles - float Yaw; - float Pitch; - // camera options - float MovementSpeed; - float MouseSensitivity; - float Zoom; - - // constructor with vectors - Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = position; - WorldUp = up; - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - // constructor with scalar values - Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) - { - Position = glm::vec3(posX, posY, posZ); - WorldUp = glm::vec3(upX, upY, upZ); - Yaw = yaw; - Pitch = pitch; - updateCameraVectors(); - } - - void setPosition(const glm::vec3& position) - { - Position = position; - updateCameraVectors(); - } + static constexpr float kDefaultDt = 1.f / 60.f; + + Camera() = default; + Camera(float fov, const glm::vec3& vPosition, const glm::ivec2& vScreenSize); + + // Getters + [[nodiscard]] float getFOV() const { return m_fFov; } + [[nodiscard]] float getYaw() const { return m_fYaw; } + [[nodiscard]] float getPitch() const { return m_fPitch; } + [[nodiscard]] float getSpeed() const { return m_fSpeed; } + [[nodiscard]] float getSensitivity() const { return m_fSensitivity; } + [[nodiscard]] const glm::vec3& getPosition() const { return m_vPosition; } + [[nodiscard]] const glm::vec3& getDirection() const { return m_vLookDirection; } + [[nodiscard]] const glm::vec3& getUp() const { return m_vUp; } + [[nodiscard]] const glm::vec3& getWorldUp() const { return m_vWorldUp; } + [[nodiscard]] const glm::vec3& getRight() const { return m_vRight; } + [[nodiscard]] const glm::mat4& getView() const { return m_mView; } + [[nodiscard]] const glm::mat4& getProjection() const { return m_mProj; } + [[nodiscard]] const glm::mat4& getProjView() const { return m_mProjView; } + + // Setters + void setFOV(float fov); + void setSpeed(float speed); + void setSensitivity(float sens); + void setViewport(int width, int height); + void setPosition(const glm::vec3& vPosition); + + // Movement + void handleKeyboardMovement(CameraMovementMask movementMask = CameraMovementMaskValues::CM_FORWARD, float dt = kDefaultDt); + void processMouseMovement(float xoffset, float yoffset, float dt = kDefaultDt); + [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax) const; - // returns the view matrix calculated using Euler Angles and the LookAt Matrix - glm::mat4 getViewMatrix() const - { - return glm::lookAt(Position, Position + Front, Up); - } - - // processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems) - void processKeyboard(Camera_Movement direction, float deltaTime, float moveScale = 1.f) - { - float velocity = MovementSpeed * deltaTime * moveScale; - if (direction == FORWARD) - Position -= Front * velocity; - if (direction == BACKWARD) - Position += Front * velocity; - if (direction == LEFT) - Position -= Right * velocity; - if (direction == RIGHT) - Position += Right * velocity; - } - - // processes input received from a mouse input system. Expects the offset value in both the x and y direction. - void processMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) - { - xoffset *= MouseSensitivity; - yoffset *= MouseSensitivity; - - Yaw += xoffset; - Pitch += yoffset; - - // make sure that when pitch is out of bounds, screen doesn't get flipped - if (constrainPitch) - { - if (Pitch > 89.0f) - Pitch = 89.0f; - if (Pitch < -89.0f) - Pitch = -89.0f; - } - - // update Front, Right and Up Vectors using the updated Euler angles - updateCameraVectors(); - } - - // processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis - void processMouseScroll(float yoffset) - { - Zoom -= (float)yoffset; - if (Zoom < 1.0f) - Zoom = 1.0f; - if (Zoom > 45.0f) - Zoom = 45.0f; - } - - // return true if object represented by vMin & vMax is visible - [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax, const glm::mat4& mProj) const - { - return true; - /* - * // still buggy shit - const bool bCenter = glm::dot(Front, Position - ((vMax - vMin) * .5f)) > .0f; - const bool bMin = glm::dot(Front, Position - vMin) > .0f; - const bool bMax = glm::dot(Front, Position - vMax) > .0f; - return bCenter || bMin || bMax; - */ - } + private: + void update(); private: - // calculates the front vector from the Camera's (updated) Euler Angles - void updateCameraVectors() - { - // calculate the new Front vector - glm::vec3 front; - front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - front.y = sin(glm::radians(Pitch)); - front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - Front = glm::normalize(front); - // also re-calculate the Right and Up vector - Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement. - Up = glm::normalize(glm::cross(Right, Front)); - } + // Camera parameters + glm::ivec2 m_vScreenSize { 0 }; + float m_fFov { 80.f }; + float m_fYaw { -90.f }; + float m_fPitch { .0f }; + float m_fSpeed { 2.5f }; + float m_fSensitivity { 0.1f }; + float m_fNearPlane { .01f }; + float m_fFarPlane { 10'000.f }; + + // Calculated things + glm::vec3 m_vPosition { .0f }; + glm::vec3 m_vLookDirection { .0f, .0f, -1.f }; + glm::vec3 m_vUp { .0f, 1.f, .0f }; + glm::vec3 m_vRight { .0f }; + glm::vec3 m_vWorldUp { .0f, 1.0f, .0f }; // On init same to m_vUp + + glm::mat4 m_mView { 1.f }; + glm::mat4 m_mProj { 1.f }; + glm::mat4 m_mProjView { 1.f }; + + // Frustum + Frustum m_sFrustum {}; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Frustum.h b/BMEdit/Editor/Include/Render/Frustum.h new file mode 100644 index 0000000..8bae8ab --- /dev/null +++ b/BMEdit/Editor/Include/Render/Frustum.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + + +namespace render +{ + class Frustum + { + public: + Frustum() = default; + + void setup(const glm::mat4& mProjView) + { + m_vPlanes[0] = glm::vec4(mProjView[0][3] + mProjView[0][0], mProjView[1][3] + mProjView[1][0], mProjView[2][3] + mProjView[2][0], mProjView[3][3] + mProjView[3][0]); + m_vPlanes[1] = glm::vec4(mProjView[0][3] - mProjView[0][0], mProjView[1][3] - mProjView[1][0], mProjView[2][3] - mProjView[2][0], mProjView[3][3] - mProjView[3][0]); + m_vPlanes[2] = glm::vec4(mProjView[0][3] + mProjView[0][1], mProjView[1][3] + mProjView[1][1], mProjView[2][3] + mProjView[2][1], mProjView[3][3] + mProjView[3][1]); + m_vPlanes[3] = glm::vec4(mProjView[0][3] - mProjView[0][1], mProjView[1][3] - mProjView[1][1], mProjView[2][3] - mProjView[2][1], mProjView[3][3] - mProjView[3][1]); + m_vPlanes[4] = glm::vec4(mProjView[0][3] + mProjView[0][2], mProjView[1][3] + mProjView[1][2], mProjView[2][3] + mProjView[2][2], mProjView[3][3] + mProjView[3][2]); + m_vPlanes[5] = glm::vec4(mProjView[0][3] - mProjView[0][2], mProjView[1][3] - mProjView[1][2], mProjView[2][3] - mProjView[2][2], mProjView[3][3] - mProjView[3][2]); + + for (auto& vPlane : m_vPlanes) + { + float fLen = glm::length(glm::vec3(vPlane)); + vPlane /= fLen; + } + } + + [[nodiscard]] bool isBoxVisible(const glm::vec3& vMin, const glm::vec3& vMax) const + { + for (const auto& vPlane : m_vPlanes) + { + if (vPlane.x * (vPlane.x > 0 ? vMax.x : vMin.x) + vPlane.y * (vPlane.y > 0 ? vMax.y : vMin.y) + vPlane.z * (vPlane.z > 0 ? vMax.z : vMin.z) + vPlane.w <= 0) + { + return false; + } + } + return true; + } + + private: + std::array m_vPlanes; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 7222011..afe2b7b 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -52,8 +52,8 @@ namespace widgets [[nodiscard]] render::Camera& getCamera() { return m_camera; } [[nodiscard]] const render::Camera& getCamera() const { return m_camera; } - [[nodiscard]] float getFOV() const { return m_fFOV; } - void setFOV(float fov) { m_fFOV = fov; m_bDirtyProj = true; } + [[nodiscard]] float getFOV() const { return m_camera.getFOV(); } + void setFOV(float fov) { m_camera.setFOV(fov); } void setGeomViewMode(gamelib::scene::SceneObject* sceneObject); void setWorldViewMode(); @@ -91,7 +91,6 @@ namespace widgets void mouseReleaseEvent(QMouseEvent *event) override; private: - void updateProjectionMatrix(int w, int h); void doLoadTextures(QOpenGLFunctions_3_3_Core* glFunctions); void doLoadGeometry(QOpenGLFunctions_3_3_Core* glFunctions); void doCompileShaders(QOpenGLFunctions_3_3_Core* glFunctions); @@ -113,11 +112,6 @@ namespace widgets // Camera & world view render::Camera m_camera {}; - glm::mat4 m_matProjection {}; - float m_fFOV { 67.664f }; - float m_fZNear { .1f }; - float m_fZFar { 100'000.f }; - bool m_bDirtyProj { true }; uint8_t m_renderMode = RenderMode::RM_DEFAULT; // State diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp new file mode 100644 index 0000000..06bbd58 --- /dev/null +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -0,0 +1,115 @@ +#include + + +namespace render +{ + Camera::Camera(float fov, const glm::vec3 &vPosition, const glm::ivec2 &vScreenSize) + : m_fFov(fov), m_vPosition(vPosition), m_vScreenSize(vScreenSize) + { + update(); + } + + void Camera::setFOV(float fov) + { + if (m_fFov != fov) + { + m_fFov = fov; + update(); + } + } + + void Camera::setSpeed(float speed) + { + if (speed > 0.f) + { + m_fSpeed = speed; + } + } + + void Camera::setSensitivity(float sens) + { + if (sens > .0f) + { + m_fSensitivity = sens; + } + } + + void Camera::setViewport(int width, int height) + { + if (m_vScreenSize.x != width || m_vScreenSize.y != height) + { + m_vScreenSize.x = width; + m_vScreenSize.y = height; + + update(); + } + } + + void Camera::setPosition(const glm::vec3& vPosition) + { + m_vPosition = vPosition; + update(); + } + + // Movement + void Camera::handleKeyboardMovement(CameraMovementMask movementMask, float dt) + { + // Handle keyboard movement + const float fSpeedUp = (movementMask & CM_SPEEDUP_MOD) ? 4.0f : 1.0f; + const float fVelocity = m_fSpeed * fSpeedUp; + + if ((movementMask & CM_FORWARD) && (movementMask & CM_BACKWARD)) movementMask &= ~(CM_FORWARD | CM_BACKWARD); + if ((movementMask & CM_LEFT) && (movementMask & CM_RIGHT)) movementMask &= ~(CM_LEFT | CM_RIGHT); + if (movementMask == CM_SPEEDUP_MOD) movementMask = 0; + + if (movementMask > 0) { + if (movementMask & CM_FORWARD) m_vPosition += m_vLookDirection * fVelocity; + if (movementMask & CM_BACKWARD) m_vPosition -= m_vLookDirection * fVelocity; + if (movementMask & CM_LEFT) m_vPosition -= m_vRight * fVelocity; + if (movementMask & CM_RIGHT) m_vPosition += m_vRight * fVelocity; + + update(); + } + } + + void Camera::processMouseMovement(float xoffset, float yoffset, float dt) + { + // Handle mouse movement + xoffset *= m_fSensitivity; + yoffset *= m_fSensitivity; + + m_fYaw += xoffset; + m_fPitch += yoffset; + + if (m_fPitch > 89.0f) + m_fPitch = 89.0f; + + if (m_fPitch < -89.0f) + m_fPitch = -89.0f; + + update(); + } + + bool Camera::canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax) const + { + return m_sFrustum.isBoxVisible(vMin, vMax); + } + + void Camera::update() + { + glm::vec3 vFront { .0f }; + vFront.x = cos(glm::radians(m_fYaw)) * cos(glm::radians(m_fPitch)); + vFront.y = sin(glm::radians(m_fPitch)); + vFront.z = sin(glm::radians(m_fYaw)) * cos(glm::radians(m_fPitch)); + m_vLookDirection = glm::normalize(vFront); + + m_vRight = glm::normalize(glm::cross(m_vLookDirection, m_vWorldUp)); + m_vUp = glm::normalize(glm::cross(m_vRight, m_vLookDirection)); + + m_mView = glm::lookAtLH(m_vPosition, m_vPosition + m_vLookDirection, m_vUp); + m_mProj = glm::perspectiveFovLH(glm::radians(m_fFov), static_cast(m_vScreenSize.x), static_cast(m_vScreenSize.y), m_fNearPlane, m_fFarPlane); + m_mProjView = m_mProj * m_mView; + + m_sFrustum.setup(m_mProjView); + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index a5ce232..ed58076 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -142,11 +142,6 @@ namespace widgets funcs->glEnable(GL_BLEND); funcs->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - if (m_bDirtyProj) - { - updateProjectionMatrix(vp.x, vp.y); - } - // NOTE: Before render anything we need to look at material and check MATRenderState. // If it's applied we need to setup OpenGL into correct state to make perfect rendering switch (m_eState) @@ -243,9 +238,9 @@ namespace widgets Q_UNUSED(h) // Update projection - updateProjectionMatrix(w, h); + m_camera.setViewport(w, h); - // Because our list of visible objects could be changed here + // Because our list of visible objects could be changed here (???) invalidateRenderList(); } @@ -253,37 +248,40 @@ namespace widgets { if (m_pLevel) { - bool bMoved = false; - constexpr float kBaseDt = 1.f / 60.f; - float kSpeedUp = 100.f; - - if (event->modifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) - kSpeedUp *= 4.f; - + render::CameraMovementMask movementMask {}; if (event->key() == Qt::Key_W) { - m_camera.processKeyboard(render::Camera_Movement::FORWARD, kBaseDt, kSpeedUp); - bMoved = true; + movementMask |= render::CameraMovementMaskValues::CM_FORWARD; + } + + if (event->key() == Qt::Key_S) + { + movementMask |= render::CameraMovementMaskValues::CM_BACKWARD; } - else if (event->key() == Qt::Key_S) + + if (event->key() == Qt::Key_A) { - m_camera.processKeyboard(render::Camera_Movement::BACKWARD, kBaseDt, kSpeedUp); - bMoved = true; + movementMask |= render::CameraMovementMaskValues::CM_LEFT; } - else if (event->key() == Qt::Key_A) + + if (event->key() == Qt::Key_D) { - m_camera.processKeyboard(render::Camera_Movement::LEFT, kBaseDt, kSpeedUp); - bMoved = true; + movementMask |= render::CameraMovementMaskValues::CM_RIGHT; } - else if (event->key() == Qt::Key_D) + + if (event->modifiers().testFlag(Qt::KeyboardModifier::ShiftModifier)) { - m_camera.processKeyboard(render::Camera_Movement::RIGHT, kBaseDt, kSpeedUp); - bMoved = true; + movementMask |= render::CameraMovementMaskValues::CM_SPEEDUP_MOD; } - if (bMoved) + if ((movementMask & CM_FORWARD) && (movementMask & CM_BACKWARD)) movementMask &= ~(CM_FORWARD | CM_BACKWARD); + if ((movementMask & CM_LEFT) && (movementMask & CM_RIGHT)) movementMask &= ~(CM_LEFT | CM_RIGHT); + + if (movementMask > 0 && movementMask != (CM_SPEEDUP_MOD)) { + m_camera.handleKeyboardMovement(movementMask /* dt */); + invalidateRenderList(); repaint(); } @@ -321,7 +319,7 @@ namespace widgets if (std::fabsf(xOffset - kMinMovement) > std::numeric_limits::epsilon() || std::fabsf(yOffset - kMinMovement) > std::numeric_limits::epsilon()) { invalidateRenderList(); - m_camera.processMouseMovement(xOffset, yOffset); + m_camera.processMouseMovement(xOffset, yOffset /* dt */); } } @@ -342,12 +340,6 @@ namespace widgets m_mouseLastPosition = QPoint(0, 0); } - void SceneRenderWidget::updateProjectionMatrix(int w, int h) - { - m_matProjection = glm::perspectiveFovLH(glm::radians(m_fFOV), static_cast(w), static_cast(h), m_fZNear, m_fZFar); - m_bDirtyProj = false; - } - void SceneRenderWidget::setLevel(gamelib::Level *pLevel) { if (m_pLevel != pLevel) @@ -1003,7 +995,7 @@ namespace widgets // Render static for (const auto& sRoomDef : m_rooms) { - if (sRoomDef.vBoundingBox.contains(m_camera.Position) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max, m_matProjection)) + if (sRoomDef.vBoundingBox.contains(m_camera.getPosition()) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) { if (auto pRoom = sRoomDef.rRoom.lock()) { @@ -1040,8 +1032,8 @@ namespace widgets // Post sorting entries.sort([&camera](const render::RenderEntry& a, const render::RenderEntry& b) -> bool { // Check distance to camera - const float fADistanceToCamera = glm::length(camera.Position - a.vPosition); - const float fBDistanceToCamera = glm::length(camera.Position - b.vPosition); + const float fADistanceToCamera = glm::length(camera.getPosition() - a.vPosition); + const float fBDistanceToCamera = glm::length(camera.getPosition() - b.vPosition); return fADistanceToCamera > fBDistanceToCamera; }); @@ -1126,7 +1118,7 @@ namespace widgets vModelBboxMin = vModelBboxMin * mWorldTransform; vModelBboxMax = vModelBboxMax * mWorldTransform; - if (m_camera.canSeeObject(glm::vec3(vModelBboxMin), glm::vec3(vModelBboxMax), m_matProjection)) { + if (m_camera.canSeeObject(glm::vec3(vModelBboxMin), glm::vec3(vModelBboxMax))) { // Add bounding box to render list if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { // Need to add mesh @@ -1371,8 +1363,8 @@ namespace widgets // Setup parameters (common) shader->setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mWorldTransform); - shader->setUniform(glFunctions, ShaderConstants::kCameraProjection, m_matProjection); - shader->setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getViewMatrix()); + shader->setUniform(glFunctions, ShaderConstants::kCameraProjection, m_camera.getProjection()); + shader->setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getView()); shader->setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); // TODO: Need to move into constants diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index 5450969..dfc8bf0 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -149,17 +149,17 @@ namespace gamelib::scene const float* pSrcMatrix = glm::value_ptr(mMatrix); float* pDstMatrix = glm::value_ptr(mResult); - // Result of reverse engineering of sub_489740 (void __cdecl Transform3x3To4x4Matrix(Mat4x4 *pDstMtx, Mat3x3 *pSrcMtx, Vec3F *pVPosition)) - pDstMatrix[0] = pSrcMatrix[6]; - pDstMatrix[1] = pSrcMatrix[7]; - pDstMatrix[2] = pSrcMatrix[8]; - pDstMatrix[3] = 0.0f; - pDstMatrix[4] = pSrcMatrix[3]; - pDstMatrix[5] = pSrcMatrix[4]; - pDstMatrix[6] = pSrcMatrix[5]; - pDstMatrix[7] = 0.0f; - pDstMatrix[8] = pSrcMatrix[0]; - pDstMatrix[9] = pSrcMatrix[1]; + // Result of reverse engineering of MatPosToMatrix (or Transform3x3To4x4Matrix from PC version (sub_489740)) + pDstMatrix[0] = pSrcMatrix[6]; + pDstMatrix[1] = pSrcMatrix[7]; + pDstMatrix[2] = pSrcMatrix[8]; + pDstMatrix[3] = 0.0f; + pDstMatrix[4] = pSrcMatrix[3]; + pDstMatrix[5] = pSrcMatrix[4]; + pDstMatrix[6] = pSrcMatrix[5]; + pDstMatrix[7] = 0.0f; + pDstMatrix[8] = pSrcMatrix[0]; + pDstMatrix[9] = pSrcMatrix[1]; pDstMatrix[10] = pSrcMatrix[2]; pDstMatrix[11] = 0.0f; pDstMatrix[12] = vPosition.x; From 214e386ca2cb2ea7a19a8f8aa43b3c0e0b388468 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 23 Dec 2023 10:49:01 +0300 Subject: [PATCH 38/80] Fixed world transformation for bounding boxes. Added render stats and current room info. --- .../Include/Widgets/SceneRenderWidget.h | 13 +++- .../Source/Widgets/SceneRenderWidget.cpp | 78 ++++++++++--------- BMEdit/Editor/UI/Include/BMEditMainWindow.h | 4 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 15 ++++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 16 ++-- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 3 + BMEdit/GameLib/Source/GameLib/BoundingBox.cpp | 29 +++++++ 7 files changed, 111 insertions(+), 47 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index afe2b7b..8e4ece5 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -39,6 +39,14 @@ namespace widgets RM_DEFAULT = RM_TEXTURE | RM_NON_ALPHA_OBJECTS | RM_ALPHA_OBJECTS, ///< Render in texture mode with alpha/non-alpha objects }; + struct RenderStats + { + QString currentRoom {}; + int allowedObjects { 0 }; + int rejectedObjects { 0 }; + float fFrameTime { .0f }; // how much time used for render this frame + }; + class SceneRenderWidget : public QOpenGLWidget { Q_OBJECT @@ -73,6 +81,7 @@ namespace widgets signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); + void frameReady(const RenderStats& stats); public slots: void onRedrawRequested(); @@ -98,8 +107,8 @@ namespace widgets void doPrepareInvalidatedResources(QOpenGLFunctions_3_3_Core* glFunctions); [[nodiscard]] glm::ivec2 getViewportSize() const; - void collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility); - void collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility); + void collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility); + void collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility); void performRender(QOpenGLFunctions_3_3_Core* glFunctions, const render::RenderEntriesList& entries, const render::Camera& camera, const std::function& filter); void invalidateRenderList(); diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index ed58076..84f4626 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include @@ -123,6 +123,10 @@ namespace widgets void SceneRenderWidget::paintGL() { + RenderStats renderStats {}; + + auto renderStartTime = std::chrono::high_resolution_clock::now(); + auto funcs = QOpenGLVersionFunctionsFactory::get(QOpenGLContext::currentContext()); if (!funcs) { qFatal("Could not obtain required OpenGL context version"); @@ -207,7 +211,7 @@ namespace widgets if (m_renderList.empty()) { - collectRenderList(m_camera, pRoot, m_renderList, bIgnoreVisibility); + collectRenderList(m_camera, pRoot, m_renderList, renderStats, bIgnoreVisibility); } if (!m_renderList.empty()) @@ -226,6 +230,15 @@ namespace widgets { performRender(funcs, m_renderList, m_camera, onlyAlpha); } + + // Submit stats + if (!m_renderList.empty()) + { + auto renderEndTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = renderEndTime - renderStartTime; + renderStats.fFrameTime = elapsed.count(); + emit frameReady(renderStats); + } } } break; @@ -978,7 +991,7 @@ namespace widgets return { QWidget::width(), QWidget::height() }; } - void SceneRenderWidget::collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, bool bIgnoreVisibility) + void SceneRenderWidget::collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility) { if (m_pLevel->getSceneObjects().empty()) return; @@ -986,7 +999,7 @@ namespace widgets if (pRootGeom != m_pLevel->getSceneObjects()[0].get() || m_rooms.empty() /* on some levels m_rooms cache could be not presented! */) { // Render from specific node - collectRenderEntriesIntoRenderList(pRootGeom, entries, bIgnoreVisibility); + collectRenderEntriesIntoRenderList(pRootGeom, entries, stats, bIgnoreVisibility); } else { @@ -995,11 +1008,19 @@ namespace widgets // Render static for (const auto& sRoomDef : m_rooms) { - if (sRoomDef.vBoundingBox.contains(m_camera.getPosition()) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) + const bool bCameraInsideRoom = sRoomDef.vBoundingBox.contains(m_camera.getPosition()); + + if (bCameraInsideRoom || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) { if (auto pRoom = sRoomDef.rRoom.lock()) { - collectRenderEntriesIntoRenderList(pRoom.get(), entries, bIgnoreVisibility); + if (bCameraInsideRoom) + { + // Store new room name + stats.currentRoom = QString::fromStdString(pRoom->getName()); + } + + collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); } acceptedRooms.emplace_back(sRoomDef); @@ -1018,12 +1039,12 @@ namespace widgets const gamelib::scene::SceneObject* pDynRoot = it->get(); using R = gamelib::scene::SceneObject::EVisitResult; - pDynRoot->visitChildren([&entries, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { if (pObject->getName().ends_with("_LOCATIONS.zip")) return R::VR_NEXT; // Collect everything inside - collectRenderEntriesIntoRenderList(pObject.get(), entries, false); + collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); return R::VR_NEXT; }); } @@ -1039,7 +1060,7 @@ namespace widgets }); } - void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, bool bIgnoreVisibility) // NOLINT(*-no-recursion) + void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility) // NOLINT(*-no-recursion) { const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getPosition(); @@ -1111,14 +1132,9 @@ namespace widgets // Get model const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + gamelib::BoundingBox modelWorldBoundingBox = gamelib::BoundingBox::toWorld(model.boundingBox, mWorldTransform); - glm::vec4 vModelBboxMin { model.boundingBox.min, 1.0f }; - glm::vec4 vModelBboxMax { model.boundingBox.max, 1.0f }; - - vModelBboxMin = vModelBboxMin * mWorldTransform; - vModelBboxMax = vModelBboxMax * mWorldTransform; - - if (m_camera.canSeeObject(glm::vec3(vModelBboxMin), glm::vec3(vModelBboxMax))) { + if (m_camera.canSeeObject(glm::vec3(modelWorldBoundingBox.min), glm::vec3(modelWorldBoundingBox.max))) { // Add bounding box to render list if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { // Need to add mesh @@ -1142,6 +1158,9 @@ namespace widgets material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; } + // increase allowed objects count + stats.allowedObjects++; + // Add each 'mesh' into render list for (int iMeshIdx = 0; iMeshIdx < model.meshes.size(); iMeshIdx++) { const auto &mesh = model.meshes[iMeshIdx]; @@ -1250,6 +1269,11 @@ namespace widgets } } } + else + { + // Increase rejected objects + stats.rejectedObjects++; + } } // Visit others @@ -1257,7 +1281,7 @@ namespace widgets { if (auto g = child.lock()) { - collectRenderEntriesIntoRenderList(g.get(), entries, bIgnoreVisibility); + collectRenderEntriesIntoRenderList(g.get(), entries, stats, bIgnoreVisibility); } } } @@ -1442,15 +1466,7 @@ namespace widgets { // Nice, collision mesh was found! Just use it as source for bbox of ZROOM auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; - - glm::vec4 vMin { sBoundingBox.min, 1.0f }; - glm::vec4 vMax { sBoundingBox.max, 1.0f }; - glm::mat4 mWorldTransform = pCollisionMesh->getWorldTransform(); - - vMin = vMin * mWorldTransform; - vMax = vMax * mWorldTransform; - - d.vBoundingBox = gamelib::BoundingBox(glm::vec3(vMin), glm::vec3(vMax)); + d.vBoundingBox = gamelib::BoundingBox::toWorld(sBoundingBox, pCollisionMesh->getWorldTransform()); return; } } @@ -1469,15 +1485,7 @@ namespace widgets if (m_resources->m_modelsCache.contains(iPrimId)) { auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; - - glm::vec4 vMin { sBoundingBox.min, 1.0f }; - glm::vec4 vMax { sBoundingBox.max, 1.0f }; - glm::mat4 mWorldTransform = pObject->getWorldTransform(); - - vMin = vMin * mWorldTransform; - vMax = vMax * mWorldTransform; - - gamelib::BoundingBox vWorldBoundingBox = { glm::vec3(vMin.x, vMin.y, vMin.z), glm::vec3(vMax.x, vMax.y, vMax.z) }; + gamelib::BoundingBox vWorldBoundingBox = gamelib::BoundingBox::toWorld(sBoundingBox, pObject->getWorldTransform()); if (!bBboxInited) { diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 84f7d7f..9825a8c 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -11,6 +11,8 @@ #include +#include + #include "LoadSceneProgressDialog.h" #include "ViewTexturesDialog.h" @@ -84,6 +86,7 @@ public slots: void onLevelAssetsLoadFailed(const QString& reason); void onSceneObjectPropertyChanged(const gamelib::scene::SceneObject* geom); void onTextureChanged(uint32_t textureIndex); + void onSceneFramePresented(const widgets::RenderStats& stats); private: // UI @@ -92,6 +95,7 @@ public slots: // Custom QLabel* m_operationLabel; QLabel* m_operationCommentLabel; + QLabel* m_renderStatsLabel; QProgressBar* m_operationProgress; // Models diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index d096e88..7478c3c 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -83,12 +83,14 @@ void BMEditMainWindow::initStatusBar() m_operationProgress = new QProgressBar(statusBar()); m_operationLabel = new QLabel(statusBar()); m_operationCommentLabel = new QLabel(statusBar()); + m_renderStatsLabel = new QLabel(statusBar()); resetStatusToDefault(); statusBar()->insertWidget(0, m_operationLabel); statusBar()->insertWidget(1, m_operationProgress); statusBar()->insertWidget(2, m_operationCommentLabel); + statusBar()->insertWidget(3, m_renderStatsLabel); } void BMEditMainWindow::initSearchInput() @@ -528,6 +530,17 @@ void BMEditMainWindow::onTextureChanged(uint32_t textureIndex) ui->sceneGLView->reloadTexture(textureIndex); } +void BMEditMainWindow::onSceneFramePresented(const widgets::RenderStats& stats) +{ + const int iApproxFPS = static_cast(std::floorf(1.f / stats.fFrameTime)); + + m_renderStatsLabel->setText(QString("ROOM: %1 | Visible objects: %2 | Rejected objects: %3 | FPS: %4") + .arg(stats.currentRoom) + .arg(stats.allowedObjects) + .arg(stats.rejectedObjects) + .arg(iApproxFPS)); +} + void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); @@ -631,6 +644,7 @@ void BMEditMainWindow::resetStatusToDefault() { m_operationLabel->setText("Progress: "); m_operationCommentLabel->setText("(No active operation)"); + m_renderStatsLabel->setText("[No render stats]"); m_operationProgress->setValue(0); } @@ -667,6 +681,7 @@ void BMEditMainWindow::initSceneTree() connect(ui->sceneGLView, &widgets::SceneRenderWidget::resourcesReady, this, &BMEditMainWindow::onLevelAssetsLoaded); connect(ui->sceneGLView, &widgets::SceneRenderWidget::resourceLoadFailed, this, &BMEditMainWindow::onLevelAssetsLoadFailed); + connect(ui->sceneGLView, &widgets::SceneRenderWidget::frameReady, this, &BMEditMainWindow::onSceneFramePresented); } void BMEditMainWindow::initProperties() diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index ba8f82f..33d798d 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -14,17 +14,13 @@ BMEdit - + - - - - - Qt::ClickFocus - - - - + + + Qt::ClickFocus + + diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index 679210b..63703fc 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace gamelib @@ -17,5 +18,7 @@ namespace gamelib void expand(const BoundingBox& another); bool contains(const glm::vec3& vPoint) const; + + static BoundingBox toWorld(const BoundingBox& source, const glm::mat4& mTransform); }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp index e6a5e60..e1d9cf1 100644 --- a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp +++ b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp @@ -29,4 +29,33 @@ bool BoundingBox::contains(const glm::vec3& vPoint) const return vPoint.x >= min.x && vPoint.x <= max.x && vPoint.y >= min.y && vPoint.y <= max.y && vPoint.z >= min.z && vPoint.z <= max.z; +} + +BoundingBox BoundingBox::toWorld(const BoundingBox& source, const glm::mat4& mTransform) +{ + glm::vec3 vMin = source.min; + glm::vec3 vMax = source.max; + + glm::vec3 avVertices[8]; + avVertices[0] = vMin; + avVertices[1] = glm::vec3(vMax.x, vMin.y, vMin.z); + avVertices[2] = glm::vec3(vMin.x, vMax.y, vMin.z); + avVertices[3] = glm::vec3(vMax.x, vMax.y, vMin.z); + avVertices[4] = glm::vec3(vMin.x, vMin.y, vMax.z); + avVertices[5] = glm::vec3(vMax.x, vMin.y, vMax.z); + avVertices[6] = glm::vec3(vMin.x, vMax.y, vMax.z); + avVertices[7] = vMax; + + BoundingBox result {}; + result.min = glm::vec3(mTransform * glm::vec4(avVertices[0], 1.f)); + result.max = result.min; + + for (int i = 1; i < 8; i++) + { + glm::vec3 vTransformed = glm::vec3(mTransform * glm::vec4(avVertices[i], 1.f)); + result.min = glm::min(result.min, vTransformed); + result.max = glm::max(result.max, vTransformed); + } + + return result; } \ No newline at end of file From 722b634e6cc5d7c5a0b8b7d0b437bfa4e32e800c Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 23 Dec 2023 11:04:37 +0300 Subject: [PATCH 39/80] Fixed bug with wrong left/right movement --- BMEdit/Editor/Source/Render/Camera.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 06bbd58..54e2b56 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -65,8 +65,8 @@ namespace render if (movementMask > 0) { if (movementMask & CM_FORWARD) m_vPosition += m_vLookDirection * fVelocity; if (movementMask & CM_BACKWARD) m_vPosition -= m_vLookDirection * fVelocity; - if (movementMask & CM_LEFT) m_vPosition -= m_vRight * fVelocity; - if (movementMask & CM_RIGHT) m_vPosition += m_vRight * fVelocity; + if (movementMask & CM_LEFT) m_vPosition += m_vRight * fVelocity; + if (movementMask & CM_RIGHT) m_vPosition -= m_vRight * fVelocity; update(); } From 32f5af9457fa93d13631bca49b1dfebd01869224 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 23 Dec 2023 11:45:46 +0300 Subject: [PATCH 40/80] Fixed issue with alpha objects on M13 --- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 84f4626..aa66249 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -216,8 +216,8 @@ namespace widgets if (!m_renderList.empty()) { - auto onlyNonAlpha = [](const render::RenderEntry& entry) -> bool { return !entry.material.renderState.isAlphaTestEnabled(); }; - auto onlyAlpha = [](const render::RenderEntry& entry) -> bool { return entry.material.renderState.isAlphaTestEnabled(); }; + auto onlyNonAlpha = [](const render::RenderEntry& entry) -> bool { return !entry.material.renderState.isAlphaTestEnabled() && !entry.material.renderState.isBlendEnabled(); }; + auto onlyAlpha = [](const render::RenderEntry& entry) -> bool { return entry.material.renderState.isAlphaTestEnabled() || entry.material.renderState.isBlendEnabled(); }; // 2 pass rendering: first render only non-alpha objects if (m_renderMode & RenderMode::RM_NON_ALPHA_OBJECTS) @@ -1212,6 +1212,12 @@ namespace widgets material.renderState = binder.renderStates[0]; } + if (!material.renderState.isEnabled()) + { + // unable to see disabled material instance + continue; + } + // Resolve & store textures std::fill(material.textures.begin(), material.textures.end(), kInvalidResource); @@ -1316,11 +1322,9 @@ namespace widgets break; case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: gapi->glBlendFunc(GL_ONE, GL_ONE); - gapi->glEnable(GL_ALPHA_TEST); break; case gamelib::mat::MATBlendMode::BM_ADD: gapi->glBlendFunc(GL_ONE, GL_ONE); - gapi->glEnable(GL_ALPHA_TEST); break; default: // Do nothing From 4c3275845bc6bd1400ccce86031b84a16e67ab12 Mon Sep 17 00:00:00 2001 From: DronCode Date: Mon, 25 Dec 2023 09:20:26 +0300 Subject: [PATCH 41/80] [PRE-PORTAL] Just backup before major changes in rendering pipeline --- Assets/g1/ZROOM.json | 6 +- .../Include/Widgets/SceneRenderWidget.h | 22 ++++ .../Source/Widgets/SceneRenderWidget.cpp | 120 ++++++++++++++++-- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 4 + BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 23 +++- 5 files changed, 162 insertions(+), 13 deletions(-) diff --git a/Assets/g1/ZROOM.json b/Assets/g1/ZROOM.json index cb20387..faf0ea4 100644 --- a/Assets/g1/ZROOM.json +++ b/Assets/g1/ZROOM.json @@ -8,12 +8,14 @@ "parent": "ZTreeGroup", "properties": [ { - "name": "property_6C", + "name": "iNeighboursCount", + "__comment__": "I'm not sure about that. 6C and 84 sometimes same", "offset": 108, "typename": "PRPOpCode.Int32" }, { - "name": "property_84", + "name": "iExitsCount", + "__comment__": "I'm not sure about that. 6C and 84 sometimes same", "offset": 132, "typename": "PRPOpCode.Int8" }, diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 8e4ece5..b5bdac0 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -114,6 +114,8 @@ namespace widgets void invalidateRenderList(); void buildRoomCache(); + void resetLastRoom(); + void updateCameraRoomAttachment(); private: // Data @@ -156,11 +158,31 @@ namespace widgets struct RoomDef { + enum class ELocation : int { + eUNDEFINED = 0, + eOUTSIDE = 1, + eINSIDE = 2, + eBOTH = 3, + }; + + /** + * @brief Weak pointer to entity which represent room + */ gamelib::scene::SceneObject::Ref rRoom {}; + + /** + * @brief World space bounding box which cover whole room. Typically it's been built from collision box, but sometimes it could be a expanded bbox (expanded by children objects) + */ gamelib::BoundingBox vBoundingBox {}; + + /** + * @brief Type of room location. Seee ELocation.json for details + */ + ELocation eLocation { ELocation::eUNDEFINED }; }; std::list m_rooms {}; + const RoomDef* m_pLastRoom { nullptr }; private: void computeRoomBoundingBox(RoomDef& d); diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index aa66249..9d22f07 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -364,6 +364,7 @@ namespace widgets invalidateRenderList(); resetViewMode(); resetRenderMode(); + resetLastRoom(); } } @@ -376,6 +377,7 @@ namespace widgets m_pLevel = nullptr; m_bFirstMouseQuery = true; invalidateRenderList(); + resetLastRoom(); resetViewMode(); resetRenderMode(); repaint(); @@ -1003,28 +1005,60 @@ namespace widgets } else { + // Need to update room before + updateCameraRoomAttachment(); + + if (!m_pLastRoom) + return; // Do nothing + std::list acceptedRooms {}; // Render static + // SEE 0047B190 (ZViewSpace::CheckExitsInRoom) for details. Current solution is piece of crap for (const auto& sRoomDef : m_rooms) { - const bool bCameraInsideRoom = sRoomDef.vBoundingBox.contains(m_camera.getPosition()); + bool bAcceptedAnything = false; - if (bCameraInsideRoom || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) + if (auto eRoomLoc = m_pLastRoom->eLocation; eRoomLoc == RoomDef::ELocation::eBOTH || eRoomLoc == RoomDef::ELocation::eUNDEFINED) { - if (auto pRoom = sRoomDef.rRoom.lock()) + // Render if we've inside or can see + if (sRoomDef.vBoundingBox.contains(m_camera.getPosition()) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) { - if (bCameraInsideRoom) + // Allowed to render + if (auto pRoom = sRoomDef.rRoom.lock()) { - // Store new room name - stats.currentRoom = QString::fromStdString(pRoom->getName()); + collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); + bAcceptedAnything = true; } - - collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); } + } + else + { + const bool bBothInside = eRoomLoc == RoomDef::ELocation::eINSIDE && sRoomDef.eLocation == RoomDef::ELocation::eINSIDE; + const bool bBothOutside = eRoomLoc == RoomDef::ELocation::eOUTSIDE && sRoomDef.eLocation == RoomDef::ELocation::eOUTSIDE; - acceptedRooms.emplace_back(sRoomDef); + if (bBothInside || bBothOutside) + { + // Allowed to render + if (auto pRoom = sRoomDef.rRoom.lock()) + { + collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); + bAcceptedAnything = true; + } + } } + + // TODO: Here we need to check if we skipped this room we need to check all gates and if we able to see room through gate - include it too + // if (!bAcceptedAnything) + } + + if (auto pRoom = m_pLastRoom->rRoom.lock()) + { + stats.currentRoom = QString::fromStdString(pRoom->getName()); + } + else + { + stats.currentRoom = {}; } // Render dynamic @@ -1075,7 +1109,7 @@ namespace widgets } // Don't draw invisible things - if (bInvisible && !bIgnoreVisibility) + if (bInvisible) return; if (g_bannedObjectIds.contains(std::string_view{geom->getName()})) @@ -1531,6 +1565,8 @@ namespace widgets { auto& room = m_rooms.emplace_back(); room.rRoom = pObject; + room.eLocation = RoomDef::ELocation::eUNDEFINED; + // ZBackdrop always has maximum possible size to see it from any point of the world constexpr float kMinPoint = std::numeric_limits::min(); constexpr float kMaxPoint = std::numeric_limits::max(); @@ -1558,6 +1594,17 @@ namespace widgets auto& room = m_rooms.emplace_back(); room.rRoom = pObject; + //room.eLocation + const auto iLocation = pObject->getProperties().getObject("Location", 0); + if (iLocation >= 0 && iLocation <= 3) + { + room.eLocation = static_cast(iLocation); + } + else + { + assert(false && "Unknown room type, room will be ignored in optimisations loop"); + } + // Compute room dimensions computeRoomBoundingBox(room); @@ -1569,4 +1616,57 @@ namespace widgets }); } } + + void SceneRenderWidget::resetLastRoom() + { + m_pLastRoom = nullptr; + } + + void SceneRenderWidget::updateCameraRoomAttachment() + { + if (!m_pLevel || m_rooms.empty()) + { + m_pLastRoom = nullptr; + return; + } + + // First of all check that we out of our current room + if (m_pLastRoom) + { + if (m_pLastRoom->vBoundingBox.contains(m_camera.getPosition())) + return; // Do nothing + + // Reject current room + m_pLastRoom = nullptr; + } + + std::list foundInRooms {}; + for (const auto& sRoom : m_rooms) + { + if (sRoom.vBoundingBox.contains(m_camera.getPosition())) + { + foundInRooms.emplace_back(&sRoom); + } + } + + if (foundInRooms.empty()) + return; // Out of rooms + + // Then need to sort found rooms list. Firstly we need to have eINSIDE rooms + foundInRooms.sort([this](const RoomDef* a, const RoomDef* b) { + if (a->eLocation == b->eLocation) + { + const float d1 = glm::distance(m_camera.getPosition(), a->vBoundingBox.getCenter()); + const float d2 = glm::distance(m_camera.getPosition(), b->vBoundingBox.getCenter()); + + return d1 < d2; + } + + return static_cast(a->eLocation) > static_cast(b->eLocation); + }); + + // Now, use first found room + // NOTE: Maybe we've better to check that top room is preferable for us? Idk + m_pLastRoom = (*foundInRooms.begin()); + } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 7478c3c..bfef57d 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -271,6 +271,7 @@ void BMEditMainWindow::onLevelLoadSuccess() // Load controllers index ui->geomControllers->switchToDefaults(); + ui->geomControllers->resetGeom(); // Export action ui->menuExport->setEnabled(true); @@ -289,6 +290,7 @@ void BMEditMainWindow::onLevelLoadFailed(const QString &reason) QMessageBox::warning(this, QString("Failed to load level"), QString("Error occurred during level load process:\n%1").arg(reason)); m_operationCommentLabel->setText(QString("Failed to open level '%1'").arg(reason)); m_operationProgress->setValue(0); + //TODO: Need to reset global state properly! } void BMEditMainWindow::onLevelLoadProgressChanged(int totalPercentsProgress, const QString ¤tOperationTag) @@ -330,6 +332,7 @@ void BMEditMainWindow::onSelectedSceneObject(const gamelib::scene::SceneObject* ui->sceneObjectName->setText(QString::fromStdString(selectedSceneObject->getName())); ui->sceneObjectTypeCombo->setEnabled(true); ui->sceneObjectTypeCombo->setCurrentText(QString::fromStdString(selectedSceneObject->getType()->getName())); + ui->geomInstanceId->setText(QString("%1").arg(selectedSceneObject->getGeomInfo().getInstanceId())); ui->sceneGLView->setSelectedObject(const_cast(selectedSceneObject)); @@ -350,6 +353,7 @@ void BMEditMainWindow::onDeselectedSceneObject() ui->sceneObjectTypeCombo->setEnabled(false); ui->sceneObjectName->clear(); + ui->geomInstanceId->setText("0"); ui->geomControllers->resetGeom(); m_sceneObjectPropertiesModel->resetGeom(); diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index 33d798d..a7d0224 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -229,7 +229,7 @@ Properties - + @@ -269,6 +269,27 @@ + + + + + + Instance ID: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + From 5b3669109b94d3b79d77b16c646417f823252cb7 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 13 Apr 2024 12:09:56 +0300 Subject: [PATCH 42/80] Fixed bug with wrong matrix multiplication order (door bug) --- BMEdit/Editor/Include/Render/Camera.h | 5 + BMEdit/Editor/Include/Render/GlacierVertex.h | 2 +- BMEdit/Editor/Include/Render/Model.h | 2 + .../Editor/Include/Widgets/BitSetViewWidget.h | 46 +++++ .../Include/Widgets/SceneRenderWidget.h | 14 +- BMEdit/Editor/Source/Render/Camera.cpp | 11 + .../Source/Widgets/BitSetViewWidget.cpp | 190 ++++++++++++++++++ .../Source/Widgets/SceneRenderWidget.cpp | 184 +++++++++++++++-- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 17 ++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 80 ++++++-- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 35 ++++ .../Include/GameLib/GMS/GMSGeomEntity.h | 6 +- .../Include/GameLib/GMS/Room/ZRoomDefs.h | 58 ++++++ BMEdit/GameLib/Include/GameLib/Level.h | 16 +- .../GameLib/Include/GameLib/OCT/OCTEntries.h | 24 +-- BMEdit/GameLib/Include/GameLib/Plane.h | 73 +++++++ BMEdit/GameLib/Include/GameLib/Span.h | 1 + .../Source/GameLib/GMS/GMSGeomEntity.cpp | 18 +- .../Source/GameLib/GMS/Room/ZRoomDefs.cpp | 39 ++++ BMEdit/GameLib/Source/GameLib/Level.cpp | 63 ++++-- .../GameLib/Source/GameLib/OCT/OCTEntries.cpp | 26 +-- BMEdit/GameLib/Source/GameLib/Plane.cpp | 73 +++++++ .../Source/GameLib/Scene/SceneObject.cpp | 17 +- 23 files changed, 912 insertions(+), 88 deletions(-) create mode 100644 BMEdit/Editor/Include/Widgets/BitSetViewWidget.h create mode 100644 BMEdit/Editor/Source/Widgets/BitSetViewWidget.cpp create mode 100644 BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h create mode 100644 BMEdit/GameLib/Include/GameLib/Plane.h create mode 100644 BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/Plane.cpp diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index 9b9c891..e0b6a69 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -2,6 +2,9 @@ #include +#include +#include + #include #include #include @@ -57,6 +60,8 @@ namespace render void handleKeyboardMovement(CameraMovementMask movementMask = CameraMovementMaskValues::CM_FORWARD, float dt = kDefaultDt); void processMouseMovement(float xoffset, float yoffset, float dt = kDefaultDt); [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax) const; + [[nodiscard]] bool canSeeObject(const gamelib::BoundingBox& bbox) const; + [[nodiscard]] bool canSeeObject(const gamelib::Plane& plane) const; private: void update(); diff --git a/BMEdit/Editor/Include/Render/GlacierVertex.h b/BMEdit/Editor/Include/Render/GlacierVertex.h index adcecde..38cbc90 100644 --- a/BMEdit/Editor/Include/Render/GlacierVertex.h +++ b/BMEdit/Editor/Include/Render/GlacierVertex.h @@ -19,7 +19,7 @@ namespace render { glm::vec3 vPos {}; - SimpleVertex(); + SimpleVertex() = default; SimpleVertex(const glm::vec3& v1) : vPos(v1) {} static const VertexFormatDescription g_FormatDescription; diff --git a/BMEdit/Editor/Include/Render/Model.h b/BMEdit/Editor/Include/Render/Model.h index f224147..9adbe54 100644 --- a/BMEdit/Editor/Include/Render/Model.h +++ b/BMEdit/Editor/Include/Render/Model.h @@ -27,6 +27,8 @@ namespace render GLuint glTextureId { kInvalidResource }; /// Render OpenGL texture resource handle uint16_t materialId { 0 }; /// Id of material from Glacier mesh (just copy) uint8_t variationId { 0 }; // Id of variation (some meshes could be attached to abstract 'variation' so each variation could be interpreted as 'group of meshes') + std::optional renderTopology; // Override topology (for debug only) + std::optional defaultColor; // Override default material color (for debug only) int trianglesCount { 0 }; diff --git a/BMEdit/Editor/Include/Widgets/BitSetViewWidget.h b/BMEdit/Editor/Include/Widgets/BitSetViewWidget.h new file mode 100644 index 0000000..5456af1 --- /dev/null +++ b/BMEdit/Editor/Include/Widgets/BitSetViewWidget.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include + + +namespace widgets +{ + class BitSetViewWidget : public QWidget + { + Q_OBJECT + public: + using Bits = std::vector>; // Bit name to bit index + + BitSetViewWidget(QWidget* parent = nullptr); + + void setPossibleValues(const Bits& values); + + [[nodiscard]] const Bits& getPossibleValues() const; + [[nodiscard]] Bits getChecked() const; + [[nodiscard]] Bits getUnchecked() const; + + void setValue(uint32_t value); + void resetValue(); + [[nodiscard]] uint32_t getValue() const; + + void reset(); + + signals: + void valueChanged(uint32_t value); + + private: + void clearView(); + void buildView(); + void updateView(); + + private: + uint32_t m_intRepr { 0u }; // it's 32, for 64 write new widget please + Bits m_allowedValues {}; + QScopedPointer m_pLayout { nullptr }; + std::vector> m_bitNrToCheckBox {}; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index b5bdac0..93c491f 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -17,6 +18,7 @@ #include #include +#include #include @@ -113,7 +115,7 @@ namespace widgets void invalidateRenderList(); - void buildRoomCache(); + void buildRoomCache(QOpenGLFunctions_3_3_Core* glFunctions); void resetLastRoom(); void updateCameraRoomAttachment(); @@ -179,6 +181,16 @@ namespace widgets * @brief Type of room location. Seee ELocation.json for details */ ELocation eLocation { ELocation::eUNDEFINED }; + + /** + * @brief Information about room exits + */ + std::vector aExists {}; + + /** + * @brief Room eXit geom boxes + */ + std::unique_ptr mExitsDebugModel { nullptr }; }; std::list m_rooms {}; diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 54e2b56..e068746 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -95,6 +95,17 @@ namespace render return m_sFrustum.isBoxVisible(vMin, vMax); } + bool Camera::canSeeObject(const gamelib::BoundingBox& bbox) const + { + return m_sFrustum.isBoxVisible(bbox.min, bbox.max); + } + + bool Camera::canSeeObject(const gamelib::Plane& plane) const + { + // I'm pretty sure that creating temporary bounding box around plane is faster solution than lookup for plane to plane lookup. + return canSeeObject(plane.makeBoundingBox()); + } + void Camera::update() { glm::vec3 vFront { .0f }; diff --git a/BMEdit/Editor/Source/Widgets/BitSetViewWidget.cpp b/BMEdit/Editor/Source/Widgets/BitSetViewWidget.cpp new file mode 100644 index 0000000..0969cc3 --- /dev/null +++ b/BMEdit/Editor/Source/Widgets/BitSetViewWidget.cpp @@ -0,0 +1,190 @@ +#include +#include + + +namespace widgets +{ + void clearLayout(QLayout* pLayout) // NOLINT(*-no-recursion) + { + if (!pLayout) + return; + + QLayoutItem* pItem = nullptr; + + while ((pItem = pLayout->takeAt(0))) + { + if (pItem->layout()) + { + clearLayout(pItem->layout()); + delete pItem->layout(); + } + + if (pItem->widget()) + { + delete pItem->widget(); + } + + delete pItem; + } + } + + BitSetViewWidget::BitSetViewWidget(QWidget *parent) : QWidget(parent) + { + m_pLayout.reset(new QVBoxLayout(this)); + setLayout(m_pLayout.get()); + } + + void BitSetViewWidget::setPossibleValues(const BitSetViewWidget::Bits &values) + { + m_allowedValues = values; + m_intRepr = 0; + m_bitNrToCheckBox.clear(); + +#ifdef QT_DEBUG + // static check + { + QSet knowUrName; + + for (const auto& [name, _] : values) + { + if (knowUrName.contains(name)) + { + Q_ASSERT_X(false, __FILE__, "KEY DUPLICATE!!!"); + return; + } + + // store + knowUrName.insert(name); + } + } +#endif + + // rebuild view + if (!m_allowedValues.empty()) + { + clearView(); + } + + buildView(); + // no need to call updateView here because no value - no view + } + + const BitSetViewWidget::Bits& BitSetViewWidget::getPossibleValues() const + { + return m_allowedValues; + } + + BitSetViewWidget::Bits BitSetViewWidget::getChecked() const + { + Bits r {}; + + for (const auto& [name, idx] : m_allowedValues) + { + if ((m_intRepr & (1 << idx)) != 0) + { + r.emplace_back(name, idx); + } + } + + return r; + } + + BitSetViewWidget::Bits BitSetViewWidget::getUnchecked() const + { + Bits r {}; + + for (const auto& [name, idx] : m_allowedValues) + { + if ((m_intRepr & (1 << idx)) == 0) + { + r.emplace_back(name, idx); + } + } + + return r; + } + + void BitSetViewWidget::setValue(uint32_t value) + { + if (value != m_intRepr) + { + m_intRepr = value; + updateView(); + + emit valueChanged(m_intRepr); + } + } + + void BitSetViewWidget::resetValue() + { + setValue(0u); + } + + uint32_t BitSetViewWidget::getValue() const + { + return m_intRepr; + } + + void BitSetViewWidget::reset() + { + m_allowedValues.clear(); + m_bitNrToCheckBox.clear(); + clearView(); + } + + void BitSetViewWidget::clearView() + { + clearLayout(m_pLayout.get()); + } + + void BitSetViewWidget::buildView() + { + for (const auto& [name, bitIdx] : m_allowedValues) + { + auto* pCheckBox = new QCheckBox(name, this); + m_bitNrToCheckBox.emplace_back(name, pCheckBox); + m_pLayout->addWidget(pCheckBox); + + connect(pCheckBox, &QCheckBox::toggled, [this, bitIdx](bool checked) { + const auto oldValue = m_intRepr; + if (checked) + { + m_intRepr |= (1 << bitIdx); + } + else + { + m_intRepr &= ~(1 << bitIdx); + } + + if (oldValue != m_intRepr) + { + emit valueChanged(m_intRepr); + } + }); + } + } + + void BitSetViewWidget::updateView() + { + int index = 0; + for (const auto& [name, pCurrentCheckBox] : m_bitNrToCheckBox) + { + if (!pCurrentCheckBox) + { + Q_ASSERT(pCurrentCheckBox != nullptr); + ++index; + continue; + } + + { + QSignalBlocker blocker { pCurrentCheckBox }; + const auto bitIdx = m_allowedValues[index].second; + + const bool bExpectedValue = static_cast((m_intRepr & (1 << bitIdx))); + pCurrentCheckBox->setChecked(bExpectedValue); + } + + ++index; + } + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 9d22f07..1c1aadc 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -8,10 +8,12 @@ #include #include #include + #include #include #include #include +#include #include #include @@ -415,6 +417,9 @@ namespace widgets if (!m_pLevel) return; + auto flags = sceneObject->getGeomInfo().getGeomFlags(); + bool isBit4Set = flags & (1 << 4); + if (m_pSelectedSceneObject != sceneObject && sceneObject != nullptr) { m_pSelectedSceneObject = sceneObject; @@ -792,7 +797,7 @@ namespace widgets } // Then load rooms cache - buildRoomCache(); + buildRoomCache(glFunctions); qDebug() << "All models (" << m_pLevel->getLevelGeometry()->primitives.models.size() << ") are loaded & ready to use!"; m_eState = ELevelState::LS_COMPILE_SHADERS; @@ -1048,6 +1053,12 @@ namespace widgets } } + if (bAcceptedAnything) + { + // Add room debug teleport points + + } + // TODO: Here we need to check if we skipped this room we need to check all gates and if we able to see room through gate - include it too // if (!bAcceptedAnything) } @@ -1084,6 +1095,44 @@ namespace widgets } } + // Add debug stuff + for (const auto& sRoomDef : m_rooms) + { + if (!sRoomDef.mExitsDebugModel) + continue; + + for (const auto& sMesh : sRoomDef.mExitsDebugModel->meshes) + { + render::RenderEntry &exitPlaneRenderEntry = entries.emplace_back(); + + // Render params + exitPlaneRenderEntry.iPrimitiveId = 0; + exitPlaneRenderEntry.iMeshIndex = 0; + exitPlaneRenderEntry.iTrianglesNr = 0; + exitPlaneRenderEntry.renderTopology = sMesh.renderTopology.value_or(render::RenderTopology::RT_TRIANGLES); + + // World params + exitPlaneRenderEntry.vPosition = glm::vec3(.0f); + exitPlaneRenderEntry.mWorldTransform = glm::mat4(1.f); + exitPlaneRenderEntry.mLocalOriginalTransform = glm::mat3(1.f); + exitPlaneRenderEntry.pMesh = const_cast(&sMesh); + + // Material + render::RenderEntry::Material &material = exitPlaneRenderEntry.material; + constexpr float kOpacity = 0.1f; + material.vDiffuseColor = sMesh.defaultColor.value_or(glm::vec4(1.f, 1.f, 0.f, kOpacity)); + material.renderState = gamelib::mat::MATRenderState("#BMEDIT/OPACITY_AREA", + true, true, true, false, false, + kOpacity, + 0.f, + 255, + gamelib::mat::MATCullMode::CM_DontCare, + gamelib::mat::MATBlendMode::BM_ADD, + gamelib::mat::MATValU()); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + } + } + // Post sorting entries.sort([&camera](const render::RenderEntry& a, const render::RenderEntry& b) -> bool { // Check distance to camera @@ -1480,6 +1529,7 @@ namespace widgets void SceneRenderWidget::computeRoomBoundingBox(RoomDef& d) { + // It's better to use data from OCT !!! using R = gamelib::scene::SceneObject::EVisitResult; if (auto pRoom = d.rRoom.lock()) @@ -1544,10 +1594,13 @@ namespace widgets } } - void SceneRenderWidget::buildRoomCache() + void SceneRenderWidget::buildRoomCache(QOpenGLFunctions_3_3_Core* glFunctions) { m_rooms.clear(); + // Save pointer to BUF file + const auto bufFileView = m_pLevel->getStaticBuffer(); + // Now we need to find ZGROUP who ends by _LOCATIONS and lookup from this ZGROUP inside auto locationsIt = std::find_if( m_pLevel->getSceneObjects().begin(), @@ -1582,7 +1635,7 @@ namespace widgets const gamelib::scene::SceneObject::Ptr& pNewRoot = *locationsIt; using R = gamelib::scene::SceneObject::EVisitResult; - pNewRoot->visitChildren([this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + pNewRoot->visitChildren([this, bufFileView](const gamelib::scene::SceneObject::Ptr& pObject) -> R { if (!pObject) { return R::VR_STOP_ALL; @@ -1605,6 +1658,25 @@ namespace widgets assert(false && "Unknown room type, room will be ignored in optimisations loop"); } + //room.iExitsCount, room.ExitOffsets (Precache room exit boxes) + const auto iExistsCount = pObject->getProperties().getObject("iExitsCount", 0); + const auto iExistsOffset = pObject->getProperties().getObject("ExitOffsets", 0); + if (iExistsCount > 0 && iExistsOffset > 0) + { + room.aExists.reserve(iExistsCount); + + // Take a slice of data + constexpr auto kEntrySize = static_cast(sizeof(gamelib::gms::room::ZRoomExit)); + const auto roomExists = bufFileView.slice(iExistsOffset, kEntrySize * iExistsCount);; + + for (int i = 0; i < iExistsCount; i++) + { + const auto exit = roomExists.slice((i * kEntrySize), kEntrySize); + auto& exitDef = room.aExists.emplace_back(); + gamelib::gms::room::ZRoomExit::deserialize(exitDef, exit); + } + } + // Compute room dimensions computeRoomBoundingBox(room); @@ -1615,6 +1687,58 @@ namespace widgets return R::VR_CONTINUE; }); } + +#if 0 // Uncomment on debug planes + // Upload debug geom + for (auto& room : m_rooms) + { + if (room.aExists.empty()) + continue; + + room.mExitsDebugModel = std::make_unique(); + + for (const auto& sExit : room.aExists) + { + gamelib::Plane sPlane { sExit.unkVec0, sExit.unkVec1, sExit.unkVec2, sExit.unkVec3 }; + + // Plane + { + auto& exitMesh = room.mExitsDebugModel->meshes.emplace_back(); + exitMesh.glTextureId = render::kInvalidResource; + exitMesh.materialId = 0; + exitMesh.renderTopology = RenderTopology::RT_TRIANGLES; + + std::vector aVertices; + std::vector aIndices; + + sPlane.toTriangles(std::back_inserter(aVertices), std::back_inserter(aIndices)); + + exitMesh.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + } + + // Normal vector direction + { + auto& exitMeshNormalView = room.mExitsDebugModel->meshes.emplace_back(); + exitMeshNormalView.glTextureId = render::kInvalidResource; + exitMeshNormalView.materialId = 0; + exitMeshNormalView.renderTopology = RenderTopology::RT_LINES; + exitMeshNormalView.defaultColor = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); + + const float kVecLength = sPlane.getSize() * 0.2f; //20% of size + + std::vector aVertices { + sPlane.getCenter(), + sPlane.getCenter() + (sPlane.getNormal() * kVecLength), + sPlane.getCenter() - (sPlane.getNormal() * kVecLength) + }; + + std::vector aIndices { 0, 1, 0, 2 }; + + exitMeshNormalView.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + } + } + } +#endif } void SceneRenderWidget::resetLastRoom() @@ -1630,6 +1754,7 @@ namespace widgets return; } +#if 1 // First of all check that we out of our current room if (m_pLastRoom) { @@ -1653,20 +1778,53 @@ namespace widgets return; // Out of rooms // Then need to sort found rooms list. Firstly we need to have eINSIDE rooms - foundInRooms.sort([this](const RoomDef* a, const RoomDef* b) { - if (a->eLocation == b->eLocation) - { - const float d1 = glm::distance(m_camera.getPosition(), a->vBoundingBox.getCenter()); - const float d2 = glm::distance(m_camera.getPosition(), b->vBoundingBox.getCenter()); - - return d1 < d2; - } - - return static_cast(a->eLocation) > static_cast(b->eLocation); + foundInRooms.sort([](const RoomDef* a, const RoomDef* b) { + return static_cast(a->eLocation) < static_cast(b->eLocation); }); // Now, use first found room // NOTE: Maybe we've better to check that top room is preferable for us? Idk m_pLastRoom = (*foundInRooms.begin()); +#else + // Lookup into RMI/RMC files and try to locate us. + // We need to convert camera world coordinates into room world space coordinates + const auto* pRooms = m_pLevel->getLevelRooms(); + const glm::vec3 vCameraWorldPos = m_camera.getPosition(); + const auto vCameraOutsideRoomPos = pRooms->outside.worldToRoom(vCameraWorldPos); + const auto vCameraInsideRoomPos = pRooms->inside.worldToRoom(vCameraWorldPos); + + uint32_t iInsideRoomIdx = 0xFFFFFFFFu; + uint32_t iOutsideRoomIdx = 0xFFFFFFFFu; + + for (int i = 0; i < pRooms->outside.objects.size(); i++) + { + const auto& currentRoom = pRooms->outside.objects[i]; + + if ( + vCameraOutsideRoomPos.x >= currentRoom.vMin.x && vCameraOutsideRoomPos.y >= currentRoom.vMin.y && vCameraOutsideRoomPos.z >= currentRoom.vMin.z && + vCameraOutsideRoomPos.x <= currentRoom.vMax.x && vCameraOutsideRoomPos.y <= currentRoom.vMax.y && vCameraOutsideRoomPos.z <= currentRoom.vMax.z + ) + { + iOutsideRoomIdx = currentRoom.gameObjectREF; + break; + } + } + + for (int i = 0; i < pRooms->inside.objects.size(); i++) + { + const auto& currentRoom = pRooms->inside.objects[i]; + + if ( + vCameraInsideRoomPos.x >= currentRoom.vMin.x && vCameraInsideRoomPos.y >= currentRoom.vMin.y && vCameraInsideRoomPos.z >= currentRoom.vMin.z && + vCameraInsideRoomPos.x <= currentRoom.vMax.x && vCameraInsideRoomPos.y <= currentRoom.vMax.y && vCameraInsideRoomPos.z <= currentRoom.vMax.z + ) + { + iInsideRoomIdx = currentRoom.gameObjectREF; + break; + } + } + + printf("VFFX\n"); +#endif } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index bfef57d..48e00e7 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -340,11 +340,28 @@ void BMEditMainWindow::onSelectedSceneObject(const gamelib::scene::SceneObject* ui->geomControllers->setGeom(const_cast(selectedSceneObject)); ui->geomControllers->switchToFirstController(); + + // Show coli bits and other ZGEOM stuff + ui->coliBitsRepr->setPossibleValues({ + { "Bit 0", 0 }, + { "Bit 1", 1 }, + { "Bit 2", 2 }, + { "Bit 3", 3 }, + { "Bit 4", 4 }, + { "Bit 5", 5 }, + { "Bit 6", 6 }, + { "Bit 7", 7 } + }); + + ui->coliBitsRepr->setValue(selectedSceneObject->getGeomInfo().getColiBits()); + ui->coliBitsRepr->setEnabled(true); } void BMEditMainWindow::onDeselectedSceneObject() { ui->sceneGLView->resetSelectedObject(); + ui->coliBitsRepr->reset(); + ui->coliBitsRepr->setEnabled(false); if (!m_sceneObjectPropertiesModel) { diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index a7d0224..ee7a796 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1548 - 790 + 1499 + 979 @@ -29,7 +29,7 @@ 0 0 - 1548 + 1499 22 @@ -221,7 +221,7 @@ - + 0 @@ -229,7 +229,7 @@ Properties - + @@ -291,18 +291,29 @@ - - - Properties: + + + Qt::Horizontal - - - QAbstractItemView::SingleSelection - - + + + + + Properties: + + + + + + + QAbstractItemView::SingleSelection + + + + @@ -316,6 +327,43 @@ + + + ZGEOM + + + + + + + + + 16777215 + 20 + + + + Coli Bits (8): + + + + + + + false + + + + 100 + 100 + + + + + + + + @@ -464,6 +512,12 @@ QOpenGLWidget
Widgets/SceneRenderWidget.h
+ + widgets::BitSetViewWidget + QWidget +
Widgets/BitSetViewWidget.h
+ 1 +
diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index 63703fc..23fc1a0 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -2,6 +2,9 @@ #include #include +#include + +#include namespace gamelib @@ -20,5 +23,37 @@ namespace gamelib bool contains(const glm::vec3& vPoint) const; static BoundingBox toWorld(const BoundingBox& source, const glm::mat4& mTransform); + + template + void toLines(TOutVertexIterator outVertexIt, TOutIndexIterator outIndexIt) + { + // Compute vertices + std::array aVertices { + glm::vec3{min.x, min.y, min.z}, glm::vec3{min.x, min.y, max.z}, + glm::vec3{min.x, max.y, min.z}, glm::vec3{min.x, max.y, max.z}, + glm::vec3{max.x, min.y, min.z}, glm::vec3{max.x, min.y, max.z}, + glm::vec3{max.x, max.y, min.z}, glm::vec3{max.x, max.y, max.z} + }; + + // Compute indices + std::array aIndices { + 0, 1, 1, 3, 3, 2, 2, 0, + 4, 5, 5, 7, 7, 6, 6, 4, + 0, 4, 1, 5, 2, 6, 3, 7 + }; + + for (const auto& vVertex : aVertices) + { + using TP = typename TOutVertexIterator::container_type::value_type; + TP vProxy; + vProxy.vPos = vVertex; + (*outVertexIt++) = std::move(vProxy); + } + + for (const auto& iIndex : aIndices) + { + (*outIndexIt++) = iIndex; + } + } }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h b/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h index ffe0027..831f366 100644 --- a/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h +++ b/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h @@ -32,6 +32,7 @@ namespace gamelib::gms [[nodiscard]] bool isInheritedOfGeom() const; [[nodiscard]] bool isRootOfGroup() const; [[nodiscard]] uint32_t getRelativeDepthLevel() const; + [[nodiscard]] uint32_t getGeomFlags() const; static void deserialize(GMSGeomEntity &entity, ZBio::ZBinaryReader::BinaryReader *gmsBinaryReader, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader); @@ -52,7 +53,10 @@ namespace gamelib::gms uint32_t m_unk10 { }; uint32_t m_typeId { }; uint32_t m_unk18 { }; - uint32_t m_coliBits { }; + uint8_t m_coliBits {}; // +1C + uint8_t m_unk1D {}; + uint8_t m_unk1E {}; + uint8_t m_unk1F {}; uint32_t m_unk20 { }; uint32_t m_unk24 { }; uint32_t m_unk28 { }; diff --git a/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h b/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h new file mode 100644 index 0000000..5c098b7 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h @@ -0,0 +1,58 @@ +/** +* This file contains definition of internal structure which defines information about room "eXit" segments +*/ +#pragma once + +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace gamelib::gms::room +{ +#pragma pack(push, 1) + /** + * @struct ZRoomExit + * @brief This structure represents all information about single room exit. See ZROOM.json properties iExitsCount and ExitOffsets + * @note Total size of this structure must be 0x38 bytes for PC version (main supported game version) + */ + struct ZRoomExit + { + glm::vec3 unkVec0; + glm::vec3 unkVec1; + glm::vec3 unkVec2; + glm::vec3 unkVec3; + uint32_t iRoomREF; // Instance ID, just lookup over entities on scene to locate it + uint8_t unk1C; + uint8_t unk1D; + uint8_t unkFlags; // bit#2 - always required, bit#4 - means processed remap or not (setup by engine in ZROOM::RemapRefs) + uint8_t unk1F; + + static void deserialize(ZRoomExit& eXit, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader); + static void deserialize(ZRoomExit& eXit, const Span& byteBufferSpan); + }; +#pragma pack(pop) + +#pragma pack(push, 1) + /** + * @struct ZRoomNeighbor + * @brief Describes room neighbor. Declared in BUF file. See ZROOM.json properties iNeighboursCount and NeighborsOffset + */ + struct ZRoomNeighbor + { + uint32_t rRoomREF; // Instance ID (not owner) + uint32_t unk4; // Means some 'amount of objects'. Maybe amount of 'neighbors'? + uint32_t unk8; // Some offset inside BUF. Maybe 'neighbors'? + + static void deserialize(ZRoomNeighbor& neighbor, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader); + }; +#pragma pack(pop) + + + static_assert(sizeof(ZRoomExit) == 0x38); +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index abc8ac8..20de052 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -60,8 +60,8 @@ namespace gamelib std::vector objects{}; std::vector ubs{}; - [[nodiscard]] glm::vec3 worldToRoom(const glm::vec3& vWorld) const; - [[nodiscard]] glm::vec3 roomToWorld(const glm::vec3& vTree) const; + [[nodiscard]] glm::i16vec3 worldToRoom(const glm::vec3& vWorld) const; + [[nodiscard]] glm::vec3 roomToWorld(const glm::i16vec3& vTree) const; }; RoomGroup outside {}; @@ -85,11 +85,17 @@ namespace gamelib [[nodiscard]] LevelGeometry* getLevelGeometry(); [[nodiscard]] const LevelMaterials* getLevelMaterials() const; [[nodiscard]] LevelMaterials* getLevelMaterials(); + [[nodiscard]] const LevelRooms* getLevelRooms() const; + [[nodiscard]] LevelRooms* getLevelRooms(); [[nodiscard]] const std::vector& getSceneObjects() const; [[nodiscard]] scene::SceneObject::Ptr getSceneObjectByGEOMREF(const std::string& path) const; + [[nodiscard]] scene::SceneObject::Ptr getSceneObjectByInstanceID(std::uint32_t instanceID) const; + + [[nodiscard]] Span getStaticBuffer() const; + void dumpAsset(io::AssetKind assetKind, std::vector &outBuffer) const; void forEachObjectOfType(const std::string& objectTypeName, const std::function& pred) const; @@ -116,6 +122,12 @@ namespace gamelib LevelMaterials m_levelMaterials; LevelRooms m_levelRooms; + struct BUF + { + std::unique_ptr data { nullptr }; + std::int64_t size{ 0 }; + } m_buf; + // Managed objects std::vector m_sceneObjects {}; }; diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h index 02d7fd2..1554578 100644 --- a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h @@ -2,6 +2,7 @@ #include #include +#include namespace ZBio::ZBinaryReader @@ -46,25 +47,16 @@ namespace gamelib::oct struct OCTUnknownBlock { uint32_t unk0 { 0 }; - float unk4 { 0.f }; - float unk8 { 0.f }; - float unkC { 0.f }; - float unk10 { 0.f }; - float unk14 { 0.f }; - float unk18 { 0.f }; - float unk1C { 0.f }; - float unk20 { 0.f }; - float unk24 { 0.f }; - float unk28 { 0.f }; - float unk2C { 0.f }; - float unk30 { 0.f }; + + glm::mat3 vUnk4 {}; + glm::vec3 vUnk28 {}; + float unk34 { 0.f }; float unk38 { 0.f }; float unk3C { 0.f }; - float unk40 { 0.f }; - float unk44 { 0.f }; - float unk48 { 0.f }; - float unk4C { 0.f }; + + glm::vec3 vUnk40 {}; + uint32_t unk4C { 0 }; float unk50 { 0.f }; static void deserialize(OCTUnknownBlock& block, ZBio::ZBinaryReader::BinaryReader* binaryReader); diff --git a/BMEdit/GameLib/Include/GameLib/Plane.h b/BMEdit/GameLib/Include/GameLib/Plane.h new file mode 100644 index 0000000..528a641 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/Plane.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + + +namespace gamelib +{ + template + concept THasPosition = requires(T t) + { + { t.vPos }; + }; + + class Plane + { + public: + Plane(); + Plane(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3); + + /** + * @brief Calculates bounding box which cover plane + */ + BoundingBox makeBoundingBox() const; + + /** + * @brief Calculates normal vector of plane + * @note This method using easy calculation method, so it can return wrong result for hard planes + */ + glm::vec3 getNormal() const; + + /** + * @return -getNormal + * @note See comment to getNormal for details + */ + glm::vec3 getBiNormal() const; + + /** + * @return center of plane (point, not a vector) + */ + glm::vec3 getCenter() const; + + /** + * @return Size of plane as maximum distance between points + */ + float getSize() const; + + template + void toTriangles(TOutVertexIterator outVertexIt, TOutIndexIterator outIndexIt) requires (THasPosition) + { + // Push vertices + for (const auto& v : m_aVertices) + { + using TP = typename TOutVertexIterator::container_type::value_type; + TP vProxy; + vProxy.vPos = v; + (*outVertexIt++) = std::move(vProxy); + } + + // Push indices + (*outIndexIt++) = 0; + (*outIndexIt++) = 1; + (*outIndexIt++) = 2; + (*outIndexIt++) = 0; + (*outIndexIt++) = 2; + (*outIndexIt++) = 3; + } + + private: + glm::vec3 m_aVertices[4]; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/Span.h b/BMEdit/GameLib/Include/GameLib/Span.h index 267cce8..d3832ab 100644 --- a/BMEdit/GameLib/Include/GameLib/Span.h +++ b/BMEdit/GameLib/Include/GameLib/Span.h @@ -49,6 +49,7 @@ namespace gamelib [[nodiscard]] bool empty() const { return m_size == 0; } [[nodiscard]] int64_t size() const { return m_size; } [[nodiscard]] T* data() { return const_cast(m_data); } + [[nodiscard]] const T* data() const { return m_data; } [[nodiscard]] explicit operator bool() const noexcept { return (m_data != nullptr); } diff --git a/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp b/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp index 69c845d..cc12540 100644 --- a/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp +++ b/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp @@ -23,7 +23,7 @@ namespace gamelib::gms uint32_t GMSGeomEntity::getColiBits() const { - return m_coliBits; + return static_cast(m_coliBits); } uint32_t GMSGeomEntity::getParentGeomIndex() const @@ -38,7 +38,7 @@ namespace gamelib::gms bool GMSGeomEntity::isRootOfGroup() const { - return (m_geomFlags & 0x1000000) != 0u; + return (m_geomFlags & 0x1000000) != 0u; // bit #24 } uint32_t GMSGeomEntity::getRelativeDepthLevel() const @@ -46,6 +46,15 @@ namespace gamelib::gms return (m_geomFlags >> 25u); } + uint32_t GMSGeomEntity::getGeomFlags() const + { + // D1 = 50776 = 0000 0000 0000 0000 1100 0110 0101 1000 + // D2 = 50808 = 0000 0000 0000 0000 1100 0110 0111 1000 + // #5 + + return m_geomFlags; + } + void GMSGeomEntity::deserialize(GMSGeomEntity &entity, ZBio::ZBinaryReader::BinaryReader *gmsBinaryReader, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader) { // Read name @@ -70,7 +79,10 @@ namespace gamelib::gms entity.m_unk18 = gmsBinaryReader->read(); // Read coliBits - entity.m_coliBits = gmsBinaryReader->read(); + entity.m_coliBits = gmsBinaryReader->read(); + entity.m_unk1D = gmsBinaryReader->read(); + entity.m_unk1E = gmsBinaryReader->read(); + entity.m_unk1F = gmsBinaryReader->read(); // Read unk20, 24, 28, 2C entity.m_unk20 = gmsBinaryReader->read(); diff --git a/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp b/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp new file mode 100644 index 0000000..80ddaf6 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + + +namespace gamelib::gms::room +{ + void ZRoomExit::deserialize(ZRoomExit& eXit, ZBio::ZBinaryReader::BinaryReader* bufBinaryReader) + { + bufBinaryReader->read(glm::value_ptr(eXit.unkVec0), 3); + bufBinaryReader->read(glm::value_ptr(eXit.unkVec1), 3); + bufBinaryReader->read(glm::value_ptr(eXit.unkVec2), 3); + bufBinaryReader->read(glm::value_ptr(eXit.unkVec3), 3); + eXit.iRoomREF = bufBinaryReader->read(); + eXit.unk1C = bufBinaryReader->read(); + eXit.unk1D = bufBinaryReader->read(); + eXit.unkFlags = bufBinaryReader->read(); + eXit.unk1F = bufBinaryReader->read(); + } + + void ZRoomExit::deserialize(ZRoomExit& eXit, const Span& byteBufferSpan) + { + if (byteBufferSpan.size() < sizeof(ZRoomExit)) + { + assert(byteBufferSpan.size() >= sizeof(ZRoomExit) && "Too small span buffer"); + return; + } + + ZBio::ZBinaryReader::BinaryReader binaryReader(reinterpret_cast(byteBufferSpan.data()), byteBufferSpan.size()); + ZRoomExit::deserialize(eXit, &binaryReader); + } + + void ZRoomNeighbor::deserialize(ZRoomNeighbor& neighbor, ZBio::ZBinaryReader::BinaryReader* bufBinaryReader) + { + neighbor.rRoomREF = bufBinaryReader->read(); + neighbor.unk4 = bufBinaryReader->read(); + neighbor.unk8 = bufBinaryReader->read(); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 98cd1d3..2d27d86 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -17,21 +19,27 @@ namespace gamelib { - glm::vec3 LevelRooms::RoomGroup::worldToRoom(const glm::vec3& vWorld) const + glm::i16vec3 LevelRooms::RoomGroup::worldToRoom(const glm::vec3& vWorld) const { - return { - (vWorld.x - header.vWorldOrigin.x) * header.fWorldScale + 32768.0f, - (vWorld.y - header.vWorldOrigin.y) * header.fWorldScale + 32768.0f, - (vWorld.z - header.vWorldOrigin.z) * header.fWorldScale + 32768.0f - }; + glm::i16vec3 vR { 0 }; + const float* pfvSrc = glm::value_ptr(vWorld); + const float* pfvOrigin = glm::value_ptr(header.vWorldOrigin); + int16_t* pvDst = glm::value_ptr(vR); + + for (int i = 0; i < 3; i++) + { + pvDst[i] = static_cast((pfvSrc[i] - pfvOrigin[i]) * header.fWorldScale + 32768.f); + } + + return vR; } - glm::vec3 LevelRooms::RoomGroup::roomToWorld(const glm::vec3& vRoom) const + glm::vec3 LevelRooms::RoomGroup::roomToWorld(const glm::i16vec3& vRoom) const { return { - (vRoom.x - 32768.0f) / header.fWorldScale + header.vWorldOrigin.x, - (vRoom.y - 32768.0f) / header.fWorldScale + header.vWorldOrigin.y, - (vRoom.z - 32768.0f) / header.fWorldScale + header.vWorldOrigin.z + (static_cast(vRoom.x) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.x, + (static_cast(vRoom.y) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.y, + (static_cast(vRoom.z) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.z }; } @@ -144,6 +152,16 @@ namespace gamelib return &m_levelMaterials; } + const LevelRooms* Level::getLevelRooms() const + { + return &m_levelRooms; + } + + LevelRooms* Level::getLevelRooms() + { + return &m_levelRooms; + } + const std::vector &Level::getSceneObjects() const { return m_sceneObjects; @@ -188,6 +206,23 @@ namespace gamelib return object; } + scene::SceneObject::Ptr Level::getSceneObjectByInstanceID(std::uint32_t instanceID) const + { + // It's lazy method. Just walk over all entities and check InstanceID + for (const auto& entity : getSceneObjects()) + { + if (entity->getGeomInfo().getInstanceId() == instanceID) + return entity; + } + + return nullptr; + } + + Span Level::getStaticBuffer() const + { + return m_buf.data ? Span(m_buf.data.get(), m_buf.size) : nullptr; + } + void Level::dumpAsset(io::AssetKind assetKind, std::vector &outBuffer) const { if (assetKind == io::AssetKind::PROPERTIES) @@ -195,6 +230,7 @@ namespace gamelib scene::SceneObjectPropertiesDumper dumper; dumper.dump(this, &outBuffer); } + else assert(false && "Unsupported"); } void Level::forEachObjectOfType(const std::string& objectTypeName, const std::function& pred) const @@ -255,7 +291,6 @@ namespace gamelib bool Level::loadLevelScene() { int64_t gmsFileSize = 0; - int64_t bufFileSize = 0; // Load raw data auto gmsFileBuffer = m_assetProvider->getAsset(io::AssetKind::SCENE, gmsFileSize); @@ -264,14 +299,14 @@ namespace gamelib return false; } - auto bufFileBuffer = m_assetProvider->getAsset(io::AssetKind::BUFFER, bufFileSize); - if (!bufFileBuffer || !bufFileSize) + m_buf.data = m_assetProvider->getAsset(io::AssetKind::BUFFER, m_buf.size); + if (!m_buf.data || !m_buf.size) { return false; } gms::GMSReader reader; - if (!reader.parse(&m_sceneProperties.header, gmsFileBuffer.get(), gmsFileSize, bufFileBuffer.get(), bufFileSize)) + if (!reader.parse(&m_sceneProperties.header, gmsFileBuffer.get(), gmsFileSize, m_buf.data.get(), m_buf.size)) { return false; } diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp index 3b3569e..08a1a06 100644 --- a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp @@ -29,26 +29,18 @@ namespace gamelib::oct void OCTUnknownBlock::deserialize(gamelib::oct::OCTUnknownBlock& block, ZBio::ZBinaryReader::BinaryReader* binaryReader) { - block.unk0 = binaryReader->read(); - block.unk4 = binaryReader->read(); - block.unk8 = binaryReader->read(); - block.unkC = binaryReader->read(); - block.unk10 = binaryReader->read(); - block.unk14 = binaryReader->read(); - block.unk18 = binaryReader->read(); - block.unk1C = binaryReader->read(); - block.unk20 = binaryReader->read(); - block.unk24 = binaryReader->read(); - block.unk28 = binaryReader->read(); - block.unk2C = binaryReader->read(); - block.unk30 = binaryReader->read(); + block.unk0 = binaryReader->read(); + + binaryReader->read(glm::value_ptr(block.vUnk4), 9); + binaryReader->read(glm::value_ptr(block.vUnk28), 3); + block.unk34 = binaryReader->read(); block.unk38 = binaryReader->read(); block.unk3C = binaryReader->read(); - block.unk40 = binaryReader->read(); - block.unk44 = binaryReader->read(); - block.unk48 = binaryReader->read(); - block.unk4C = binaryReader->read(); + + binaryReader->read(glm::value_ptr(block.vUnk40), 3); + + block.unk4C = binaryReader->read(); block.unk50 = binaryReader->read(); } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Plane.cpp b/BMEdit/GameLib/Source/GameLib/Plane.cpp new file mode 100644 index 0000000..5fa9588 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/Plane.cpp @@ -0,0 +1,73 @@ +#include + + +namespace gamelib +{ + Plane::Plane() = default; + + Plane::Plane(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) + { + m_aVertices[0] = v0; + m_aVertices[1] = v1; + m_aVertices[2] = v2; + m_aVertices[3] = v3; + } + + BoundingBox Plane::makeBoundingBox() const + { + glm::vec3 vMin = m_aVertices[0]; + glm::vec3 vMax = m_aVertices[0]; + + for (int i = 1; i < 4; i++) + { + vMin = glm::min(vMin, m_aVertices[i]); + vMax = glm::min(vMax, m_aVertices[i]); + } + + return { vMin, vMax }; + } + + glm::vec3 Plane::getNormal() const + { + glm::vec3 u = m_aVertices[1] - m_aVertices[0]; + glm::vec3 v = m_aVertices[2] - m_aVertices[0]; + glm::vec3 vNormal = glm::cross(u, v); + return glm::normalize(vNormal); + } + + glm::vec3 Plane::getBiNormal() const + { + return -getNormal(); + } + + glm::vec3 Plane::getCenter() const + { + glm::vec3 center { .0f }; + + for (const auto& vPoint : m_aVertices) { + center += vPoint; + } + + center /= 4.0f; + return center; + } + + float Plane::getSize() const + { + float fMaxDistance = .0f; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (i == j) + continue; + + float fDistance = glm::distance(m_aVertices[i], m_aVertices[j]); + fMaxDistance = std::max(fMaxDistance, fDistance); + } + } + + return fMaxDistance; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index dfc8bf0..bbca6e0 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -182,17 +182,20 @@ namespace gamelib::scene glm::mat4 SceneObject::getWorldTransform() const { - const SceneObject* current = this; - glm::mat4 mWorldMatrix = glm::mat4(1.f); + // if bit#4 is set we need to take self transform and multiply by parent transform (we are relative to parent) + // otherwise return only self transform + glm::mat4 mWorldMartix = getLocalTransform(); - while (current) - { - mWorldMatrix = mWorldMatrix * current->getLocalTransform(); +// const bool bHasBit2 = getGeomInfo().getGeomFlags() & (1 << 2); +// const bool bHasBit4 = getGeomInfo().getGeomFlags() & (1 << 4); +// const bool bHasBit5 = getGeomInfo().getGeomFlags() & (1 << 5); - current = current->getParent().expired() ? nullptr : current->getParent().lock().get(); + if (!getParent().expired()) + { + mWorldMartix = getParent().lock()->getWorldTransform() * mWorldMartix; } - return mWorldMatrix; + return mWorldMartix; } void SceneObject::visitChildren(const std::function& pred) const From f3d2546dad553e68c3560ae56f20f0be075ff65e Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 13 Apr 2024 17:41:38 +0300 Subject: [PATCH 43/80] Added partial rendering and mid-scene lookup for portals --- BMEdit/Editor/Include/Render/Camera.h | 1 + BMEdit/Editor/Include/Render/Frustum.h | 6 + .../Include/Widgets/SceneRenderWidget.h | 26 +- BMEdit/Editor/Source/Render/Camera.cpp | 9 + .../Source/Widgets/SceneRenderWidget.cpp | 369 ++++++++++++------ BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 14 + BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 21 + .../Include/GameLib/GMS/Room/ZRoomDefs.h | 12 +- BMEdit/GameLib/Include/GameLib/Plane.h | 6 + .../Source/GameLib/GMS/Room/ZRoomDefs.cpp | 16 +- BMEdit/GameLib/Source/GameLib/Plane.cpp | 11 + .../Source/GameLib/Scene/SceneObject.cpp | 6 - 12 files changed, 356 insertions(+), 141 deletions(-) diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index e0b6a69..b06f186 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -62,6 +62,7 @@ namespace render [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax) const; [[nodiscard]] bool canSeeObject(const gamelib::BoundingBox& bbox) const; [[nodiscard]] bool canSeeObject(const gamelib::Plane& plane) const; + [[nodiscard]] bool canSeePlanePartial(const gamelib::Plane& plane) const; private: void update(); diff --git a/BMEdit/Editor/Include/Render/Frustum.h b/BMEdit/Editor/Include/Render/Frustum.h index 8bae8ab..90dc819 100644 --- a/BMEdit/Editor/Include/Render/Frustum.h +++ b/BMEdit/Editor/Include/Render/Frustum.h @@ -39,6 +39,12 @@ namespace render return true; } + [[nodiscard]] bool isPointVisible(const glm::vec3& vPoint) const + { + // stupid + return isBoxVisible(vPoint, vPoint); + } + private: std::array m_vPlanes; }; diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 93c491f..956bfb8 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -80,6 +80,12 @@ namespace widgets void reloadTexture(uint32_t textureIndex); + bool shouldRenderPortals() const; + void setShouldRenderPortals(bool bVal); + + bool shouldRenderRoomBoundingBox() const; + void setShouldRenderRoomBoundingBox(bool bVal); + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); @@ -117,7 +123,13 @@ namespace widgets void buildRoomCache(QOpenGLFunctions_3_3_Core* glFunctions); void resetLastRoom(); - void updateCameraRoomAttachment(); + + /** + * @brief Method trying to find a new room for current camera (if camera not in that room of bRejectLastResult is true) + * @param stats - reference to render stats object (method updates room name if new room presented) + * @param bRejectLastResult - pass true to reject current room and try to find a new one + */ + void updateCameraRoomAttachment(RenderStats& stats, bool bRejectLastResult = true); private: // Data @@ -141,6 +153,8 @@ namespace widgets ELevelState m_eState { ELevelState::LS_NONE }; QPoint m_mouseLastPosition {}; bool m_bFirstMouseQuery { true }; + bool m_bRenderPortals { false }; // Should we render portals between rooms (debug view) + bool m_bRenderRoomBoundingBox { false }; // Should we render room bounding box (of all rooms) // View mode enum class EViewMode : uint8_t @@ -187,10 +201,20 @@ namespace widgets */ std::vector aExists {}; + /** + * @brief Information about neighbour rooms + */ + std::vector aNeighbours {}; + /** * @brief Room eXit geom boxes */ std::unique_ptr mExitsDebugModel { nullptr }; + + /** + * @brief Room bounding box debug model + */ + std::unique_ptr mBBoxModel { nullptr }; }; std::list m_rooms {}; diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index e068746..6d027f1 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -106,6 +106,15 @@ namespace render return canSeeObject(plane.makeBoundingBox()); } + bool Camera::canSeePlanePartial(const gamelib::Plane& plane) const + { + // if at least 1 point visible this method will return true + return m_sFrustum.isPointVisible(plane.getPoint(0)) || + m_sFrustum.isPointVisible(plane.getPoint(1)) || + m_sFrustum.isPointVisible(plane.getPoint(2)) || + m_sFrustum.isPointVisible(plane.getPoint(3)); + } + void Camera::update() { glm::vec3 vFront { .0f }; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 1c1aadc..726d944 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -476,6 +476,34 @@ namespace widgets m_resources->m_invalidatedTextures.insert(textureIndex); } + bool SceneRenderWidget::shouldRenderPortals() const + { + return m_bRenderPortals; + } + + void SceneRenderWidget::setShouldRenderPortals(bool bVal) + { + if (m_bRenderPortals != bVal) + { + m_bRenderPortals = bVal; + repaint(); + } + } + + bool SceneRenderWidget::shouldRenderRoomBoundingBox() const + { + return m_bRenderRoomBoundingBox; + } + + void SceneRenderWidget::setShouldRenderRoomBoundingBox(bool bVal) + { + if (m_bRenderRoomBoundingBox != bVal) + { + m_bRenderRoomBoundingBox = bVal; + repaint(); + } + } + void SceneRenderWidget::onRedrawRequested() { if (m_pLevel) @@ -1000,14 +1028,53 @@ namespace widgets void SceneRenderWidget::collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility) { - if (m_pLevel->getSceneObjects().empty()) + if (!m_pLevel || m_pLevel->getSceneObjects().empty()) + { return; + } + + // Update room + updateCameraRoomAttachment(stats); if (pRootGeom != m_pLevel->getSceneObjects()[0].get() || m_rooms.empty() /* on some levels m_rooms cache could be not presented! */) { - // Render from specific node + // Render from specific node (no performance optimisations here) collectRenderEntriesIntoRenderList(pRootGeom, entries, stats, bIgnoreVisibility); } + else + { + // Try to render + if (m_pLastRoom) + { + if (!m_pLastRoom->rRoom.expired()) + { + // First of all let's render only current room + collectRenderEntriesIntoRenderList(m_pLastRoom->rRoom.lock().get(), entries, stats, bIgnoreVisibility); + } + + // Let's check if we can see any portal - we need to render that room. + if (!m_pLastRoom->aExists.empty()) + { + // Iterate over all exits and check what exit camera can see right now + for (const auto& eXit : m_pLastRoom->aExists) + { + gamelib::Plane sPlane { eXit.v0, eXit.v1, eXit.v2, eXit.v3 }; + + if (m_camera.canSeePlanePartial(sPlane)) + { + const auto& pRoom = m_pLevel->getSceneObjectByInstanceID(eXit.iRoomREF); + if (pRoom) + { + // We can see another room - save it + collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); + // in theory 1 room is enough, but... idk) + } + } + } + } + } + } +#if 0 // Well optimized variant since this place else { // Need to update room before @@ -1052,15 +1119,6 @@ namespace widgets } } } - - if (bAcceptedAnything) - { - // Add room debug teleport points - - } - - // TODO: Here we need to check if we skipped this room we need to check all gates and if we able to see room through gate - include it too - // if (!bAcceptedAnything) } if (auto pRoom = m_pLastRoom->rRoom.lock()) @@ -1093,43 +1151,102 @@ namespace widgets return R::VR_NEXT; }); } - } + else + { + // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( + using R = gamelib::scene::SceneObject::EVisitResult; + + m_pLevel->getSceneObjects()[0]->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + static const std::array kAllowedTypes = { // only base types + "ZActor", "ZPlayer", "ZItem", "ZItemAmmo" + }; + + for (const auto& sEntBaseName : kAllowedTypes) + { + if (pObject->isInheritedOf(sEntBaseName)) + { + collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); + return R::VR_NEXT; + } + } + return R::VR_CONTINUE; // go deeper + }); + } + } +#endif // Add debug stuff - for (const auto& sRoomDef : m_rooms) + if (m_bRenderPortals || m_bRenderRoomBoundingBox) { - if (!sRoomDef.mExitsDebugModel) - continue; - - for (const auto& sMesh : sRoomDef.mExitsDebugModel->meshes) + for (const auto &sRoomDef : m_rooms) { - render::RenderEntry &exitPlaneRenderEntry = entries.emplace_back(); - - // Render params - exitPlaneRenderEntry.iPrimitiveId = 0; - exitPlaneRenderEntry.iMeshIndex = 0; - exitPlaneRenderEntry.iTrianglesNr = 0; - exitPlaneRenderEntry.renderTopology = sMesh.renderTopology.value_or(render::RenderTopology::RT_TRIANGLES); - - // World params - exitPlaneRenderEntry.vPosition = glm::vec3(.0f); - exitPlaneRenderEntry.mWorldTransform = glm::mat4(1.f); - exitPlaneRenderEntry.mLocalOriginalTransform = glm::mat3(1.f); - exitPlaneRenderEntry.pMesh = const_cast(&sMesh); - - // Material - render::RenderEntry::Material &material = exitPlaneRenderEntry.material; - constexpr float kOpacity = 0.1f; - material.vDiffuseColor = sMesh.defaultColor.value_or(glm::vec4(1.f, 1.f, 0.f, kOpacity)); - material.renderState = gamelib::mat::MATRenderState("#BMEDIT/OPACITY_AREA", - true, true, true, false, false, - kOpacity, - 0.f, - 255, - gamelib::mat::MATCullMode::CM_DontCare, - gamelib::mat::MATBlendMode::BM_ADD, - gamelib::mat::MATValU()); - material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + if (sRoomDef.mExitsDebugModel && m_bRenderPortals) + { + for (const auto &sMesh : sRoomDef.mExitsDebugModel->meshes) + { + render::RenderEntry &exitPlaneRenderEntry = entries.emplace_back(); + + // Render params + exitPlaneRenderEntry.iPrimitiveId = 0; + exitPlaneRenderEntry.iMeshIndex = 0; + exitPlaneRenderEntry.iTrianglesNr = 0; + exitPlaneRenderEntry.renderTopology = sMesh.renderTopology.value_or(render::RenderTopology::RT_TRIANGLES); + + // World params + exitPlaneRenderEntry.vPosition = glm::vec3(.0f); + exitPlaneRenderEntry.mWorldTransform = glm::mat4(1.f); + exitPlaneRenderEntry.mLocalOriginalTransform = glm::mat3(1.f); + exitPlaneRenderEntry.pMesh = const_cast(&sMesh); + + // Material + render::RenderEntry::Material &material = exitPlaneRenderEntry.material; + constexpr float kOpacity = 0.1f; + material.vDiffuseColor = sMesh.defaultColor.value_or(glm::vec4(1.f, 1.f, 0.f, kOpacity)); + material.renderState = gamelib::mat::MATRenderState("#BMEDIT/OPACITY_AREA", + true, true, true, false, false, + kOpacity, + 0.f, + 255, + gamelib::mat::MATCullMode::CM_DontCare, + gamelib::mat::MATBlendMode::BM_ADD, + gamelib::mat::MATValU()); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + } + } + + if (sRoomDef.mBBoxModel && m_bRenderRoomBoundingBox) + { + for (const auto &sMesh : sRoomDef.mBBoxModel->meshes) + { + render::RenderEntry &lineRenderEntry = entries.emplace_back(); + + // Render params + lineRenderEntry.iPrimitiveId = 0; + lineRenderEntry.iMeshIndex = 0; + lineRenderEntry.iTrianglesNr = 0; + lineRenderEntry.renderTopology = sMesh.renderTopology.value_or(render::RenderTopology::RT_LINES); + + // World params + lineRenderEntry.vPosition = glm::vec3(.0f); + lineRenderEntry.mWorldTransform = glm::mat4(1.f); + lineRenderEntry.mLocalOriginalTransform = glm::mat3(1.f); + lineRenderEntry.pMesh = const_cast(&sMesh); + + // Material + render::RenderEntry::Material &material = lineRenderEntry.material; + constexpr float kOpacity = 0.1f; + material.vDiffuseColor = sMesh.defaultColor.value_or(glm::vec4(1.f, 0.f, 0.f, kOpacity)); + material.renderState = gamelib::mat::MATRenderState("#BMEDIT/OPACITY_AREA", + true, true, true, false, false, + kOpacity, + 0.f, + 255, + gamelib::mat::MATCullMode::CM_DontCare, + gamelib::mat::MATBlendMode::BM_ADD, + gamelib::mat::MATValU()); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + } + } } } @@ -1529,7 +1646,6 @@ namespace widgets void SceneRenderWidget::computeRoomBoundingBox(RoomDef& d) { - // It's better to use data from OCT !!! using R = gamelib::scene::SceneObject::EVisitResult; if (auto pRoom = d.rRoom.lock()) @@ -1667,7 +1783,7 @@ namespace widgets // Take a slice of data constexpr auto kEntrySize = static_cast(sizeof(gamelib::gms::room::ZRoomExit)); - const auto roomExists = bufFileView.slice(iExistsOffset, kEntrySize * iExistsCount);; + const auto roomExists = bufFileView.slice(iExistsOffset, kEntrySize * iExistsCount); for (int i = 0; i < iExistsCount; i++) { @@ -1677,6 +1793,24 @@ namespace widgets } } + //room.iNeighboursCount, room.NeighborsOffset + const auto iNeighboursCount = pObject->getProperties().getObject("iNeighboursCount", 0); + const auto iNeighborsOffset = pObject->getProperties().getObject("NeighborsOffset", 0); + if (iNeighboursCount > 0 && iNeighborsOffset > 0) + { + room.aNeighbours.reserve(iNeighboursCount); + + constexpr auto kEntrySize = static_cast(sizeof(gamelib::gms::room::ZRoomNeighbor)); + const auto roomNeighbours = bufFileView.slice(iNeighborsOffset, kEntrySize * iNeighboursCount); + + for (int i = 0; i < iNeighboursCount; i++) + { + const auto neighbour = roomNeighbours.slice((i * kEntrySize), kEntrySize); + auto& neighbourDef = room.aNeighbours.emplace_back(); + gamelib::gms::room::ZRoomNeighbor::deserialize(neighbourDef, neighbour); + } + } + // Compute room dimensions computeRoomBoundingBox(room); @@ -1688,57 +1822,71 @@ namespace widgets }); } -#if 0 // Uncomment on debug planes // Upload debug geom for (auto& room : m_rooms) { - if (room.aExists.empty()) - continue; - - room.mExitsDebugModel = std::make_unique(); - - for (const auto& sExit : room.aExists) + if (!room.aExists.empty()) { - gamelib::Plane sPlane { sExit.unkVec0, sExit.unkVec1, sExit.unkVec2, sExit.unkVec3 }; + room.mExitsDebugModel = std::make_unique(); - // Plane + for (const auto& sExit : room.aExists) { - auto& exitMesh = room.mExitsDebugModel->meshes.emplace_back(); - exitMesh.glTextureId = render::kInvalidResource; - exitMesh.materialId = 0; - exitMesh.renderTopology = RenderTopology::RT_TRIANGLES; + gamelib::Plane sPlane { sExit.v0, sExit.v1, sExit.v2, sExit.v3 }; - std::vector aVertices; - std::vector aIndices; + // Plane + { + auto& exitMesh = room.mExitsDebugModel->meshes.emplace_back(); + exitMesh.glTextureId = render::kInvalidResource; + exitMesh.materialId = 0; + exitMesh.renderTopology = RenderTopology::RT_TRIANGLES; - sPlane.toTriangles(std::back_inserter(aVertices), std::back_inserter(aIndices)); + std::vector aVertices; + std::vector aIndices; - exitMesh.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); - } + sPlane.toTriangles(std::back_inserter(aVertices), std::back_inserter(aIndices)); - // Normal vector direction - { - auto& exitMeshNormalView = room.mExitsDebugModel->meshes.emplace_back(); - exitMeshNormalView.glTextureId = render::kInvalidResource; - exitMeshNormalView.materialId = 0; - exitMeshNormalView.renderTopology = RenderTopology::RT_LINES; - exitMeshNormalView.defaultColor = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); - - const float kVecLength = sPlane.getSize() * 0.2f; //20% of size - - std::vector aVertices { - sPlane.getCenter(), - sPlane.getCenter() + (sPlane.getNormal() * kVecLength), - sPlane.getCenter() - (sPlane.getNormal() * kVecLength) - }; + exitMesh.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + } + + // Normal vector direction + { + auto& exitMeshNormalView = room.mExitsDebugModel->meshes.emplace_back(); + exitMeshNormalView.glTextureId = render::kInvalidResource; + exitMeshNormalView.materialId = 0; + exitMeshNormalView.renderTopology = RenderTopology::RT_LINES; + exitMeshNormalView.defaultColor = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); + + const float kVecLength = sPlane.getSize() * 0.2f; //20% of size + + std::vector aVertices { + sPlane.getCenter(), + sPlane.getCenter() + (sPlane.getNormal() * kVecLength), + sPlane.getCenter() - (sPlane.getNormal() * kVecLength) + }; - std::vector aIndices { 0, 1, 0, 2 }; + std::vector aIndices { 0, 1, 0, 2 }; - exitMeshNormalView.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + exitMeshNormalView.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + } } } + + // Add bounding box model + { + room.mBBoxModel = std::make_unique(); + + auto& bboxMesh = room.mBBoxModel->meshes.emplace_back(); + bboxMesh.glTextureId = render::kInvalidResource; + bboxMesh.materialId = 0; + bboxMesh.renderTopology = RenderTopology::RT_LINES; + + std::vector aVertices; + std::vector aIndices; + + room.vBoundingBox.toLines(std::back_inserter(aVertices), std::back_inserter(aIndices)); + bboxMesh.setup(glFunctions, render::SimpleVertex::g_FormatDescription, aVertices, aIndices, false); + } } -#endif } void SceneRenderWidget::resetLastRoom() @@ -1746,20 +1894,29 @@ namespace widgets m_pLastRoom = nullptr; } - void SceneRenderWidget::updateCameraRoomAttachment() + void SceneRenderWidget::updateCameraRoomAttachment(RenderStats& stats, bool bRejectLastResult) { + stats.currentRoom.clear(); + + if (bRejectLastResult) + { + m_pLastRoom = nullptr; + } + if (!m_pLevel || m_rooms.empty()) { m_pLastRoom = nullptr; return; } -#if 1 // First of all check that we out of our current room if (m_pLastRoom) { if (m_pLastRoom->vBoundingBox.contains(m_camera.getPosition())) + { + stats.currentRoom = QString::fromStdString(m_pLastRoom->rRoom.lock()->getName()); return; // Do nothing + } // Reject current room m_pLastRoom = nullptr; @@ -1779,52 +1936,14 @@ namespace widgets // Then need to sort found rooms list. Firstly we need to have eINSIDE rooms foundInRooms.sort([](const RoomDef* a, const RoomDef* b) { - return static_cast(a->eLocation) < static_cast(b->eLocation); + return static_cast(a->eLocation) > static_cast(b->eLocation); }); // Now, use first found room // NOTE: Maybe we've better to check that top room is preferable for us? Idk m_pLastRoom = (*foundInRooms.begin()); -#else - // Lookup into RMI/RMC files and try to locate us. - // We need to convert camera world coordinates into room world space coordinates - const auto* pRooms = m_pLevel->getLevelRooms(); - const glm::vec3 vCameraWorldPos = m_camera.getPosition(); - const auto vCameraOutsideRoomPos = pRooms->outside.worldToRoom(vCameraWorldPos); - const auto vCameraInsideRoomPos = pRooms->inside.worldToRoom(vCameraWorldPos); - - uint32_t iInsideRoomIdx = 0xFFFFFFFFu; - uint32_t iOutsideRoomIdx = 0xFFFFFFFFu; - - for (int i = 0; i < pRooms->outside.objects.size(); i++) - { - const auto& currentRoom = pRooms->outside.objects[i]; - - if ( - vCameraOutsideRoomPos.x >= currentRoom.vMin.x && vCameraOutsideRoomPos.y >= currentRoom.vMin.y && vCameraOutsideRoomPos.z >= currentRoom.vMin.z && - vCameraOutsideRoomPos.x <= currentRoom.vMax.x && vCameraOutsideRoomPos.y <= currentRoom.vMax.y && vCameraOutsideRoomPos.z <= currentRoom.vMax.z - ) - { - iOutsideRoomIdx = currentRoom.gameObjectREF; - break; - } - } - - for (int i = 0; i < pRooms->inside.objects.size(); i++) - { - const auto& currentRoom = pRooms->inside.objects[i]; - if ( - vCameraInsideRoomPos.x >= currentRoom.vMin.x && vCameraInsideRoomPos.y >= currentRoom.vMin.y && vCameraInsideRoomPos.z >= currentRoom.vMin.z && - vCameraInsideRoomPos.x <= currentRoom.vMax.x && vCameraInsideRoomPos.y <= currentRoom.vMax.y && vCameraInsideRoomPos.z <= currentRoom.vMax.z - ) - { - iInsideRoomIdx = currentRoom.gameObjectREF; - break; - } - } - - printf("VFFX\n"); -#endif + // Save room name + stats.currentRoom = QString::fromStdString(m_pLastRoom->rRoom.lock()->getName()); } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 48e00e7..267dde4 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -130,6 +130,12 @@ void BMEditMainWindow::connectActions() ui->sceneGLView->setRenderMode(newMode); }); + connect(ui->actionRenderMode_Portals, &QAction::toggled, this, [this](bool val) { + ui->sceneGLView->setShouldRenderPortals(val); + }); + connect(ui->actionRenderMode_RenderRoomBoundingBoxes, &QAction::toggled, this, [this](bool val) { + ui->sceneGLView->setShouldRenderRoomBoundingBox(val); + }); } void BMEditMainWindow::connectDockWidgetActions() @@ -244,6 +250,10 @@ void BMEditMainWindow::onLevelLoadSuccess() ui->actionRenderMode_Texture->setChecked(true); ui->actionRenderMode_Wireframe->setEnabled(true); ui->actionRenderMode_Wireframe->setChecked(false); + ui->actionRenderMode_Portals->setEnabled(true); + ui->actionRenderMode_Portals->setChecked(ui->sceneGLView->shouldRenderPortals()); + ui->actionRenderMode_RenderRoomBoundingBoxes->setEnabled(true); + ui->actionRenderMode_RenderRoomBoundingBoxes->setChecked(ui->sceneGLView->shouldRenderRoomBoundingBox()); // Setup models if (m_sceneTreeModel) @@ -421,6 +431,10 @@ void BMEditMainWindow::onCloseLevel() ui->actionRenderMode_Texture->setChecked(true); ui->actionRenderMode_Wireframe->setEnabled(false); ui->actionRenderMode_Wireframe->setChecked(true); + ui->actionRenderMode_Portals->setEnabled(false); + ui->actionRenderMode_Portals->setChecked(ui->sceneGLView->shouldRenderPortals()); + ui->actionRenderMode_RenderRoomBoundingBoxes->setEnabled(false); + ui->actionRenderMode_RenderRoomBoundingBoxes->setChecked(ui->sceneGLView->shouldRenderRoomBoundingBox()); // Disable filtering QSignalBlocker blocker { ui->searchInputField }; diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index ee7a796..d5b0fb4 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -77,6 +77,8 @@ + + @@ -494,6 +496,25 @@ Texture & Wireframe + + + true + + + Render Portals + + + Render Portals + + + + + true + + + Render Room Bounding Boxes + + diff --git a/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h b/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h index 5c098b7..35d43d9 100644 --- a/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h +++ b/BMEdit/GameLib/Include/GameLib/GMS/Room/ZRoomDefs.h @@ -23,10 +23,13 @@ namespace gamelib::gms::room */ struct ZRoomExit { - glm::vec3 unkVec0; - glm::vec3 unkVec1; - glm::vec3 unkVec2; - glm::vec3 unkVec3; + // Plane vertices + glm::vec3 v0; + glm::vec3 v1; + glm::vec3 v2; + glm::vec3 v3; + + // Other data uint32_t iRoomREF; // Instance ID, just lookup over entities on scene to locate it uint8_t unk1C; uint8_t unk1D; @@ -50,6 +53,7 @@ namespace gamelib::gms::room uint32_t unk8; // Some offset inside BUF. Maybe 'neighbors'? static void deserialize(ZRoomNeighbor& neighbor, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader); + static void deserialize(ZRoomNeighbor& neighbor, const Span& byteBufferSpan); }; #pragma pack(pop) diff --git a/BMEdit/GameLib/Include/GameLib/Plane.h b/BMEdit/GameLib/Include/GameLib/Plane.h index 528a641..7d2c726 100644 --- a/BMEdit/GameLib/Include/GameLib/Plane.h +++ b/BMEdit/GameLib/Include/GameLib/Plane.h @@ -41,6 +41,12 @@ namespace gamelib */ glm::vec3 getCenter() const; + /** + * @return point of plane (0 to 3, other index will return (0;0;0) point) + * @param idx - index of point + */ + const glm::vec3& getPoint(size_t idx) const; + /** * @return Size of plane as maximum distance between points */ diff --git a/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp b/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp index 80ddaf6..7447664 100644 --- a/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp +++ b/BMEdit/GameLib/Source/GameLib/GMS/Room/ZRoomDefs.cpp @@ -7,10 +7,10 @@ namespace gamelib::gms::room { void ZRoomExit::deserialize(ZRoomExit& eXit, ZBio::ZBinaryReader::BinaryReader* bufBinaryReader) { - bufBinaryReader->read(glm::value_ptr(eXit.unkVec0), 3); - bufBinaryReader->read(glm::value_ptr(eXit.unkVec1), 3); - bufBinaryReader->read(glm::value_ptr(eXit.unkVec2), 3); - bufBinaryReader->read(glm::value_ptr(eXit.unkVec3), 3); + bufBinaryReader->read(glm::value_ptr(eXit.v0), 3); + bufBinaryReader->read(glm::value_ptr(eXit.v1), 3); + bufBinaryReader->read(glm::value_ptr(eXit.v2), 3); + bufBinaryReader->read(glm::value_ptr(eXit.v3), 3); eXit.iRoomREF = bufBinaryReader->read(); eXit.unk1C = bufBinaryReader->read(); eXit.unk1D = bufBinaryReader->read(); @@ -30,10 +30,16 @@ namespace gamelib::gms::room ZRoomExit::deserialize(eXit, &binaryReader); } - void ZRoomNeighbor::deserialize(ZRoomNeighbor& neighbor, ZBio::ZBinaryReader::BinaryReader* bufBinaryReader) + void ZRoomNeighbor::deserialize(ZRoomNeighbor& neighbor, ZBio::ZBinaryReader::BinaryReader *bufBinaryReader) { neighbor.rRoomREF = bufBinaryReader->read(); neighbor.unk4 = bufBinaryReader->read(); neighbor.unk8 = bufBinaryReader->read(); } + + void ZRoomNeighbor::deserialize(gamelib::gms::room::ZRoomNeighbor& neighbor, const Span& byteBufferSpan) + { + ZBio::ZBinaryReader::BinaryReader binaryReader(reinterpret_cast(byteBufferSpan.data()), byteBufferSpan.size()); + ZRoomNeighbor::deserialize(neighbor, &binaryReader); + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Plane.cpp b/BMEdit/GameLib/Source/GameLib/Plane.cpp index 5fa9588..66eedd5 100644 --- a/BMEdit/GameLib/Source/GameLib/Plane.cpp +++ b/BMEdit/GameLib/Source/GameLib/Plane.cpp @@ -52,6 +52,17 @@ namespace gamelib return center; } + const glm::vec3& Plane::getPoint(size_t idx) const + { + if (idx >= 0 && idx <= 3) + { + return m_aVertices[idx]; + } + + static const glm::vec3 kNull { 0.f }; + return kNull; + } + float Plane::getSize() const { float fMaxDistance = .0f; diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index bbca6e0..e6d423a 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -182,14 +182,8 @@ namespace gamelib::scene glm::mat4 SceneObject::getWorldTransform() const { - // if bit#4 is set we need to take self transform and multiply by parent transform (we are relative to parent) - // otherwise return only self transform glm::mat4 mWorldMartix = getLocalTransform(); -// const bool bHasBit2 = getGeomInfo().getGeomFlags() & (1 << 2); -// const bool bHasBit4 = getGeomInfo().getGeomFlags() & (1 << 4); -// const bool bHasBit5 = getGeomInfo().getGeomFlags() & (1 << 5); - if (!getParent().expired()) { mWorldMartix = getParent().lock()->getWorldTransform() * mWorldMartix; From 17c4211a257cffdf6c8297c1b21a72b7ebf83b6e Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 14 Apr 2024 10:45:12 +0300 Subject: [PATCH 44/80] Small refactoring, added mid-room rendering (base) and fixed few bugs with performance. --- BMEdit/Editor/Include/Render/Camera.h | 1 - BMEdit/Editor/Include/Render/Frustum.h | 6 - .../Include/Widgets/SceneRenderWidget.h | 8 + BMEdit/Editor/Source/Render/Camera.cpp | 19 +- .../Source/Widgets/SceneRenderWidget.cpp | 238 +++++++++--------- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 3 + BMEdit/GameLib/Source/GameLib/BoundingBox.cpp | 21 ++ 7 files changed, 167 insertions(+), 129 deletions(-) diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index b06f186..e0b6a69 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -62,7 +62,6 @@ namespace render [[nodiscard]] bool canSeeObject(const glm::vec3& vMin, const glm::vec3& vMax) const; [[nodiscard]] bool canSeeObject(const gamelib::BoundingBox& bbox) const; [[nodiscard]] bool canSeeObject(const gamelib::Plane& plane) const; - [[nodiscard]] bool canSeePlanePartial(const gamelib::Plane& plane) const; private: void update(); diff --git a/BMEdit/Editor/Include/Render/Frustum.h b/BMEdit/Editor/Include/Render/Frustum.h index 90dc819..8bae8ab 100644 --- a/BMEdit/Editor/Include/Render/Frustum.h +++ b/BMEdit/Editor/Include/Render/Frustum.h @@ -39,12 +39,6 @@ namespace render return true; } - [[nodiscard]] bool isPointVisible(const glm::vec3& vPoint) const - { - // stupid - return isBoxVisible(vPoint, vPoint); - } - private: std::array m_vPlanes; }; diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 956bfb8..6e6dfa4 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -86,6 +86,14 @@ namespace widgets bool shouldRenderRoomBoundingBox() const; void setShouldRenderRoomBoundingBox(bool bVal); + bool isGameObjectInActiveRoom(const gamelib::scene::SceneObject::Ptr& pObject) const; + + int32_t getGameObjectPrimitiveId(const gamelib::scene::SceneObject::Ptr& pObject) const; + int32_t getGameObjectPrimitiveId(const gamelib::scene::SceneObject* pObject) const; + + glm::mat4 getGameObjectTransform(const gamelib::scene::SceneObject::Ptr& pObject) const; + glm::mat4 getGameObjectTransform(const gamelib::scene::SceneObject* pObject) const; + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 6d027f1..3840846 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -102,17 +102,16 @@ namespace render bool Camera::canSeeObject(const gamelib::Plane& plane) const { - // I'm pretty sure that creating temporary bounding box around plane is faster solution than lookup for plane to plane lookup. - return canSeeObject(plane.makeBoundingBox()); - } + const glm::vec3 vU = plane.getPoint(1) - plane.getPoint(0); + const glm::vec3 vV = plane.getPoint(2) - plane.getPoint(0); + const glm::vec3 vNormal = glm::cross(vU, vV); - bool Camera::canSeePlanePartial(const gamelib::Plane& plane) const - { - // if at least 1 point visible this method will return true - return m_sFrustum.isPointVisible(plane.getPoint(0)) || - m_sFrustum.isPointVisible(plane.getPoint(1)) || - m_sFrustum.isPointVisible(plane.getPoint(2)) || - m_sFrustum.isPointVisible(plane.getPoint(3)); + if (glm::dot(vNormal, m_vLookDirection) >= 0) { + return false; + } + + return true; // NOTE: Maybe we really need to check this, but it works well for now + //return m_sFrustum.isPlaneVisible(plane); } void Camera::update() diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 726d944..93f623a 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -504,6 +504,96 @@ namespace widgets } } + bool SceneRenderWidget::isGameObjectInActiveRoom(const gamelib::scene::SceneObject::Ptr& pObject) const + { + if (!m_pLastRoom || !pObject) + { + return false; + } + + //m_pLastRoom->vBoundingBox.intersect + auto primId = getGameObjectPrimitiveId(pObject); + if (primId == 0) + { + return false; + } + + const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + glm::mat4 mWorldTransform = getGameObjectTransform(pObject); + gamelib::BoundingBox modelWorldBoundingBox = gamelib::BoundingBox::toWorld(model.boundingBox, mWorldTransform); + + return m_pLastRoom->vBoundingBox.intersect(modelWorldBoundingBox); + } + + int32_t SceneRenderWidget::getGameObjectPrimitiveId(const gamelib::scene::SceneObject::Ptr& pObject) const + { + if (!pObject) return 0; + + return getGameObjectPrimitiveId(pObject.get()); + } + + int32_t SceneRenderWidget::getGameObjectPrimitiveId(const gamelib::scene::SceneObject* pObject) const + { + if (!pObject) return 0; + + if (pObject->isInheritedOf("ZItem")) // Need support of item ammo & item container here + { + //ZItems has no PrimId. Instead of this they are refs to another geom by path + auto rItemTemplatePath = pObject->getProperties().getObject("rItemTemplate"); + const auto pItemTemplate = m_pLevel->getSceneObjectByGEOMREF(rItemTemplatePath); + + if (pItemTemplate) + { + gamelib::scene::SceneObject::Ptr pItem = nullptr; + + // Item found by path. That's cool! But this is not an item, for item need to ask Ground... object inside + for (const auto& childRef : pItemTemplate->getChildren()) + { + if (auto child = childRef.lock(); child && child->getName().starts_with("Ground")) + { + pItem = child; + break; + } + } + + if (pItem) + { + // Nice! Now we ready to replace primId + return pItem->getProperties().getObject("PrimId"); + } + } + } + + return pObject->getProperties().getObject("PrimId", 0); + } + + glm::mat4 SceneRenderWidget::getGameObjectTransform(const gamelib::scene::SceneObject::Ptr& pObject) const + { + if (!pObject) return glm::mat4(1.f); + return getGameObjectTransform(pObject.get()); + } + + glm::mat4 SceneRenderWidget::getGameObjectTransform(const gamelib::scene::SceneObject* pObject) const + { + if (auto it = m_resources->m_modelTransformCache.find(const_cast(pObject)); it != m_resources->m_modelTransformCache.end()) + { + return it->second; + } + else + { + glm::mat4 mWorldTransform = pObject->getWorldTransform(); + m_resources->m_modelTransformCache[const_cast(pObject)] = mWorldTransform; + return mWorldTransform; + } + + // idk) + return glm::mat4(1.f); + } + + /* + * + */ + void SceneRenderWidget::onRedrawRequested() { if (m_pLevel) @@ -930,7 +1020,7 @@ namespace widgets if (player) { // Ok, level contains player. Let's take his room and move camera to player - const auto iPrimId = player->getProperties().getObject("PrimId", 0); + const auto iPrimId = getGameObjectPrimitiveId(player); const auto vPlayerPosition = player->getParent().lock()->getPosition(); glm::vec3 vCameraPosition = vPlayerPosition; @@ -1060,7 +1150,7 @@ namespace widgets { gamelib::Plane sPlane { eXit.v0, eXit.v1, eXit.v2, eXit.v3 }; - if (m_camera.canSeePlanePartial(sPlane)) + if (m_camera.canSeeObject(sPlane)) { const auto& pRoom = m_pLevel->getSceneObjectByInstanceID(eXit.iRoomREF); if (pRoom) @@ -1073,63 +1163,8 @@ namespace widgets } } } - } -#if 0 // Well optimized variant since this place - else - { - // Need to update room before - updateCameraRoomAttachment(); - - if (!m_pLastRoom) - return; // Do nothing - - std::list acceptedRooms {}; - - // Render static - // SEE 0047B190 (ZViewSpace::CheckExitsInRoom) for details. Current solution is piece of crap - for (const auto& sRoomDef : m_rooms) - { - bool bAcceptedAnything = false; - - if (auto eRoomLoc = m_pLastRoom->eLocation; eRoomLoc == RoomDef::ELocation::eBOTH || eRoomLoc == RoomDef::ELocation::eUNDEFINED) - { - // Render if we've inside or can see - if (sRoomDef.vBoundingBox.contains(m_camera.getPosition()) || m_camera.canSeeObject(sRoomDef.vBoundingBox.min, sRoomDef.vBoundingBox.max)) - { - // Allowed to render - if (auto pRoom = sRoomDef.rRoom.lock()) - { - collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); - bAcceptedAnything = true; - } - } - } - else - { - const bool bBothInside = eRoomLoc == RoomDef::ELocation::eINSIDE && sRoomDef.eLocation == RoomDef::ELocation::eINSIDE; - const bool bBothOutside = eRoomLoc == RoomDef::ELocation::eOUTSIDE && sRoomDef.eLocation == RoomDef::ELocation::eOUTSIDE; - - if (bBothInside || bBothOutside) - { - // Allowed to render - if (auto pRoom = sRoomDef.rRoom.lock()) - { - collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); - bAcceptedAnything = true; - } - } - } - } - - if (auto pRoom = m_pLastRoom->rRoom.lock()) - { - stats.currentRoom = QString::fromStdString(pRoom->getName()); - } - else - { - stats.currentRoom = {}; - } + // Try to render dynamic objects // Render dynamic const auto& vObjects = m_pLevel->getSceneObjects(); auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { @@ -1143,11 +1178,12 @@ namespace widgets using R = gamelib::scene::SceneObject::EVisitResult; pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (pObject->getName().ends_with("_LOCATIONS.zip")) - return R::VR_NEXT; + if (!pObject->getName().ends_with("_LOCATIONS.zip")) + { + // Collect everything inside + collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); + } - // Collect everything inside - collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); return R::VR_NEXT; }); } @@ -1157,24 +1193,34 @@ namespace widgets using R = gamelib::scene::SceneObject::EVisitResult; m_pLevel->getSceneObjects()[0]->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - static const std::array kAllowedTypes = { // only base types - "ZActor", "ZPlayer", "ZItem", "ZItemAmmo" + static const std::array kAllowedTypes = { // only base types + "ZActor", "ZPlayer", "ZItem", "ZItemAmmo" }; for (const auto& sEntBaseName : kAllowedTypes) { + if (pObject->isInheritedOf("ZROOM")) + { + return R::VR_NEXT; // Skip all rooms + } + + // If object based on ... if (pObject->isInheritedOf(sEntBaseName)) { - collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); - return R::VR_NEXT; + if (isGameObjectInActiveRoom(pObject)) + { + collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); + return R::VR_NEXT; // accepted, jump to next + } } } - return R::VR_CONTINUE; // go deeper + // Go deeper + return R::VR_CONTINUE; }); } } -#endif + // Add debug stuff if (m_bRenderPortals || m_bRenderRoomBoundingBox) { @@ -1264,7 +1310,7 @@ namespace widgets { const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getPosition(); - auto primId = geom->getProperties().getObject("PrimId", 0); + auto primId = getGameObjectPrimitiveId(geom); // Calculate object world space bounding box and check that this bbox is visible by out camera @@ -1284,51 +1330,11 @@ namespace widgets // Check that our 'object' is not a collision box if (auto parent = geom->getParent().lock(); parent && parent->getType()->getName() == "ZROOM" && parent->getName() == geom->getName()) return; // Do not render collision meshes - - // NOTE: Need to refactor this place and move it into separated area - // TODO: Move this hack into another place! - if (geom->isInheritedOf("ZItem")) - { - //ZItems has no PrimId. Instead of this they are refs to another geom by path - auto rItemTemplatePath = geom->getProperties().getObject("rItemTemplate"); - const auto pItemTemplate = m_pLevel->getSceneObjectByGEOMREF(rItemTemplatePath); - - if (pItemTemplate) - { - gamelib::scene::SceneObject::Ptr pItem = nullptr; - - // Item found by path. That's cool! But this is not an item, for item need to ask Ground... object inside - for (const auto& childRef : pItemTemplate->getChildren()) - { - if (auto child = childRef.lock(); child && child->getName().starts_with("Ground")) - { - pItem = child; - break; - } - } - - if (pItem) - { - // Nice! Now we ready to replace primId - primId = pItem->getProperties().getObject("PrimId"); - } - } - } // Check that object could be rendered by any way if (primId != 0 && m_resources->m_modelsCache.contains(primId)) { - glm::mat4 mWorldTransform = glm::mat4(1.f); - - if (auto it = m_resources->m_modelTransformCache.find(const_cast(geom)); it != m_resources->m_modelTransformCache.end()) - { - mWorldTransform = it->second; - } - else - { - mWorldTransform = geom->getWorldTransform(); - m_resources->m_modelTransformCache[const_cast(geom)] = mWorldTransform; - } + glm::mat4 mWorldTransform = getGameObjectTransform(geom); // Get model const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; @@ -1665,7 +1671,7 @@ namespace widgets if (pCollisionMesh) { - auto iPrimId = pCollisionMesh->getProperties().getObject("PrimId", 0); + auto iPrimId = getGameObjectPrimitiveId(pCollisionMesh); if (iPrimId != 0) { // Nice, collision mesh was found! Just use it as source for bbox of ZROOM @@ -1681,7 +1687,7 @@ namespace widgets if (!pObject) return R::VR_NEXT; - auto iPrimId = pObject->getProperties().getObject("PrimId", 0); + auto iPrimId = getGameObjectPrimitiveId(pObject); if (!iPrimId) return R::VR_CONTINUE; // Go deeper @@ -1936,6 +1942,14 @@ namespace widgets // Then need to sort found rooms list. Firstly we need to have eINSIDE rooms foundInRooms.sort([](const RoomDef* a, const RoomDef* b) { + if (a->vBoundingBox.getVolume() < b->vBoundingBox.getVolume()) + { + return true; + } + + if (a->eLocation == RoomDef::ELocation::eINSIDE && a->eLocation == RoomDef::ELocation::eOUTSIDE) + return true; + return static_cast(a->eLocation) > static_cast(b->eLocation); }); diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index 23fc1a0..97479a1 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -21,6 +21,9 @@ namespace gamelib void expand(const BoundingBox& another); bool contains(const glm::vec3& vPoint) const; + bool intersect(const BoundingBox& another) const; + + float getVolume() const; static BoundingBox toWorld(const BoundingBox& source, const glm::mat4& mTransform); diff --git a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp index e1d9cf1..d848cda 100644 --- a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp +++ b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp @@ -31,6 +31,27 @@ bool BoundingBox::contains(const glm::vec3& vPoint) const vPoint.z >= min.z && vPoint.z <= max.z; } +bool BoundingBox::intersect(const gamelib::BoundingBox& another) const +{ + if (min.x > another.max.x) return false; + if (max.x < another.min.x) return false; + if (min.y > another.max.y) return false; + if (max.y < another.min.y) return false; + if (min.z > another.max.z) return false; + if (max.z < another.min.z) return false; + + return true; +} + +float BoundingBox::getVolume() const +{ + const float v1 = max.x - min.x; + const float v2 = max.y - min.y; + const float v3 = max.z - min.z; + + return v1 * v2 * v3; +} + BoundingBox BoundingBox::toWorld(const BoundingBox& source, const glm::mat4& mTransform) { glm::vec3 vMin = source.min; From 9bad07a1fefb0bbdb3165c003ff396e69e1ed271 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 14 Apr 2024 12:10:36 +0300 Subject: [PATCH 45/80] Added world picking v0.1 (no dynamic objects, debug switching view) --- BMEdit/Editor/Include/Render/Camera.h | 4 + BMEdit/Editor/Include/Render/Ray.h | 16 ++++ .../Include/Widgets/SceneRenderWidget.h | 3 + BMEdit/Editor/Source/Render/Camera.cpp | 20 +++++ BMEdit/Editor/Source/Render/Ray.cpp | 25 ++++++ .../Source/Widgets/SceneRenderWidget.cpp | 78 +++++++++++++++++-- 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 BMEdit/Editor/Include/Render/Ray.h create mode 100644 BMEdit/Editor/Source/Render/Ray.cpp diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index e0b6a69..6ee322d 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -49,6 +50,9 @@ namespace render [[nodiscard]] const glm::mat4& getProjection() const { return m_mProj; } [[nodiscard]] const glm::mat4& getProjView() const { return m_mProjView; } + [[nodiscard]] Ray getRayFromScreen(const glm::ivec2& vScreenPos) const; + [[nodiscard]] Ray getRayFromScreen(float x, float y) const; + // Setters void setFOV(float fov); void setSpeed(float speed); diff --git a/BMEdit/Editor/Include/Render/Ray.h b/BMEdit/Editor/Include/Render/Ray.h new file mode 100644 index 0000000..085e246 --- /dev/null +++ b/BMEdit/Editor/Include/Render/Ray.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + + +namespace render +{ + struct Ray + { + glm::vec3 vOrigin { .0f }; + glm::vec3 vDirection { .0f }; + + [[nodiscard]] bool intersect(const gamelib::BoundingBox& boundingBox, bool bAllowRaysStartingInsideTargetBbox) const; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 6e6dfa4..1e2f9bf 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -94,6 +94,9 @@ namespace widgets glm::mat4 getGameObjectTransform(const gamelib::scene::SceneObject::Ptr& pObject) const; glm::mat4 getGameObjectTransform(const gamelib::scene::SceneObject* pObject) const; + std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject::Ptr& pObject, bool bWorldTransform = true) const; + std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject* pObject, bool bWorldTransform = true) const; + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 3840846..af00c10 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -9,6 +9,26 @@ namespace render update(); } + Ray Camera::getRayFromScreen(const glm::ivec2& vScreenPos) const + { + return getRayFromScreen(static_cast(vScreenPos.x), static_cast(vScreenPos.y)); + } + + Ray Camera::getRayFromScreen(float x, float y) const + { + constexpr float kSign = 1.f; + + glm::vec4 vRayClip = glm::vec4( + (2.f * x) / static_cast(m_vScreenSize.x) - 1.f, + 1.f - (2.f * y) / static_cast(m_vScreenSize.y), + kSign, 1.f); + + glm::vec4 vRayEye = glm::inverse(m_mProj) * vRayClip; + vRayEye = glm::vec4 { vRayEye.x, vRayEye.y, kSign, .0f }; + glm::vec3 vRayWorld = glm::normalize(glm::vec3(glm::inverse(m_mView) * vRayEye)); + return { getPosition(), vRayWorld }; + } + void Camera::setFOV(float fov) { if (m_fFov != fov) diff --git a/BMEdit/Editor/Source/Render/Ray.cpp b/BMEdit/Editor/Source/Render/Ray.cpp new file mode 100644 index 0000000..a9682c8 --- /dev/null +++ b/BMEdit/Editor/Source/Render/Ray.cpp @@ -0,0 +1,25 @@ +#include + + +namespace render +{ + bool Ray::intersect(const gamelib::BoundingBox& boundingBox, bool bAllowRaysStartingInsideTargetBbox) const + { + const float t1 = (boundingBox.min.x - vOrigin.x) / vDirection.x; + const float t2 = (boundingBox.max.x - vOrigin.x) / vDirection.x; + const float t3 = (boundingBox.min.y - vOrigin.y) / vDirection.y; + const float t4 = (boundingBox.max.y - vOrigin.y) / vDirection.y; + const float t5 = (boundingBox.min.z - vOrigin.z) / vDirection.z; + const float t6 = (boundingBox.max.z - vOrigin.z) / vDirection.z; + + const float tMin = glm::max(glm::max(glm::min(t1, t2), glm::min(t3, t4)), glm::min(t5, t6)); + const float tMax = glm::min(glm::min(glm::max(t1, t2), glm::max(t3, t4)), glm::max(t5, t6)); + + if (tMax < .0f || tMin > tMax || (!bAllowRaysStartingInsideTargetBbox && tMin < 0.f)) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 93f623a..dfa96ef 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -345,6 +345,54 @@ namespace widgets void SceneRenderWidget::mousePressEvent(QMouseEvent* event) { QOpenGLWidget::mousePressEvent(event); + + if (event->button() == Qt::MouseButton::RightButton && m_pLastRoom != nullptr) + { + // Begin ray cast + const auto vMouseClickPos = event->position(); + render::Ray sRay = m_camera.getRayFromScreen(static_cast(vMouseClickPos.x()), + static_cast(vMouseClickPos.y())); + + // Need to find intersects with this thing. Need to visit only current room + if (auto pRoom = m_pLastRoom->rRoom.lock()) + { + using R = gamelib::scene::SceneObject::EVisitResult; + std::vector vHitList; + + // Hit dynamic + // TODO: Implement me please + + // Hit static + pRoom->visitChildren([&sRay, &vHitList, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) + { + // Need to exclude objects where bbox origin is inside + if (sRay.intersect(bbox.value(), false)) + { + vHitList.push_back(pObject); + return R::VR_NEXT; + } + } + + return R::VR_CONTINUE; + }); + + // Sort hit list by distance to camera + std::sort(vHitList.begin(), vHitList.end(), [&sRay, this](const gamelib::scene::SceneObject::Ptr& pFirst, const gamelib::scene::SceneObject::Ptr& pSecond) { + const float fDist0 = glm::distance(sRay.vOrigin, pFirst->getPosition()); + const float fDist1 = glm::distance(sRay.vOrigin, pSecond->getPosition()); + + return fDist0 < fDist1; + }); + + if (!vHitList.empty()) + { + // TODO: Remove later! + setGeomViewMode(vHitList[0].get()); + setSelectedObject(vHitList[0].get()); + } + } + } } void SceneRenderWidget::mouseReleaseEvent(QMouseEvent* event) @@ -511,7 +559,6 @@ namespace widgets return false; } - //m_pLastRoom->vBoundingBox.intersect auto primId = getGameObjectPrimitiveId(pObject); if (primId == 0) { @@ -590,9 +637,31 @@ namespace widgets return glm::mat4(1.f); } - /* - * - */ + std::optional SceneRenderWidget::getGameObjectBoundingBox(const gamelib::scene::SceneObject::Ptr& pObject, bool bWorldTransform) const + { + if (!pObject) return std::nullopt; + return getGameObjectBoundingBox(pObject.get(), bWorldTransform); + } + + std::optional SceneRenderWidget::getGameObjectBoundingBox(const gamelib::scene::SceneObject* pObject, bool bWorldTransform) const + { + if (!pObject) return std::nullopt; + + auto primId = getGameObjectPrimitiveId(pObject); + if (primId == 0) + { + return std::nullopt; + } + + const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; + if (bWorldTransform) + { + glm::mat4 mWorldTransform = getGameObjectTransform(pObject); + return gamelib::BoundingBox::toWorld(model.boundingBox, mWorldTransform); + } + + return model.boundingBox; + } void SceneRenderWidget::onRedrawRequested() { @@ -1165,7 +1234,6 @@ namespace widgets } // Try to render dynamic objects - // Render dynamic const auto& vObjects = m_pLevel->getSceneObjects(); auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); From da53c90405848b59db0e9d1c2146f4819ad1c2ab Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 14 Apr 2024 21:24:11 +0300 Subject: [PATCH 46/80] World picking: added signal to widget, fixed few rendering issues with HitmanBloodMoney.ZIP level. Small refactoring --- .../Include/Widgets/SceneRenderWidget.h | 19 ++ BMEdit/Editor/Source/Render/Camera.cpp | 7 +- .../Source/Widgets/SceneRenderWidget.cpp | 175 +++++++++++------- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 4 + 4 files changed, 130 insertions(+), 75 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 1e2f9bf..a9f1dc5 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -49,6 +49,21 @@ namespace widgets float fFrameTime { .0f }; // how much time used for render this frame }; + struct RayCastObjectDescription + { + enum EPriority { + EP_STATIC_OBJECT = 0, + EP_DYNAMIC_OBJECT = 1 + }; + + EPriority ePrio { EPriority::EP_STATIC_OBJECT }; + float fRayOriginDistance { .0f }; + gamelib::scene::SceneObject::Ptr pObject { nullptr }; + + // Operators + bool operator<(const RayCastObjectDescription& another) const; + }; + class SceneRenderWidget : public QOpenGLWidget { Q_OBJECT @@ -97,11 +112,15 @@ namespace widgets std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject::Ptr& pObject, bool bWorldTransform = true) const; std::optional getGameObjectBoundingBox(const gamelib::scene::SceneObject* pObject, bool bWorldTransform = true) const; + std::vector performRayCastToScene(const QPointF& screenSpace, const gamelib::scene::SceneObject::Ptr& pStartObject = nullptr) const; + signals: void resourcesReady(); void resourceLoadFailed(const QString& reason); void frameReady(const RenderStats& stats); + void worldSelectionChanged(const std::vector& selectedObjects); + public slots: void onRedrawRequested(); diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index af00c10..3e9b724 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -17,12 +17,7 @@ namespace render Ray Camera::getRayFromScreen(float x, float y) const { constexpr float kSign = 1.f; - - glm::vec4 vRayClip = glm::vec4( - (2.f * x) / static_cast(m_vScreenSize.x) - 1.f, - 1.f - (2.f * y) / static_cast(m_vScreenSize.y), - kSign, 1.f); - + glm::vec4 vRayClip = glm::vec4((2.f * x) / static_cast(m_vScreenSize.x) - 1.f, 1.f - (2.f * y) / static_cast(m_vScreenSize.y), kSign, 1.f); glm::vec4 vRayEye = glm::inverse(m_mProj) * vRayClip; vRayEye = glm::vec4 { vRayEye.x, vRayEye.y, kSign, .0f }; glm::vec3 vRayWorld = glm::normalize(glm::vec3(glm::inverse(m_mView) * vRayEye)); diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index dfa96ef..39027ec 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -37,6 +37,21 @@ namespace widgets "AdditionalResources", "AllLevels/mainsceneincludes.zip", "AllLevels/equipment.zip" }; + bool RayCastObjectDescription::operator<(const widgets::RayCastObjectDescription& another) const + { + if (another.ePrio == ePrio) + { + if (std::fabsf(another.fRayOriginDistance - fRayOriginDistance) <= std::numeric_limits::epsilon()) + { + return false; // They are completely same (except object itself, but who cares?) + } + + return fRayOriginDistance < another.fRayOriginDistance; + } + + return static_cast(ePrio) < static_cast(another.ePrio); + } + using namespace render; struct SceneRenderWidget::GLResources @@ -346,51 +361,16 @@ namespace widgets { QOpenGLWidget::mousePressEvent(event); - if (event->button() == Qt::MouseButton::RightButton && m_pLastRoom != nullptr) + if (event->button() == Qt::MouseButton::RightButton && !m_pLevel->getSceneObjects().empty()) { // Begin ray cast const auto vMouseClickPos = event->position(); - render::Ray sRay = m_camera.getRayFromScreen(static_cast(vMouseClickPos.x()), - static_cast(vMouseClickPos.y())); - // Need to find intersects with this thing. Need to visit only current room - if (auto pRoom = m_pLastRoom->rRoom.lock()) + // If no rooms on level we should use ROOT as initial point (not recommended in MOST cases) + auto result = performRayCastToScene(vMouseClickPos, (!m_pLastRoom && m_rooms.empty()) ? m_pLevel->getSceneObjects()[0] : nullptr); + if (!result.empty()) { - using R = gamelib::scene::SceneObject::EVisitResult; - std::vector vHitList; - - // Hit dynamic - // TODO: Implement me please - - // Hit static - pRoom->visitChildren([&sRay, &vHitList, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) - { - // Need to exclude objects where bbox origin is inside - if (sRay.intersect(bbox.value(), false)) - { - vHitList.push_back(pObject); - return R::VR_NEXT; - } - } - - return R::VR_CONTINUE; - }); - - // Sort hit list by distance to camera - std::sort(vHitList.begin(), vHitList.end(), [&sRay, this](const gamelib::scene::SceneObject::Ptr& pFirst, const gamelib::scene::SceneObject::Ptr& pSecond) { - const float fDist0 = glm::distance(sRay.vOrigin, pFirst->getPosition()); - const float fDist1 = glm::distance(sRay.vOrigin, pSecond->getPosition()); - - return fDist0 < fDist1; - }); - - if (!vHitList.empty()) - { - // TODO: Remove later! - setGeomViewMode(vHitList[0].get()); - setSelectedObject(vHitList[0].get()); - } + emit worldSelectionChanged(result); } } } @@ -663,6 +643,64 @@ namespace widgets return model.boundingBox; } + std::vector SceneRenderWidget::performRayCastToScene(const QPointF& screenSpace, const gamelib::scene::SceneObject::Ptr& pStartObject) const + { + render::Ray sRay = m_camera.getRayFromScreen(static_cast(screenSpace.x()), + static_cast(screenSpace.y())); + + gamelib::scene::SceneObject* pRoot = pStartObject.get(); + + if (!pRoot) + { + if (m_pLastRoom && !m_pLastRoom->rRoom.expired()) + { + pRoot = m_pLastRoom->rRoom.lock().get(); + } + } + + // Need to find intersects with this thing. Need to visit only current room + if (pRoot) + { + using R = gamelib::scene::SceneObject::EVisitResult; + + std::vector collectedObjects {}; + + static RayCastObjectDescription::EPriority s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; + + auto hitObjVisitor = [&sRay, &collectedObjects, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) + { + // Need to exclude objects where bbox origin is inside + if (sRay.intersect(bbox.value(), false)) + { + auto& obj = collectedObjects.emplace_back(); + obj.ePrio = s_CurrentPrio; + obj.pObject = pObject; + obj.fRayOriginDistance = glm::distance(sRay.vOrigin, pObject->getPosition()); + return R::VR_NEXT; + } + } + + return R::VR_CONTINUE; + }; + + // Hit dynamic (not implemented yet) + s_CurrentPrio = RayCastObjectDescription::EPriority::EP_DYNAMIC_OBJECT; // Now dynamic objects + // TODO: Iterate over dynamic objects and check collision with them + + // Hit static + s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; // Now static objects + pRoot->visitChildren(hitObjVisitor); + + // Sort hit list by distance to camera + std::sort(collectedObjects.begin(), collectedObjects.end()); + + return collectedObjects; + } + + return {}; + } + void SceneRenderWidget::onRedrawRequested() { if (m_pLevel) @@ -1195,7 +1233,7 @@ namespace widgets // Update room updateCameraRoomAttachment(stats); - if (pRootGeom != m_pLevel->getSceneObjects()[0].get() || m_rooms.empty() /* on some levels m_rooms cache could be not presented! */) + if (pRootGeom != m_pLevel->getSceneObjects()[0].get()) { // Render from specific node (no performance optimisations here) collectRenderEntriesIntoRenderList(pRootGeom, entries, stats, bIgnoreVisibility); @@ -1231,39 +1269,33 @@ namespace widgets } } } - } - // Try to render dynamic objects - const auto& vObjects = m_pLevel->getSceneObjects(); - auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { - return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); - }); + // Try to render dynamic objects + const auto& vObjects = m_pLevel->getSceneObjects(); + auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { + return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); + }); - if (it != vObjects.end()) - { - // Add this 'ROOM' as another thing to visit - const gamelib::scene::SceneObject* pDynRoot = it->get(); + gamelib::scene::SceneObject* pDynRoot = nullptr; - using R = gamelib::scene::SceneObject::EVisitResult; - pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (!pObject->getName().ends_with("_LOCATIONS.zip")) - { - // Collect everything inside - collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); - } + if (it != vObjects.end()) + { + // Add this 'ROOM' as another thing to visit + pDynRoot = it->get(); + } + else + { + // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( + using R = gamelib::scene::SceneObject::EVisitResult; - return R::VR_NEXT; - }); - } - else - { - // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( + pDynRoot = m_pLevel->getSceneObjects()[0].get(); + } + + // Visit dynamic root using R = gamelib::scene::SceneObject::EVisitResult; - m_pLevel->getSceneObjects()[0]->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - static const std::array kAllowedTypes = { // only base types - "ZActor", "ZPlayer", "ZItem", "ZItemAmmo" - }; + pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + static const std::array kAllowedTypes = { "ZActor", "ZPlayer", "ZItem", "ZItemAmmo", "ZLNKOBJ" }; for (const auto& sEntBaseName : kAllowedTypes) { @@ -1275,7 +1307,7 @@ namespace widgets // If object based on ... if (pObject->isInheritedOf(sEntBaseName)) { - if (isGameObjectInActiveRoom(pObject)) + if (isGameObjectInActiveRoom(pObject) || m_rooms.empty()) { collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); return R::VR_NEXT; // accepted, jump to next @@ -1287,6 +1319,11 @@ namespace widgets return R::VR_CONTINUE; }); } + else + { + // Ok, stupid render approach here. Just render everything starts from ROOT (dynamic & static doesn't matter in this case) + collectRenderEntriesIntoRenderList(m_pLevel->getSceneObjects()[0].get(), entries, stats, bIgnoreVisibility); + } } // Add debug stuff diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 267dde4..0c567f1 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -136,6 +136,10 @@ void BMEditMainWindow::connectActions() connect(ui->actionRenderMode_RenderRoomBoundingBoxes, &QAction::toggled, this, [this](bool val) { ui->sceneGLView->setShouldRenderRoomBoundingBox(val); }); + connect(ui->sceneGLView, &widgets::SceneRenderWidget::worldSelectionChanged, this, [this](const std::vector& hitList) { + // On hit performed we need to react somehow + // TODO: Do something here + }); } void BMEditMainWindow::connectDockWidgetActions() From 9027bdd393f9a99c1fda8d9742bf9bdb720eecd9 Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 16 Apr 2024 21:47:49 +0300 Subject: [PATCH 47/80] Reversed some OCT entities. --- .../GameLib/Include/GameLib/OCT/OCTEntries.h | 18 +++++++++--------- .../GameLib/Source/GameLib/OCT/OCTEntries.cpp | 8 ++------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h index 1554578..ac40d3a 100644 --- a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h @@ -43,21 +43,21 @@ namespace gamelib::oct /** * Idk what this block contains + * + * Note: first entry in most cases zeroed ni vUnk4, vUnk28, vUnk34, vUnk40, unk4C, unk50 */ struct OCTUnknownBlock { - uint32_t unk0 { 0 }; + uint32_t unk0 { 0 }; // always 1? - glm::mat3 vUnk4 {}; - glm::vec3 vUnk28 {}; + glm::mat3 vUnk4 {}; // in most cases identity matrix - float unk34 { 0.f }; - float unk38 { 0.f }; - float unk3C { 0.f }; + glm::vec3 vUnk28 {}; // some vector + glm::vec3 vUnk34 { 0.f }; // another vector, Z component bigger than vUnk28 + glm::vec3 vUnk40 {}; // vector, idk - glm::vec3 vUnk40 {}; - uint32_t unk4C { 0 }; - float unk50 { 0.f }; + uint32_t unk4C { 0 }; // Looks like priority or flags. In hideout first 1114, then less and decreased by 1 since second entry + uint32_t unk50 { 0 }; // In eOUTSIDE tree always zeroed, in eINSIDE/eBOTH/eUNKNOWN in most cases 0 but sometimes > 0 static void deserialize(OCTUnknownBlock& block, ZBio::ZBinaryReader::BinaryReader* binaryReader); }; diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp index 08a1a06..f4c5f8c 100644 --- a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp @@ -33,14 +33,10 @@ namespace gamelib::oct binaryReader->read(glm::value_ptr(block.vUnk4), 9); binaryReader->read(glm::value_ptr(block.vUnk28), 3); - - block.unk34 = binaryReader->read(); - block.unk38 = binaryReader->read(); - block.unk3C = binaryReader->read(); - + binaryReader->read(glm::value_ptr(block.vUnk34), 3); binaryReader->read(glm::value_ptr(block.vUnk40), 3); block.unk4C = binaryReader->read(); - block.unk50 = binaryReader->read(); + block.unk50 = binaryReader->read(); } } \ No newline at end of file From b970de1cecae5edfb7949c2cce2ea52794c55a24 Mon Sep 17 00:00:00 2001 From: DronCode Date: Fri, 26 Apr 2024 22:11:06 +0300 Subject: [PATCH 48/80] Reversed some stuff in some places --- Assets/g1/ZGuardQuarterController.json | 2 +- Assets/g1/ZHM3Actor.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/g1/ZGuardQuarterController.json b/Assets/g1/ZGuardQuarterController.json index dfbe780..df987c3 100644 --- a/Assets/g1/ZGuardQuarterController.json +++ b/Assets/g1/ZGuardQuarterController.json @@ -3,6 +3,6 @@ "parent": "ZEventBase", "kind": "TypeKind.COMPLEX", "properties": [ - { "name": "property_DC", "typename": "ZGEOMREF", "offset": 220 } + { "name": "rWeaponStorage", "typename": "ZGEOMREF", "offset": 220 } ] } \ No newline at end of file diff --git a/Assets/g1/ZHM3Actor.json b/Assets/g1/ZHM3Actor.json index 6aa42f8..a3a72d4 100644 --- a/Assets/g1/ZHM3Actor.json +++ b/Assets/g1/ZHM3Actor.json @@ -8,7 +8,7 @@ "parent": "ZActor", "properties": [ { - "name": "property_900", + "name": "rDisguise", "offset": 2304, "typename": "ZGEOMREF" }, From ffef3ff50a7a97c4924769a0a39108905e6d9cc6 Mon Sep 17 00:00:00 2001 From: DronCode Date: Fri, 26 Apr 2024 22:11:34 +0300 Subject: [PATCH 49/80] Added base templates for script reversal. --- Assets/scripts/AllLevels.json | 131 ++++++++++++++++++++ Assets/scripts/M12.json | 223 ++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 Assets/scripts/AllLevels.json create mode 100644 Assets/scripts/M12.json diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json new file mode 100644 index 0000000..1555e28 --- /dev/null +++ b/Assets/scripts/AllLevels.json @@ -0,0 +1,131 @@ +{ + "alllevels\\civilian": { + "parameters": [ + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rHelpPointList" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + } + ] + }, + "alllevels\\guard": { + "parameters": [ + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rHelpPointList" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPrimaryWeapon" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rBodyStorage" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray", + "Array", "Int32", "EndArray" + ] + } + ] + }, + "alllevels\\radiocontroller": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownMask" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + } +} \ No newline at end of file diff --git a/Assets/scripts/M12.json b/Assets/scripts/M12.json new file mode 100644 index 0000000..8064e37 --- /dev/null +++ b/Assets/scripts/M12.json @@ -0,0 +1,223 @@ +{ + "m12\\m12_touristwithsuitcase": { + "parent": "alllevels\\civilian", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuidePerson" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rTargetPlace" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rSuitcase" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPutDownSuitcaseHere" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rSmokeHere" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rXrayPutDown" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rXrayPickup" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPutDownSuitcaseXRAYHere" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "cSecurityXray" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rInterrogationPlace" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rFollowToGuard" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "cEntranceController" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "BoxPosEntranceArea" + } + ] + }, + "m12\\m12_touristpushable": { + "parent": "alllevels\\civilian", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPushPoint0" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "pathThrownOut" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rAlarmController" + } + ] + }, + "m12\\m12_tourist": { + "parent": "alllevels\\civilian", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuidePerson" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rMuseumFoyer" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPosEntranceCheckDone" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "cEntranceController" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "BoxPosFrontArea" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "BoxPosEntranceArea" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rCameraItem" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "Array" + ] + }, + { + "kind": "VARIABLE", + "typename": "PRPOpCode.Int32", + "name": "iPersonInPhotoIdx" + }, + { + "kind": "UNKNOWN", + "opcodes": [ + "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPhotoPos" + } + ] + }, + "m12\\m12_tourguide": { + "parent": "alllevels\\civilian", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rTourList" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rTouristTourPositionsList" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rTouristList" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rlTouristList" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "cEntranceController" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPosWaitForTourists" + } + ] + }, + "m12\\m12_museumstaff": { "parent": "alllevels\\civilian" }, + "m12\\m12_museumstaffinshower": { "parent": "alllevels\\civilian" }, + "m12\\m12_guardbase": { "parent": "alllevels\\guard" }, + "m12\\m12_marinebase": { + "parent": "m12\\m12_guardbase", + "parameters": [ + { + "kind": "UNKNOWN", + "opcodes": [ + "Array", "Int32", "EndArray" + ] + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rPosMontrePosition" + } + ] + }, + "m12\\m12_marinecameraguard": { "parent": "m12\\m12_marinebase" }, + "m12\\m12_suitcasebasementcontroller": { + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rFrom" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rIDK" + } + ] + } +} \ No newline at end of file From 68b6a1d3f79c5e5640745a91ce9d9e9ce3ddd8df Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 27 Apr 2024 10:55:40 +0300 Subject: [PATCH 50/80] All base scripts marked --- Assets/scripts/AllLevels.json | 380 ++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json index 1555e28..67b4f2d 100644 --- a/Assets/scripts/AllLevels.json +++ b/Assets/scripts/AllLevels.json @@ -127,5 +127,385 @@ { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnknownValue1" }, { "kind": "UNKNOWN", "opcodes": ["EndArray"] } ] + }, + "alllevels\\rat": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnkRef" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnkRef1" } + ] + }, + "alllevels\\useable\\useable_wait": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_sitdown": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_playanim": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnimationName" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue6" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue10" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_playubanim": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnimationName" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rUnknownValue6" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_piss": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_smoking": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCigarettes" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_consumeobject": { + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rItems" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDummyObject" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_lookaround": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_playoneliner": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\useable\\useable_waypointnotify": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\vehicles\\car": { + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCheckInside" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\kissingcouple": {}, + "alllevels\\armedbartender": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnkValue4" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnkValue8" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue10" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBartendingPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBarguestUsepoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGlass" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGlassPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBottle" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBottlePos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSponge" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGlassToClean" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue19" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + ] + }, + "alllevels\\bird": { + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFlyBox" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue1" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue2" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue10" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue11" } + ] + }, + "alllevels\\alligator": { + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknown" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHidePos" } + ] } } \ No newline at end of file From 912c08e011b846e50e59292c93021f36a07e2c37 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 27 Apr 2024 11:26:43 +0300 Subject: [PATCH 51/80] M00 level control marked M13 all scripts marked --- Assets/scripts/M00.json | 46 ++++++++ Assets/scripts/M13.json | 226 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 Assets/scripts/M00.json create mode 100644 Assets/scripts/M13.json diff --git a/Assets/scripts/M00.json b/Assets/scripts/M00.json new file mode 100644 index 0000000..3aecc99 --- /dev/null +++ b/Assets/scripts/M00.json @@ -0,0 +1,46 @@ +{ + "m00\\m00_levelcontrol": { + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rMrSwingKing" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoop" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rFrontGateGuard" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rLightRack0" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rSpotsList" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWinchA" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWinchA_Broken" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rDruglabActorsList" + } + ] + } +} \ No newline at end of file diff --git a/Assets/scripts/M13.json b/Assets/scripts/M13.json new file mode 100644 index 0000000..84633c4 --- /dev/null +++ b/Assets/scripts/M13.json @@ -0,0 +1,226 @@ +{ + "m13\\m13_levelcontrol": {}, + "m13\\journalist": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rHelpPointsList" + }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rVoiceRecorder" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWheelchairGuy" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWheelchairGuy_ForGame" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWheelchairGuy_MOVETOCREATION" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rLookout" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rWaypointsList" + } + ] + }, + + "m13\\wheelchair": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game" } + ] + }, + "m13\\wheelchairguy": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_0" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_1" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rJournalist_01" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rScar" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_Mausoleum" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_lookout" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_Escape" } + ] + }, + "m13\\priest": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHelpPointsList" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBible" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_Podium" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_preach" } + ] + }, + "m13\\secretservice": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardQuarter" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue11" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue12" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCinematicTargets" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rInsideChurchRoom" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue15" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue16" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy_ForGame" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rNearVehiclesWaypoint" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue20" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardList" } + ] + } +} \ No newline at end of file From 941332df4b94c8271c069286e421a190ea7f9e7a Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 27 Apr 2024 11:27:02 +0300 Subject: [PATCH 52/80] Hideout all scripts marked --- Assets/scripts/Hideout.json | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Assets/scripts/Hideout.json diff --git a/Assets/scripts/Hideout.json b/Assets/scripts/Hideout.json new file mode 100644 index 0000000..8e54f36 --- /dev/null +++ b/Assets/scripts/Hideout.json @@ -0,0 +1,77 @@ +{ + "hideout\\hideout_levelcontrol": { + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rListAllWeapons" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rListAllAmmo" + } + ] + }, + "hideout\\hideout_canary": { + "parameters": [ + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue1" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue2" }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue10" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue11" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBirdPos" } + ] + }, + "hideout\\hideout_happyrat": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rRatRace" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rArrow" } + ] + } +} \ No newline at end of file From 95c816581333b3776e79809de67a946098218007 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 28 Apr 2024 12:01:18 +0300 Subject: [PATCH 53/80] Small markup changes --- Assets/TypesRegistry.json | 3 +- Assets/g1/ZScriptC.json | 2 +- Assets/scripts/AllLevels.json | 422 +++++++++++++++++++--------------- Assets/scripts/Hideout.json | 44 ++-- Assets/scripts/M12.json | 6 +- Assets/scripts/M13.json | 133 ++++++----- 6 files changed, 325 insertions(+), 285 deletions(-) diff --git a/Assets/TypesRegistry.json b/Assets/TypesRegistry.json index 12f22ab..a2b5340 100644 --- a/Assets/TypesRegistry.json +++ b/Assets/TypesRegistry.json @@ -115,5 +115,6 @@ "0x200458": "ZHFlowLnkObj", "0x20045C": "ZMardiGrassHFlow", "0x20045F": "ZHFlowM11" - } + }, + "script_incs": "scripts" } \ No newline at end of file diff --git a/Assets/g1/ZScriptC.json b/Assets/g1/ZScriptC.json index 6b83440..bebcac4 100644 --- a/Assets/g1/ZScriptC.json +++ b/Assets/g1/ZScriptC.json @@ -4,6 +4,6 @@ "kind": "TypeKind.COMPLEX", "skip_unexposed_properties": true, "properties": [ - { "name": "ScriptName", "typename": "PRPOpCode.String" } + { "name": "ScriptName", "typename": "PRPOpCode.String", "__note": "DO NOT MODIFY THIS NAME!!!" } ] } \ No newline at end of file diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json index 67b4f2d..58516cf 100644 --- a/Assets/scripts/AllLevels.json +++ b/Assets/scripts/AllLevels.json @@ -4,9 +4,9 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] }, { @@ -17,9 +17,9 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] } ] @@ -29,9 +29,9 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] }, { @@ -42,9 +42,9 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] }, { @@ -55,8 +55,8 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] }, { @@ -67,80 +67,80 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray", - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray", + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] } ] }, "alllevels\\radiocontroller": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownMask" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\rat": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnkRef" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnkRef1" } @@ -148,303 +148,343 @@ }, "alllevels\\useable\\useable_wait": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_sitdown": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_playanim": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnimationName" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue6" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnkValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue10" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_playubanim": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnimationName" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_piss": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_smoking": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCigarettes" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_consumeobject": { "parameters": [ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rItems" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDummyObject" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_lookaround": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_playoneliner": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sAnim3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\useable\\useable_waypointnotify": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\vehicles\\car": { "parameters": [ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCheckInside" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] + }, + "alllevels\\kissingcouple": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rCheckInside" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue4" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknownGeomRef" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnkValue8" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMale" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue11" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue12" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, - "alllevels\\kissingcouple": {}, "alllevels\\armedbartender": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnkValue4" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnkValue8" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue10" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBartendingPos" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBarguestUsepoint" }, @@ -455,9 +495,9 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSponge" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGlassToClean" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnkValue19" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] } + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, "alllevels\\bird": { @@ -466,37 +506,37 @@ { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue1" }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue10" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue11" } ] diff --git a/Assets/scripts/Hideout.json b/Assets/scripts/Hideout.json index 8e54f36..810f2e5 100644 --- a/Assets/scripts/Hideout.json +++ b/Assets/scripts/Hideout.json @@ -19,37 +19,37 @@ { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue1" }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue7" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue10" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue11" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBirdPos" } @@ -57,17 +57,17 @@ }, "hideout\\hideout_happyrat": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rRatRace" }, diff --git a/Assets/scripts/M12.json b/Assets/scripts/M12.json index 8064e37..925fdc0 100644 --- a/Assets/scripts/M12.json +++ b/Assets/scripts/M12.json @@ -130,7 +130,7 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array" + "PRPOpCode.Array" ] }, { @@ -141,7 +141,7 @@ { "kind": "UNKNOWN", "opcodes": [ - "EndArray" + "PRPOpCode.EndArray" ] }, { @@ -195,7 +195,7 @@ { "kind": "UNKNOWN", "opcodes": [ - "Array", "Int32", "EndArray" + "PRPOpCode.Array", "PRPOpCode.Int32", "PRPOpCode.EndArray" ] }, { diff --git a/Assets/scripts/M13.json b/Assets/scripts/M13.json index 84633c4..36ab810 100644 --- a/Assets/scripts/M13.json +++ b/Assets/scripts/M13.json @@ -2,17 +2,17 @@ "m13\\m13_levelcontrol": {}, "m13\\journalist": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", @@ -20,17 +20,17 @@ "name": "rHelpPointsList" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", @@ -64,20 +64,19 @@ } ] }, - "m13\\wheelchair": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy" }, @@ -86,41 +85,41 @@ }, "m13\\wheelchairguy": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_0" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_1" }, @@ -133,31 +132,31 @@ }, "m13\\priest": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHelpPointsList" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBible" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_Podium" }, @@ -166,51 +165,51 @@ }, "m13\\secretservice": { "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardQuarter" }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue11" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "UNKNOWN", "opcodes": ["Array"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue12" }, - { "kind": "UNKNOWN", "opcodes": ["EndArray"] }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCinematicTargets" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rInsideChurchRoom" }, From 9a8efa6abd5f09bdc8473f73694bf8ff38abe72d Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 28 Apr 2024 19:43:20 +0300 Subject: [PATCH 54/80] Script replacement works well! --- .../Models/SceneObjectControllerModel.h | 27 +- .../Include/Widgets/SceneRenderWidget.h | 49 +- .../Resources/Shaders/TexturedEntity_GL33.fsh | 1 + .../Resources/Shaders/TexturedEntity_GL33.vsh | 29 +- .../Models/SceneObjectControllerModel.cpp | 162 +++++- .../Models/SceneObjectPropertiesModel.cpp | 2 +- .../Source/Widgets/GeomControllersWidget.cpp | 1 + .../Source/Widgets/SceneRenderWidget.cpp | 486 ++++++++++-------- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 48 +- BMEdit/GameLib/Include/GameLib/BoundingBox.h | 5 +- .../GameLib/Include/GameLib/OCT/OCTEntries.h | 15 +- .../Include/GameLib/Scene/SceneObject.h | 16 +- BMEdit/GameLib/Include/GameLib/TypeRegistry.h | 13 + BMEdit/GameLib/Include/GameLib/Value.h | 9 +- BMEdit/GameLib/Source/GameLib/BoundingBox.cpp | 37 +- BMEdit/GameLib/Source/GameLib/Level.cpp | 24 +- .../GameLib/Source/GameLib/OCT/OCTEntries.cpp | 2 +- .../GameLib/Source/GameLib/OCT/OCTReader.cpp | 2 +- .../GameLib/Source/GameLib/PRP/PRPOpCode.cpp | 5 +- .../Source/GameLib/Scene/SceneObject.cpp | 14 +- .../Scene/SceneObjectPropertiesDumper.cpp | 2 +- .../Scene/SceneObjectPropertiesLoader.cpp | 3 +- .../GameLib/Source/GameLib/TypeRegistry.cpp | 152 ++++++ BMEdit/GameLib/Source/GameLib/Value.cpp | 20 +- 24 files changed, 833 insertions(+), 291 deletions(-) diff --git a/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h b/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h index b29ed82..c8a9ba5 100644 --- a/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h +++ b/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h @@ -1,8 +1,10 @@ #pragma once +#include #include #include #include +#include namespace models @@ -10,6 +12,20 @@ namespace models class SceneObjectControllerModel : public ValueModelBase { Q_OBJECT + + private: + struct SpecialRow + { + int startRow { 0 }; + int endRow { 0 }; + QColor backgroundColor {}; + + bool includes(int row) const + { + return row >= startRow && row <= endRow; + } + }; + public: SceneObjectControllerModel(QObject *parent = nullptr); @@ -18,11 +34,20 @@ namespace models void setControllerIndex(int controllerIndex); void resetController(); + QVariant data(const QModelIndex &index, int role) const override; + + private: + void addSugarViews(const gamelib::Type* pControllerType, gamelib::Value& v, const std::string& scriptName); + void removeSugarViews(const gamelib::Type* pControllerType, gamelib::Value& v); + private slots: void onValueChanged(); private: + static constexpr int kUnset = -1; + gamelib::scene::SceneObject *m_geom { nullptr }; - int m_currentControllerIndex = -1; + int m_currentControllerIndex = kUnset; + std::vector m_specialRows {}; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index a9f1dc5..b62b4f8 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -49,14 +49,15 @@ namespace widgets float fFrameTime { .0f }; // how much time used for render this frame }; - struct RayCastObjectDescription + enum class EObjectPriority { - enum EPriority { - EP_STATIC_OBJECT = 0, - EP_DYNAMIC_OBJECT = 1 - }; + EP_STATIC_OBJECT = 0, + EP_DYNAMIC_OBJECT = 1 + }; - EPriority ePrio { EPriority::EP_STATIC_OBJECT }; + struct RayCastObjectDescription + { + EObjectPriority ePrio { EObjectPriority::EP_STATIC_OBJECT }; float fRayOriginDistance { .0f }; gamelib::scene::SceneObject::Ptr pObject { nullptr }; @@ -64,6 +65,13 @@ namespace widgets bool operator<(const RayCastObjectDescription& another) const; }; + struct SeebleObject + { + EObjectPriority ePrio { EObjectPriority::EP_STATIC_OBJECT }; + gamelib::BoundingBox sBoundingBox {}; + gamelib::scene::SceneObject::Ptr pObject {}; + }; + class SceneRenderWidget : public QOpenGLWidget { Q_OBJECT @@ -101,8 +109,6 @@ namespace widgets bool shouldRenderRoomBoundingBox() const; void setShouldRenderRoomBoundingBox(bool bVal); - bool isGameObjectInActiveRoom(const gamelib::scene::SceneObject::Ptr& pObject) const; - int32_t getGameObjectPrimitiveId(const gamelib::scene::SceneObject::Ptr& pObject) const; int32_t getGameObjectPrimitiveId(const gamelib::scene::SceneObject* pObject) const; @@ -146,13 +152,12 @@ namespace widgets [[nodiscard]] glm::ivec2 getViewportSize() const; void collectRenderList(const render::Camera& camera, const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility); - void collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility); + void collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* pRootGeom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility, bool bBreakOnChild = false); void performRender(QOpenGLFunctions_3_3_Core* glFunctions, const render::RenderEntriesList& entries, const render::Camera& camera, const std::function& filter); void invalidateRenderList(); void buildRoomCache(QOpenGLFunctions_3_3_Core* glFunctions); - void resetLastRoom(); /** * @brief Method trying to find a new room for current camera (if camera not in that room of bRejectLastResult is true) @@ -211,6 +216,13 @@ namespace widgets eBOTH = 3, }; + enum class EBoundingBoxSource : int { + BBS_ROOM_COLLISION_MESH, ///< Calculated via collision mesh + BBS_ZBOUNDS_AUTO_EXPAND, ///< Calculated by ZBOUND object points + BBS_AUTO_ROOM_EXPAND, ///< Calculated as expand of all objects in room + BBS_NONE ///< Not calculated or other BoundingBox source (auto-gen as example) + }; + /** * @brief Weak pointer to entity which represent room */ @@ -226,6 +238,11 @@ namespace widgets */ ELocation eLocation { ELocation::eUNDEFINED }; + /** + * @brief How bounding box calculated + */ + EBoundingBoxSource eBoundingBoxSource { EBoundingBoxSource::BBS_NONE }; + /** * @brief Information about room exits */ @@ -241,14 +258,24 @@ namespace widgets */ std::unique_ptr mExitsDebugModel { nullptr }; + /** + * @brief This list contains objects which could be visible in this specific room + */ + std::vector vObjects {}; + /** * @brief Room bounding box debug model */ std::unique_ptr mBBoxModel { nullptr }; + + /** + * @brief Means "is this room created because no other rooms exists" + */ + bool bIsVirtualBigRoom { false }; }; std::list m_rooms {}; - const RoomDef* m_pLastRoom { nullptr }; + std::list m_cameraInRooms {}; private: void computeRoomBoundingBox(RoomDef& d); diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh index 062bb60..23f0163 100644 --- a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.fsh @@ -13,6 +13,7 @@ struct Material vec4 gm_vZBiasOffset; vec4 v4Opacity; vec4 v4Bias; + float fZOffset; int alphaREF; // Textures diff --git a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh index 8a90dbd..8bb0bf1 100644 --- a/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh +++ b/BMEdit/Editor/Resources/Shaders/TexturedEntity_GL33.vsh @@ -21,6 +21,29 @@ struct Transform mat4 model; }; +struct Material +{ + // See Common.fx for details + // Common uniforms + vec4 v4DiffuseColor; + vec4 gm_vZBiasOffset; + vec4 v4Opacity; + vec4 v4Bias; + float fZOffset; + int alphaREF; + + // Textures + sampler2D mapDiffuse; + sampler2D mapSpecularMask; + sampler2D mapEnvironment; + sampler2D mapReflectionMask; + sampler2D mapReflectionFallOff; + sampler2D mapIllumination; + sampler2D mapTranslucency; +}; + +uniform Material i_uMaterial; + // Uniforms uniform Camera i_uCamera; uniform Transform i_uTransform; @@ -30,6 +53,10 @@ out vec2 g_TexCoord; void main() { - gl_Position = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); + vec4 vOut = i_uCamera.proj * i_uCamera.view * i_uTransform.model * vec4(aPos.x, aPos.y, aPos.z, 1.0); + vOut -= i_uMaterial.gm_vZBiasOffset; + vOut.z -= i_uMaterial.fZOffset; + + gl_Position = vOut; g_TexCoord = aUV; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp index e357cc6..f0b0a9e 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp @@ -1,20 +1,28 @@ #include +#include +#include +#include using namespace models; +static constexpr std::string_view kScriptC = "ZScriptC"; +static const std::string kScriptNamePN = "ScriptName"; + SceneObjectControllerModel::SceneObjectControllerModel(QObject *parent) : ValueModelBase(parent) { - connect(this, &ValueModelBase::valueChanged, [=]() { onValueChanged(); }); + connect(this, &ValueModelBase::valueChanged, [this]() { onValueChanged(); }); } void SceneObjectControllerModel::setGeom(gamelib::scene::SceneObject *geom) { const bool isNewGeom = m_geom != geom; beginResetModel(); + + m_specialRows.clear(); m_geom = geom; - m_currentControllerIndex = -1; + m_currentControllerIndex = kUnset; endResetModel(); if (isNewGeom) @@ -26,8 +34,11 @@ void SceneObjectControllerModel::setGeom(gamelib::scene::SceneObject *geom) void SceneObjectControllerModel::resetGeom() { beginResetModel(); + + // and after that we've ready to do smth else + m_specialRows.clear(); m_geom = nullptr; - m_currentControllerIndex = -1; + m_currentControllerIndex = kUnset; endResetModel(); resetValue(); @@ -40,27 +51,162 @@ void SceneObjectControllerModel::setControllerIndex(int controllerIndex) return; } + auto& controller = m_geom->getControllers().at(controllerIndex); + beginResetModel(); + + // reset special rows info + m_specialRows.clear(); + + // reset controller index m_currentControllerIndex = controllerIndex; endResetModel(); - const auto& controller = m_geom->getControllers().at(controllerIndex); - setValue(controller.properties); + if (controller.type->getName() == kScriptC) + { + // Ok, need handle this correctly + gamelib::Value proxy { controller.properties }; + + // And here we need to update proxy views (just add mappings) + addSugarViews(controller.type, proxy, proxy.getObject(kScriptNamePN)); + + // And store it here + setValue(proxy); + } + else + { + // Default way + setValue(controller.properties); + } } void SceneObjectControllerModel::resetController() { beginResetModel(); - m_currentControllerIndex = -1; + m_currentControllerIndex = kUnset; endResetModel(); resetValue(); } +QVariant SceneObjectControllerModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::BackgroundRole) + { + for (const auto& specialRow : m_specialRows) + { + if (specialRow.includes(index.row())) + { + return specialRow.backgroundColor; + } + } + } + + // other requests redirect to root logic + return ValueModelBase::data(index, role); +} + +void SceneObjectControllerModel::addSugarViews(const gamelib::Type* pControllerType, gamelib::Value &v, const std::string &scriptName) +{ + // Ok, it must be pretty easy. First of all we need to find a script description in TypeRegistry + // Then we need to copy all unexposed instructions to 'v' bucket and add views for VARIABLE entries + const auto asDefault = pControllerType->makeDefaultPropertiesPack(); //TODO: Less hacks, please + const int baseViewsNr = asDefault.getEntries().size(); + const int baseSize = asDefault.getInstructions().size(); + + SpecialRow row {}; + row.endRow = row.startRow = baseSize; + row.backgroundColor = QColor(26, 188, 156); + + if (auto scriptInfo = gamelib::TypeRegistry::getInstance().getScriptInfo(scriptName); scriptInfo.has_value()) + { + // Ok, let's insert that data + for (const auto& ent : scriptInfo.value().entries) + { + // need to rebase this thing + gamelib::ValueEntry temp = ent; + temp.instructions.iOffset += baseSize; + v += temp; + ++row.endRow; // I'm not sure that +1 is enough here, but += temp.instructions.iSize is not valid too! + } + + // save new row + m_specialRows.emplace_back(row); + } +} + +void SceneObjectControllerModel::removeSugarViews(const gamelib::Type* pControllerType, gamelib::Value &v) +{ + // Just reset views & entries from owner type + if (pControllerType->getKind() == gamelib::TypeKind::COMPLEX) + { + v.removeEntriesAndViewsSince( + pControllerType->makeDefaultPropertiesPack().getEntries().size() + ); + } +} + void SceneObjectControllerModel::onValueChanged() { - if (m_geom && m_currentControllerIndex != -1 && m_currentControllerIndex >= 0 && m_currentControllerIndex < m_geom->getControllers().size() && getValue().has_value()) + if (m_geom && m_currentControllerIndex != kUnset && m_currentControllerIndex >= 0 && m_currentControllerIndex < m_geom->getControllers().size() && getValue().has_value()) { - m_geom->getControllers().at(m_currentControllerIndex).properties = getValue().value(); + auto& controller = m_geom->getControllers().at(m_currentControllerIndex); + + if (controller.type->getName() == kScriptC) + { + // Here we need to drop our modified views and store 'typed' original views + gamelib::Value proxy = getValue().value(); + + const bool bScriptChanged = controller.properties.getObject(kScriptNamePN) != proxy.getObject(kScriptNamePN); + const std::string kNewScriptName = proxy.getObject(kScriptNamePN); + + if (bScriptChanged && gamelib::TypeRegistry::getInstance().hasScriptInfo(kNewScriptName)) + { + // Take original first instruction + std::vector instructions = { + getValue().value().getInstructions()[0] + }; + + // Nice! Now we've ready to append a new bunch of properties + const auto scriptInfo = gamelib::TypeRegistry::getInstance().getScriptInfo(kNewScriptName); + + for (const auto& instruction : scriptInfo->initialInstructions) + { + instructions.push_back(instruction); + } + + // And remember: don't forget about SkipMark opcode. Game iterating until not see this opcode and will crash + instructions.emplace_back(gamelib::prp::PRPOpCode::SkipMark); + + // Create new properties bucket + gamelib::Value newProperties { controller.type, instructions }; + + // Add 1 entry (original property markup) + newProperties += getValue().value().getEntries()[0]; + + // Save + controller.properties = newProperties; + + // And add extra stubs + for (const auto& ent : scriptInfo->entries) + newProperties += ent; + + // Done + setValue(newProperties); + } + else + { + // Desugar this stub + removeSugarViews(controller.type, proxy); + + // Save + controller.properties = proxy; + } + } + else + { + // Default way + controller.properties = getValue().value(); + } } } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp index 654143e..a9b0ef2 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectPropertiesModel.cpp @@ -76,7 +76,7 @@ namespace models if (value.value() != m_geom->getProperties()) { - m_geom->getProperties() = value.value(); + m_geom->setProperties(value.value()); emit objectPropertiesChanged(m_geom); } diff --git a/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp b/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp index 255b91c..1ddef98 100644 --- a/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/GeomControllersWidget.cpp @@ -211,6 +211,7 @@ void GeomControllersWidget::addControllerToGeom(const QString& controllerName) gamelib::scene::SceneObject::Controller& newController = m_sceneObject->getControllers().emplace_back(); newController.name = serializeControllerName(pType->getName()); newController.properties = pType->makeDefaultPropertiesPack(); + newController.type = pType; // Update UI updateAvailableControllersList(); diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 39027ec..b40ecc3 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -367,11 +367,14 @@ namespace widgets const auto vMouseClickPos = event->position(); // If no rooms on level we should use ROOT as initial point (not recommended in MOST cases) - auto result = performRayCastToScene(vMouseClickPos, (!m_pLastRoom && m_rooms.empty()) ? m_pLevel->getSceneObjects()[0] : nullptr); - if (!result.empty()) - { - emit worldSelectionChanged(result); - } + // Two step raycast: 1 - to room bbox (allow to run ray from room) + // 2 - to in-room objects + +// auto result = performRayCastToScene(vMouseClickPos, (!m_pLastRoom && m_rooms.empty()) ? m_pLevel->getSceneObjects()[0] : nullptr); +// if (!result.empty()) +// { +// emit worldSelectionChanged(result); +// } } } @@ -394,7 +397,6 @@ namespace widgets invalidateRenderList(); resetViewMode(); resetRenderMode(); - resetLastRoom(); } } @@ -407,7 +409,6 @@ namespace widgets m_pLevel = nullptr; m_bFirstMouseQuery = true; invalidateRenderList(); - resetLastRoom(); resetViewMode(); resetRenderMode(); repaint(); @@ -532,26 +533,6 @@ namespace widgets } } - bool SceneRenderWidget::isGameObjectInActiveRoom(const gamelib::scene::SceneObject::Ptr& pObject) const - { - if (!m_pLastRoom || !pObject) - { - return false; - } - - auto primId = getGameObjectPrimitiveId(pObject); - if (primId == 0) - { - return false; - } - - const Model& model = m_resources->m_models[m_resources->m_modelsCache[primId]]; - glm::mat4 mWorldTransform = getGameObjectTransform(pObject); - gamelib::BoundingBox modelWorldBoundingBox = gamelib::BoundingBox::toWorld(model.boundingBox, mWorldTransform); - - return m_pLastRoom->vBoundingBox.intersect(modelWorldBoundingBox); - } - int32_t SceneRenderWidget::getGameObjectPrimitiveId(const gamelib::scene::SceneObject::Ptr& pObject) const { if (!pObject) return 0; @@ -647,25 +628,16 @@ namespace widgets { render::Ray sRay = m_camera.getRayFromScreen(static_cast(screenSpace.x()), static_cast(screenSpace.y())); + std::vector collectedObjects {}; gamelib::scene::SceneObject* pRoot = pStartObject.get(); - if (!pRoot) - { - if (m_pLastRoom && !m_pLastRoom->rRoom.expired()) - { - pRoot = m_pLastRoom->rRoom.lock().get(); - } - } - // Need to find intersects with this thing. Need to visit only current room if (pRoot) { using R = gamelib::scene::SceneObject::EVisitResult; - std::vector collectedObjects {}; - - static RayCastObjectDescription::EPriority s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; + static EObjectPriority s_CurrentPrio = EObjectPriority::EP_STATIC_OBJECT; auto hitObjVisitor = [&sRay, &collectedObjects, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { if (auto bbox = getGameObjectBoundingBox(pObject); bbox.has_value()) @@ -685,11 +657,11 @@ namespace widgets }; // Hit dynamic (not implemented yet) - s_CurrentPrio = RayCastObjectDescription::EPriority::EP_DYNAMIC_OBJECT; // Now dynamic objects + s_CurrentPrio = EObjectPriority::EP_DYNAMIC_OBJECT; // Now dynamic objects // TODO: Iterate over dynamic objects and check collision with them // Hit static - s_CurrentPrio = RayCastObjectDescription::EPriority::EP_STATIC_OBJECT; // Now static objects + s_CurrentPrio = EObjectPriority::EP_STATIC_OBJECT; // Now static objects pRoot->visitChildren(hitObjVisitor); // Sort hit list by distance to camera @@ -698,7 +670,7 @@ namespace widgets return collectedObjects; } - return {}; + return collectedObjects; } void SceneRenderWidget::onRedrawRequested() @@ -1241,89 +1213,94 @@ namespace widgets else { // Try to render - if (m_pLastRoom) + for (const auto& pRoom : m_cameraInRooms) { - if (!m_pLastRoom->rRoom.expired()) + for (const SeebleObject& sObject : pRoom->vObjects) { - // First of all let's render only current room - collectRenderEntriesIntoRenderList(m_pLastRoom->rRoom.lock().get(), entries, stats, bIgnoreVisibility); - } - - // Let's check if we can see any portal - we need to render that room. - if (!m_pLastRoom->aExists.empty()) - { - // Iterate over all exits and check what exit camera can see right now - for (const auto& eXit : m_pLastRoom->aExists) + if (m_camera.canSeeObject(sObject.sBoundingBox)) { - gamelib::Plane sPlane { eXit.v0, eXit.v1, eXit.v2, eXit.v3 }; - - if (m_camera.canSeeObject(sPlane)) - { - const auto& pRoom = m_pLevel->getSceneObjectByInstanceID(eXit.iRoomREF); - if (pRoom) - { - // We can see another room - save it - collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); - // in theory 1 room is enough, but... idk) - } - } + // Need to render it + collectRenderEntriesIntoRenderList(sObject.pObject.get(), entries, stats, bIgnoreVisibility, true); } } + } +// if (m_pLastRoom) +// { +// if (!m_pLastRoom->rRoom.expired()) +// { +// // First of all let's render only current room +// collectRenderEntriesIntoRenderList(m_pLastRoom->rRoom.lock().get(), entries, stats, bIgnoreVisibility); +// } +// +// // Let's check if we can see any portal - we need to render that room. +// if (!m_pLastRoom->aExists.empty()) +// { +// // Iterate over all exits and check what exit camera can see right now +// for (const auto& eXit : m_pLastRoom->aExists) +// { +// gamelib::Plane sPlane { eXit.v0, eXit.v1, eXit.v2, eXit.v3 }; +// +// if (m_camera.canSeeObject(sPlane)) +// { +// const auto& pRoom = m_pLevel->getSceneObjectByInstanceID(eXit.iRoomREF); +// if (pRoom) +// { +// // We can see another room - save it +// collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); +// // in theory 1 room is enough, but... idk) +// } +// } +// } +// } // Try to render dynamic objects - const auto& vObjects = m_pLevel->getSceneObjects(); - auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { - return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); - }); - - gamelib::scene::SceneObject* pDynRoot = nullptr; - - if (it != vObjects.end()) - { - // Add this 'ROOM' as another thing to visit - pDynRoot = it->get(); - } - else - { - // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( - using R = gamelib::scene::SceneObject::EVisitResult; - - pDynRoot = m_pLevel->getSceneObjects()[0].get(); - } - - // Visit dynamic root - using R = gamelib::scene::SceneObject::EVisitResult; - - pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - static const std::array kAllowedTypes = { "ZActor", "ZPlayer", "ZItem", "ZItemAmmo", "ZLNKOBJ" }; - - for (const auto& sEntBaseName : kAllowedTypes) - { - if (pObject->isInheritedOf("ZROOM")) - { - return R::VR_NEXT; // Skip all rooms - } - - // If object based on ... - if (pObject->isInheritedOf(sEntBaseName)) - { - if (isGameObjectInActiveRoom(pObject) || m_rooms.empty()) - { - collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); - return R::VR_NEXT; // accepted, jump to next - } - } - } - - // Go deeper - return R::VR_CONTINUE; - }); - } - else - { - // Ok, stupid render approach here. Just render everything starts from ROOT (dynamic & static doesn't matter in this case) - collectRenderEntriesIntoRenderList(m_pLevel->getSceneObjects()[0].get(), entries, stats, bIgnoreVisibility); - } +// const auto& vObjects = m_pLevel->getSceneObjects(); +// auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { +// return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); +// }); +// +// gamelib::scene::SceneObject* pDynRoot = nullptr; +// +// if (it != vObjects.end()) +// { +// // Add this 'ROOM' as another thing to visit +// pDynRoot = it->get(); +// } +// else +// { +// // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( +// using R = gamelib::scene::SceneObject::EVisitResult; +// +// pDynRoot = m_pLevel->getSceneObjects()[0].get(); +// } +// +// // Visit dynamic root +// using R = gamelib::scene::SceneObject::EVisitResult; +// +// pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { +// static const std::array kAllowedTypes = { "ZActor", "ZPlayer", "ZItem", "ZItemAmmo", "ZLNKOBJ" }; +// +// for (const auto& sEntBaseName : kAllowedTypes) +// { +// if (pObject->isInheritedOf("ZROOM")) +// { +// return R::VR_NEXT; // Skip all rooms +// } +// +// // If object based on ... +// if (pObject->isInheritedOf(sEntBaseName)) +// { +// if (isGameObjectInActiveRoom(pObject) || m_rooms.empty()) +// { +// collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); +// return R::VR_NEXT; // accepted, jump to next +// } +// } +// } +// +// // Go deeper +// return R::VR_CONTINUE; +// }); } // Add debug stuff @@ -1411,7 +1388,7 @@ namespace widgets }); } - void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility) // NOLINT(*-no-recursion) + void SceneRenderWidget::collectRenderEntriesIntoRenderList(const gamelib::scene::SceneObject* geom, render::RenderEntriesList& entries, RenderStats& stats, bool bIgnoreVisibility, bool bBreakOnChild) // NOLINT(*-no-recursion) { const bool bInvisible = geom->getProperties().getObject("Invisible", false); const auto vPosition = geom->getPosition(); @@ -1593,6 +1570,9 @@ namespace widgets } } + if (bBreakOnChild) + return; + // Visit others for (const auto& child : geom->getChildren()) { @@ -1612,36 +1592,37 @@ namespace widgets // Enable or disable blending if (renderState.isBlendEnabled()) { gapi->glEnable(GL_BLEND); + + // Set blend mode based on your enum values + switch (renderState.getBlendMode()) + { + case gamelib::mat::MATBlendMode::BM_TRANS: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD: + gapi->glBlendFunc(GL_ONE, GL_ONE); + gapi->glEnable(GL_BLEND); + break; + default: + // Do nothing + break; + } } else { gapi->glDisable(GL_BLEND); } - // Set blend mode based on your enum values - switch (renderState.getBlendMode()) - { - case gamelib::mat::MATBlendMode::BM_TRANS: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - default: - // Do nothing - break; - } - // Enable or disable alpha testing if (renderState.isAlphaTestEnabled()) { gapi->glEnable(GL_ALPHA_TEST); @@ -1656,13 +1637,15 @@ namespace widgets gapi->glDisable(GL_FOG); } +#if 0 // Enable or disable depth offset (Z bias) if (renderState.hasZBias()) { gapi->glEnable(GL_POLYGON_OFFSET_FILL); - gapi->glPolygonOffset(1.0f, renderState.getZOffset()); + gapi->glPolygonOffset(2.0f, renderState.getZOffset()); } else { gapi->glDisable(GL_POLYGON_OFFSET_FILL); } +#endif // Set cull mode based on your enum values switch (renderState.getCullMode()) @@ -1708,10 +1691,11 @@ namespace widgets // TODO: Need to move into constants shader->setUniform(glFunctions, "i_uMaterial.v4DiffuseColor", entry.material.vDiffuseColor); - shader->setUniform(glFunctions, "i_uMaterial.gm_vZBiasOffset", entry.material.gm_vZBiasOffset); + shader->setUniform(glFunctions, "i_uMaterial.gm_vZBiasOffset", entry.material.renderState.hasZBias() ? entry.material.gm_vZBiasOffset : glm::vec4(0.f)); shader->setUniform(glFunctions, "i_uMaterial.v4Opacity", entry.material.v4Opacity); shader->setUniform(glFunctions, "i_uMaterial.v4Bias", entry.material.v4Bias); shader->setUniform(glFunctions, "i_uMaterial.alphaREF", std::clamp(entry.material.iAlphaREF, 0, 255)); + shader->setUniform(glFunctions, "i_uMaterial.fZOffset", entry.material.renderState.getZOffset()); // Bind textures for (int slotIdx = render::TextureSlotId::kMapDiffuse; slotIdx < render::TextureSlotId::kMaxTextureSlot; slotIdx++) @@ -1765,7 +1749,7 @@ namespace widgets const auto& children = pRoom->getChildren(); gamelib::scene::SceneObject* pCollisionMesh = nullptr; pRoom->visitChildren([&pCollisionMesh, sTargetName = pRoom->getName()](const gamelib::scene::SceneObject::Ptr& pObject) -> R { - if (pObject->getName() == sTargetName && pObject->getType()->getName() == "ZSTDOBJ") + if (pObject->getName() == sTargetName/* && pObject->getType()->getName() == "ZSTDOBJ"*/) { pCollisionMesh = pObject.get(); return R::VR_STOP_ALL; @@ -1782,48 +1766,70 @@ namespace widgets // Nice, collision mesh was found! Just use it as source for bbox of ZROOM auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; d.vBoundingBox = gamelib::BoundingBox::toWorld(sBoundingBox, pCollisionMesh->getWorldTransform()); + d.eBoundingBoxSource = RoomDef::EBoundingBoxSource::BBS_ROOM_COLLISION_MESH; return; } } + // Ok, let's try to find all ZBOUND objects and make BoundingBox + gamelib::BoundingBox sTempBbox {}; + int iZBoundObjsFound = 0; + pRoom->visitChildren([this, &sTempBbox, &iZBoundObjsFound](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (pObject && pObject->getType()->getName() == "ZBOUND") + { + if (auto bbox = getGameObjectBoundingBox(pObject.get()); bbox.has_value()) + { + ++iZBoundObjsFound; + sTempBbox.expand(bbox.value()); + } + } + + return R::VR_NEXT; // Never go inside + }); + + if (iZBoundObjsFound > 0) + { + d.vBoundingBox = sTempBbox; + d.eBoundingBoxSource = RoomDef::EBoundingBoxSource::BBS_ZBOUNDS_AUTO_EXPAND; + return; + } + + // Old and hard way: just collect all visible objects with bboxes and combine them all into 1 single big bbox bool bBboxInited = false; pRoom->visitChildren([&](const gamelib::scene::SceneObject::Ptr& pObject) -> R { if (!pObject) return R::VR_NEXT; - auto iPrimId = getGameObjectPrimitiveId(pObject); - if (!iPrimId) - return R::VR_CONTINUE; // Go deeper - - // Find prim cache - if (m_resources->m_modelsCache.contains(iPrimId)) + if (auto bbox = getGameObjectBoundingBox(pObject.get()); bbox.has_value()) { - auto sBoundingBox = m_resources->m_models[m_resources->m_modelsCache[iPrimId]].boundingBox; - gamelib::BoundingBox vWorldBoundingBox = gamelib::BoundingBox::toWorld(sBoundingBox, pObject->getWorldTransform()); - if (!bBboxInited) { bBboxInited = true; - d.vBoundingBox = vWorldBoundingBox; + d.vBoundingBox = bbox.value(); } else { - d.vBoundingBox.expand(vWorldBoundingBox); + d.vBoundingBox.expand(bbox.value()); } - // Optimisation: we've assumed that when we have an object with bbox we will use top bbox instead of compute sub-bboxes return R::VR_NEXT; } return R::VR_CONTINUE; }); + + d.eBoundingBoxSource = RoomDef::EBoundingBoxSource::BBS_AUTO_ROOM_EXPAND; } } void SceneRenderWidget::buildRoomCache(QOpenGLFunctions_3_3_Core* glFunctions) { + using R = gamelib::scene::SceneObject::EVisitResult; + + // clear caches m_rooms.clear(); + m_cameraInRooms.clear(); // Save pointer to BUF file const auto bufFileView = m_pLevel->getStaticBuffer(); @@ -1839,29 +1845,9 @@ namespace widgets if (locationsIt != m_pLevel->getSceneObjects().end()) { // we've able to use standard workflow - // Save backdrop - m_pLevel->forEachObjectOfType("ZBackdrop", [this](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { - if (pObject) - { - auto& room = m_rooms.emplace_back(); - room.rRoom = pObject; - room.eLocation = RoomDef::ELocation::eUNDEFINED; - - // ZBackdrop always has maximum possible size to see it from any point of the world - constexpr float kMinPoint = std::numeric_limits::min(); - constexpr float kMaxPoint = std::numeric_limits::max(); - room.vBoundingBox = gamelib::BoundingBox(glm::vec3(kMinPoint), glm::vec3(kMaxPoint)); - - return false; - } - - return true; - }); - // Find ZROOMs const gamelib::scene::SceneObject::Ptr& pNewRoot = *locationsIt; - using R = gamelib::scene::SceneObject::EVisitResult; pNewRoot->visitChildren([this, bufFileView](const gamelib::scene::SceneObject::Ptr& pObject) -> R { if (!pObject) { @@ -1875,13 +1861,21 @@ namespace widgets room.rRoom = pObject; //room.eLocation - const auto iLocation = pObject->getProperties().getObject("Location", 0); - if (iLocation >= 0 && iLocation <= 3) + static const std::map s_LocNameToKind { + { "eBOTH", RoomDef::ELocation::eBOTH }, + { "eINSIDE", RoomDef::ELocation::eINSIDE }, + { "eOUTSIDE", RoomDef::ELocation::eOUTSIDE }, + { "eUNDEFINED", RoomDef::ELocation::eUNDEFINED } + }; + const auto sLocation = pObject->getProperties().getObject("Location", ""); + + if (auto it = s_LocNameToKind.find(sLocation); it != s_LocNameToKind.end()) { - room.eLocation = static_cast(iLocation); + room.eLocation = it->second; } else { + room.eLocation = RoomDef::ELocation::eUNDEFINED; assert(false && "Unknown room type, room will be ignored in optimisations loop"); } @@ -1925,6 +1919,22 @@ namespace widgets // Compute room dimensions computeRoomBoundingBox(room); + // Collect objects list (all visible objects from room + dynamics from scene) + pObject->visitChildren([&room, this](const gamelib::scene::SceneObject::Ptr& pObj) -> R { + if (pObj->is("ZROOM")) return R::VR_CONTINUE; + + if (auto bbox = getGameObjectBoundingBox(pObj); bbox.has_value()) + { + SeebleObject& sObject = room.vObjects.emplace_back(); + sObject.pObject = pObj; + sObject.ePrio = EObjectPriority::EP_STATIC_OBJECT; + sObject.sBoundingBox = bbox.value(); + return R::VR_NEXT; // Go to next + } + return R::VR_CONTINUE; // go deeper + }); + + // TODO: Collect dynamic objects (remember: when collecting remember to check that object is in room bounding box) return R::VR_NEXT; } @@ -1932,6 +1942,45 @@ namespace widgets return R::VR_CONTINUE; }); } + else + { + // No rooms found. Need to generate 1 big room + RoomDef& sVirtualRoom = m_rooms.emplace_back(); + + // Use really huge bbox (FLT32_MIN;FLT32_MIN;FLT32_MIN) (FLT32_MAX; FLT32_MAX; FLT32_MAX) + sVirtualRoom.vBoundingBox = gamelib::BoundingBox( + glm::vec3( + std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::min() + ), + glm::vec3( + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max() + ) + ); + + // Set location & flags + sVirtualRoom.eLocation = RoomDef::ELocation::eUNDEFINED; + sVirtualRoom.bIsVirtualBigRoom = true; + + // Collect objects + m_pLevel->getSceneObjects()[0]->visitChildren([&sVirtualRoom, this](const gamelib::scene::SceneObject::Ptr& pObj) -> R { + if (pObj->is("ZROOM")) return R::VR_CONTINUE; + + if (auto bbox = getGameObjectBoundingBox(pObj); bbox.has_value()) + { + SeebleObject& sObject = sVirtualRoom.vObjects.emplace_back(); + sObject.pObject = pObj; + sObject.ePrio = EObjectPriority::EP_STATIC_OBJECT; // Idk, but in this case all objects are STATIC + sObject.sBoundingBox = bbox.value(); + return R::VR_NEXT; // Go to next + } + + return R::VR_CONTINUE; // go deeper + }); + } // Upload debug geom for (auto& room : m_rooms) @@ -2000,69 +2049,52 @@ namespace widgets } } - void SceneRenderWidget::resetLastRoom() - { - m_pLastRoom = nullptr; - } - void SceneRenderWidget::updateCameraRoomAttachment(RenderStats& stats, bool bRejectLastResult) { - stats.currentRoom.clear(); - - if (bRejectLastResult) - { - m_pLastRoom = nullptr; - } - - if (!m_pLevel || m_rooms.empty()) - { - m_pLastRoom = nullptr; - return; - } + m_cameraInRooms.clear(); - // First of all check that we out of our current room - if (m_pLastRoom) + for (const auto& sRoom : m_rooms) { - if (m_pLastRoom->vBoundingBox.contains(m_camera.getPosition())) + if (sRoom.vBoundingBox.contains(m_camera.getPosition())) { - stats.currentRoom = QString::fromStdString(m_pLastRoom->rRoom.lock()->getName()); - return; // Do nothing + m_cameraInRooms.emplace_back(&sRoom); } - - // Reject current room - m_pLastRoom = nullptr; } - std::list foundInRooms {}; +#if 0 + /** + * Here is a place from hell. We need to know in which room camera and what rooms we can see from this place. + * + * First: + * IDK how to solve + * + * Second: + * Each room has "exits" and "neighbours". We just need to check what planes we can see from this room and this pos + dir (camera) + */ + std::list roomCandidates {}; + for (const auto& sRoom : m_rooms) { if (sRoom.vBoundingBox.contains(m_camera.getPosition())) { - foundInRooms.emplace_back(&sRoom); + roomCandidates.emplace_back(&sRoom); } } - if (foundInRooms.empty()) - return; // Out of rooms - - // Then need to sort found rooms list. Firstly we need to have eINSIDE rooms - foundInRooms.sort([](const RoomDef* a, const RoomDef* b) { - if (a->vBoundingBox.getVolume() < b->vBoundingBox.getVolume()) - { - return true; - } + if (roomCandidates.empty()) + return; - if (a->eLocation == RoomDef::ELocation::eINSIDE && a->eLocation == RoomDef::ELocation::eOUTSIDE) - return true; + roomCandidates.sort([](const RoomDef* a, const RoomDef* b) { return a->eLocation < b->eLocation; }); - return static_cast(a->eLocation) > static_cast(b->eLocation); - }); + RoomDef::ELocation currentLocation = (*roomCandidates.begin())->eLocation; - // Now, use first found room - // NOTE: Maybe we've better to check that top room is preferable for us? Idk - m_pLastRoom = (*foundInRooms.begin()); + for (const auto& sRoom : roomCandidates) + { + if (sRoom->eLocation != currentLocation) + continue; - // Save room name - stats.currentRoom = QString::fromStdString(m_pLastRoom->rRoom.lock()->getName()); + m_cameraInRooms.emplace_back(sRoom); + } +#endif } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 0c567f1..f08a99b 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -284,8 +284,8 @@ void BMEditMainWindow::onLevelLoadSuccess() ui->sceneGLView->setLevel(const_cast(currentLevel)); // Load controllers index - ui->geomControllers->switchToDefaults(); - ui->geomControllers->resetGeom(); + ui->geomControllers->resetGeom(); // FIRST: Reset geom, save or revert changes + ui->geomControllers->switchToDefaults(); // SECOND: Drop value // Export action ui->menuExport->setEnabled(true); @@ -584,6 +584,7 @@ void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); + // TODO: Move this code to another place!!! gamelib::TypeRegistry::getInstance().reset(); QFile typeRegistryFile("TypesRegistry.json"); @@ -605,7 +606,7 @@ void BMEditMainWindow::loadTypesDataBase() m_operationProgress->setValue(OperationToProgress::DATABASE_PARSED); - if (!registryFile.contains("inc") || !registryFile.contains("db")) + if (!registryFile.contains("inc") || !registryFile.contains("db") || !registryFile.contains("script_incs")) { m_operationCommentLabel->setText("Invalid types database format"); return; @@ -621,6 +622,7 @@ void BMEditMainWindow::loadTypesDataBase() } const auto incPath = registryFile["inc"].get(); + const auto scriptsPath = registryFile["script_incs"].get(); m_operationProgress->setValue(OperationToProgress::LOADING_TYPE_DESCRIPTORS); m_operationCommentLabel->setText(QString("Hash indices loaded (%1), loading types from '%2' folder").arg(typesToHashes.size()).arg(QString::fromStdString(incPath))); @@ -653,10 +655,50 @@ void BMEditMainWindow::loadTypesDataBase() } } + // Here we need to lookup for script declarations and parse them + std::unordered_map scriptInfos; + QDirIterator scriptInfoFolderIterator(QString::fromStdString(scriptsPath), { "*.json" }, QDir::Files); + while (scriptInfoFolderIterator.hasNext()) + { + auto path = scriptInfoFolderIterator.next(); + + QFile scriptDescriptionFile(path); + if (!scriptDescriptionFile.open(QIODevice::ReadOnly)) + { + m_operationCommentLabel->setText(QString("ERROR: Failed to open file '%1'").arg(path)); + return; + } + + auto scriptInfoContents = scriptDescriptionFile.readAll().toStdString(); + scriptDescriptionFile.close(); + + nlohmann::json jContents = nlohmann::json::parse(scriptInfoContents, nullptr, false, true); + if (jContents.is_discarded()) + { + qWarning() << "Failed to parse " << path << " (script def)"; + continue; + } + + for (const auto& [key, data] : jContents.items()) + { + if (scriptInfos.contains(key)) + { + qWarning() << "Duplicate script name " << key << " in " << path << " (script def)"; + continue; + } + + scriptInfos[key] = data; + } + } + try { + // register common types registry.registerTypes(std::move(typeInfos), std::move(typesToHashes)); + // register script extensions (extra types) + registry.registerScripts(std::move(scriptInfos)); + QStringList allAvailableTypes; gamelib::TypeRegistry::getInstance().forEachType([&allAvailableTypes](const gamelib::Type *type) { allAvailableTypes.push_back(QString::fromStdString(type->getName())); }); diff --git a/BMEdit/GameLib/Include/GameLib/BoundingBox.h b/BMEdit/GameLib/Include/GameLib/BoundingBox.h index 97479a1..dfa2449 100644 --- a/BMEdit/GameLib/Include/GameLib/BoundingBox.h +++ b/BMEdit/GameLib/Include/GameLib/BoundingBox.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -20,10 +21,12 @@ namespace gamelib glm::vec3 getCenter() const; void expand(const BoundingBox& another); + void expand(const glm::vec3& vPoint); bool contains(const glm::vec3& vPoint) const; bool intersect(const BoundingBox& another) const; - float getVolume() const; + double getVolume() const; + std::tuple getDimensions() const; static BoundingBox toWorld(const BoundingBox& source, const glm::mat4& mTransform); diff --git a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h index ac40d3a..6c19f99 100644 --- a/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h +++ b/BMEdit/GameLib/Include/GameLib/OCT/OCTEntries.h @@ -23,11 +23,22 @@ namespace gamelib::oct struct OCTNode { - uint16_t childCount { 0 }; // It's mask. Real count of objects could be extracted via (childCount >> 3) & 0xFFF + union + { + struct + { + uint16_t unk3 : 3 { 0 }; // Idk + uint16_t childCount : 12 { 0 }; // Count of child objects + uint16_t unk1 : 1 { 0 }; // Idk + } uVal; + uint16_t iVal { 0 }; + } childCountData; uint16_t childIndex { 0 }; // It's index of NODE uint16_t objectIndex { 0 }; - [[nodiscard]] uint16_t getChildCount() const { return (childCount >> 3) & 0xFFF; } + static_assert(sizeof(childCountData) == sizeof(uint16_t), "Bad size of childCountData"); + + [[nodiscard]] uint16_t getChildCount() const { return childCountData.uVal.childCount; } static void deserialize(OCTNode& node, ZBio::ZBinaryReader::BinaryReader* binaryReader); }; diff --git a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h index 0f1b2bb..74c6714 100644 --- a/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h +++ b/BMEdit/GameLib/Include/GameLib/Scene/SceneObject.h @@ -24,10 +24,14 @@ namespace gamelib::scene using Ref = std::weak_ptr; using Instructions = std::vector; + /** + * Scene geom controller description (ZEventBase in Glacier) + */ struct Controller { - std::string name; - Value properties; + std::string name; /// Name of controller + Value properties; /// Properties pack + const Type* type { nullptr }; /// Type of controller (native type, see TypeRegistry for details) bool operator==(const std::string &controllerName) const; bool operator!=(const std::string &controllerName) const; @@ -51,7 +55,7 @@ namespace gamelib::scene [[nodiscard]] const Controllers &getControllers() const; [[nodiscard]] Controllers &getControllers(); [[nodiscard]] const Value &getProperties() const; - [[nodiscard]] Value &getProperties(); + void setProperties(const Value &v); [[nodiscard]] const gms::GMSGeomEntity &getGeomInfo() const; [[nodiscard]] gms::GMSGeomEntity &getGeomInfo(); [[nodiscard]] const SceneObject::Ref &getParent() const; @@ -64,6 +68,12 @@ namespace gamelib::scene */ [[nodiscard]] bool isInheritedOf(const std::string& baseType) const; + /** + * @param type - name of type + * @return true if type equals to required 'type' + */ + [[nodiscard]] bool is(const std::string& type) const; + /** * @brief Calculate transform matrix for OpenGL and other render API buddies * @note This function calculates matrix at runtime. So, it's not huge operation but avoid of frequency calling of this function, please. diff --git a/BMEdit/GameLib/Include/GameLib/TypeRegistry.h b/BMEdit/GameLib/Include/GameLib/TypeRegistry.h index 76bee7b..93acdf1 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeRegistry.h +++ b/BMEdit/GameLib/Include/GameLib/TypeRegistry.h @@ -14,6 +14,13 @@ namespace gamelib { + struct ScriptInfo + { + std::string name; // name of scripts + std::vector entries; // entries in script + std::vector initialInstructions; // contains instructions to produce whole description in PRP + }; + class TypeRegistry { TypeRegistry(); @@ -32,6 +39,11 @@ namespace gamelib std::vector &&typeDeclarations, std::unordered_map &&typeToHash); + void registerScripts(std::unordered_map&& scriptInfoMap); + + [[nodiscard]] std::optional getScriptInfo(const std::string& scriptName); + [[nodiscard]] bool hasScriptInfo(const std::string& scriptName); + [[nodiscard]] const Type *findTypeByName(const std::string &typeName) const; [[nodiscard]] const Type *findTypeByHash(const std::string &hash) const; [[nodiscard]] const Type *findTypeByHash(std::size_t hash) const; @@ -86,5 +98,6 @@ namespace gamelib std::vector> m_types; std::unordered_map m_typesByHash; std::unordered_map m_typesByName; + std::unordered_map m_scriptsByName; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/Value.h b/BMEdit/GameLib/Include/GameLib/Value.h index 68a458d..427b11c 100644 --- a/BMEdit/GameLib/Include/GameLib/Value.h +++ b/BMEdit/GameLib/Include/GameLib/Value.h @@ -74,13 +74,18 @@ namespace gamelib */ Value& operator+=(const std::pair& another); + /** + * Add a new view + */ + Value& operator+=(const ValueEntry& ent); + /** * Extract span of instructions which represents requested property * @param propertyName - name of the property * @return span of instructions * @note This function may throw an exception if requested propertyName not found. You should check your property via hasProperty before ask operator[] */ - Span operator[](const char* propertyName); + Span operator[](const char* propertyName) const; [[nodiscard]] bool operator==(const Value &other) const; [[nodiscard]] bool operator!=(const Value &other) const; @@ -109,6 +114,8 @@ namespace gamelib return def; } + void removeEntriesAndViewsSince(size_t startIndex); + private: const Type *m_type {nullptr}; // type std::vector m_data; // instructions diff --git a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp index d848cda..9937025 100644 --- a/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp +++ b/BMEdit/GameLib/Source/GameLib/BoundingBox.cpp @@ -16,12 +16,18 @@ glm::vec3 BoundingBox::getCenter() const void BoundingBox::expand(const BoundingBox& another) { - min.x = std::min(min.x, another.min.x); - min.y = std::min(min.y, another.min.y); - min.z = std::min(min.z, another.min.z); - max.x = std::max(max.x, another.max.x); - max.y = std::max(max.y, another.max.y); - max.z = std::max(max.z, another.max.z); + expand(another.min); + expand(another.max); +} + +void BoundingBox::expand(const glm::vec3 &vPoint) +{ + min.x = std::min(min.x, vPoint.x); + min.y = std::min(min.y, vPoint.y); + min.z = std::min(min.z, vPoint.z); + max.x = std::max(max.x, vPoint.x); + max.y = std::max(max.y, vPoint.y); + max.z = std::max(max.z, vPoint.z); } bool BoundingBox::contains(const glm::vec3& vPoint) const @@ -43,13 +49,22 @@ bool BoundingBox::intersect(const gamelib::BoundingBox& another) const return true; } -float BoundingBox::getVolume() const +double BoundingBox::getVolume() const { - const float v1 = max.x - min.x; - const float v2 = max.y - min.y; - const float v3 = max.z - min.z; + static auto w = static_cast(max.x - min.x); + static auto h = static_cast(max.y - min.y); + static auto d = static_cast(max.z - min.z); - return v1 * v2 * v3; + return w * h * d; +} + +std::tuple BoundingBox::getDimensions() const +{ + return std::make_tuple( + max.x - min.x, + max.y - min.y, + max.z - min.z + ); } BoundingBox BoundingBox::toWorld(const BoundingBox& source, const glm::mat4& mTransform) diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 2d27d86..896531a 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -37,9 +37,9 @@ namespace gamelib glm::vec3 LevelRooms::RoomGroup::roomToWorld(const glm::i16vec3& vRoom) const { return { - (static_cast(vRoom.x) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.x, - (static_cast(vRoom.y) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.y, - (static_cast(vRoom.z) - 32768.0f) / header.fWorldScale + header.vWorldOrigin.z + static_cast(vRoom.x - 32768) / header.fWorldScale + header.vWorldOrigin.x, + static_cast(vRoom.y - 32768) / header.fWorldScale + header.vWorldOrigin.y, + static_cast(vRoom.z - 32768) / header.fWorldScale + header.vWorldOrigin.z }; } @@ -478,26 +478,26 @@ namespace gamelib // Read inside rooms { - int64_t rmcFileSize = 0; - auto rmcFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::ROOM_TREE_INSIDE, rmcFileSize); + int64_t rmiFileSize = 0; + auto rmiFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::ROOM_TREE_INSIDE, rmiFileSize); - if (!rmcFileBuffer || !rmcFileSize) + if (!rmiFileBuffer || !rmiFileSize) { return false; } - oct::OCTReader rmcReader {}; - const bool bParseResult = rmcReader.parse(rmcFileBuffer.get(), rmcFileSize); + oct::OCTReader rmiReader {}; + const bool bParseResult = rmiReader.parse(rmiFileBuffer.get(), rmiFileSize); if (!bParseResult) { return false; } - m_levelRooms.inside.header = rmcReader.getHeader(); - m_levelRooms.inside.nodes = std::move(rmcReader.takeNodes()); - m_levelRooms.inside.objects = std::move(rmcReader.takeObjects()); - m_levelRooms.inside.ubs = std::move(rmcReader.takeUBS()); + m_levelRooms.inside.header = rmiReader.getHeader(); + m_levelRooms.inside.nodes = std::move(rmiReader.takeNodes()); + m_levelRooms.inside.objects = std::move(rmiReader.takeObjects()); + m_levelRooms.inside.ubs = std::move(rmiReader.takeUBS()); } // Read collisions diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp index f4c5f8c..156d6ee 100644 --- a/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTEntries.cpp @@ -15,7 +15,7 @@ namespace gamelib::oct void OCTNode::deserialize(OCTNode& node, ZBio::ZBinaryReader::BinaryReader* binaryReader) { - node.childCount = binaryReader->read(); + node.childCountData.iVal = binaryReader->read(); node.childIndex = binaryReader->read(); node.objectIndex = binaryReader->read(); } diff --git a/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp index abf92b1..1d4dcec 100644 --- a/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp +++ b/BMEdit/GameLib/Source/GameLib/OCT/OCTReader.cpp @@ -30,7 +30,7 @@ namespace gamelib::oct OCTNode node; OCTNode::deserialize(node, &octReader); - if (node.childCount == 0xCDCDu && node.childIndex == 0xCDCDu && node.objectIndex == 0xCDCDu) + if (node.childCountData.iVal == 0xCDCDu && node.childIndex == 0xCDCDu && node.objectIndex == 0xCDCDu) { // Alignment node. Skip and break break; diff --git a/BMEdit/GameLib/Source/GameLib/PRP/PRPOpCode.cpp b/BMEdit/GameLib/Source/GameLib/PRP/PRPOpCode.cpp index be72342..c15174a 100644 --- a/BMEdit/GameLib/Source/GameLib/PRP/PRPOpCode.cpp +++ b/BMEdit/GameLib/Source/GameLib/PRP/PRPOpCode.cpp @@ -74,7 +74,10 @@ namespace gamelib PRPOpCode fromString(const std::string &asString) { -#define OPSW(x) if (asString.starts_with("PRPOpCode.") && asString.find(#x) != std::string::npos) return PRPOpCode::x; + if (!asString.starts_with("PRPOpCode.")) return PRPOpCode::ERR_UNKNOWN; + const std::string argName = asString.substr(10); + +#define OPSW(x) if (argName == #x) return PRPOpCode::x; OPSW(Array) OPSW(BeginObject) OPSW(Reference) diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index e6d423a..a053a55 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -24,7 +24,7 @@ namespace gamelib::scene bool SceneObject::Controller::operator==(const SceneObject::Controller &other) const { - return name == other.name && properties == other.properties; + return name == other.name && properties == other.properties && type == other.type; } bool SceneObject::Controller::operator!=(const SceneObject::Controller &other) const @@ -88,9 +88,9 @@ namespace gamelib::scene return m_properties; } - Value &SceneObject::getProperties() + void SceneObject::setProperties(const gamelib::Value &v) { - return m_properties; + m_properties = v; } const gms::GMSGeomEntity &SceneObject::getGeomInfo() const @@ -138,6 +138,14 @@ namespace gamelib::scene return false; } + bool SceneObject::is(const std::string& type) const + { + const auto* pType = getType(); + if (!pType) return false; + + return pType->getName() == type; + } + glm::mat4 SceneObject::getLocalTransform() const { const auto vPosition = getPosition(); diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesDumper.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesDumper.cpp index 5be43bf..ecf6755 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesDumper.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesDumper.cpp @@ -88,7 +88,7 @@ void SceneObjectPropertiesDumper::visitSceneObject(const SceneObject *sceneObjec // Controllers out.emplace_back(PRPInstruction(PRPOpCode::Container, PRPOperandVal(static_cast(sceneObject->getControllers().size())))); - for (const auto& [name, properties] : sceneObject->getControllers()) + for (const auto& [name, properties, type] : sceneObject->getControllers()) { out.reserve(3 + properties.getInstructions().size()); diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesLoader.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesLoader.cpp index 536c506..d4f1334 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesLoader.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObjectPropertiesLoader.cpp @@ -111,7 +111,7 @@ namespace gamelib::scene throw SceneObjectVisitorException(objectIdx, "Invalid instructions set (verification failed) [2]"); } - currentObject->getProperties() = *value; + currentObject->setProperties(*value); ip = newIP; // Assign new ip } @@ -192,6 +192,7 @@ namespace gamelib::scene auto& controller = currentObject->getControllers().emplace_back(); controller.name = controllerName; controller.properties = controllerMapResult.value(); + controller.type = controllerType; if (ip[0].getOpCode() != PRPOpCode::EndObject && reinterpret_cast(controllerType)->areUnexposedInstructionsAllowed()) { diff --git a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp index e8727e1..cf9dbfe 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp @@ -60,6 +60,158 @@ namespace gamelib linkTypes(); } + void produceOpCode(ScriptInfo& si, prp::PRPOpCode opCode) + { + prp::PRPInstruction instruction { opCode }; // idk is it ok or not + si.initialInstructions.emplace_back(instruction); + } + + bool collectParameters(const std::unordered_map& base, const nlohmann::json& current, ScriptInfo& info, int& instructionOffset) // NOLINT(*-no-recursion) + { + // Push current parameters on top + if (current.contains("parent")) + { + const std::string parentName = current["parent"].get(); + if (auto it = base.find(parentName); it != base.end()) + { + if (!collectParameters(base, it->second, info, instructionOffset)) + return false; + } + else + { + return false; + } + } + + // Copy parameters to front + if (auto parametersIt = current.find("parameters"); parametersIt != current.end()) + { + for (const auto& item : (*parametersIt)) + { + const std::string kind = item["kind"].get(); + + if (kind == "UNKNOWN") + { + // It's skip block. Just skip N instructions + if (!item.contains("opcodes")) + return false; // bad format + + const auto& opcodes = item["opcodes"]; + for (const auto& opcodeAsStr : opcodes) + { + if (auto opCode = prp::fromString(opcodeAsStr.get()); OPCODE_VALID(opCode)) + { + // store opcode as instruction into info + produceOpCode(info, opCode); + } + else + { + // WTF? + assert(false && "Bad opcode"); + return false; + } + } + + instructionOffset += static_cast(item["opcodes"].size()); + } + else if (kind == "VARIABLE") + { + if (!item.contains("typename") || !item.contains("name")) + return false; + + const std::string typeName = item["typename"].get(); + const std::string varName = item["name"].get(); + + if (typeName.empty() || varName.empty()) + return false; // invalid entry + + // build entry + ValueEntry entry {}; + entry.name = varName; + entry.instructions.iOffset = instructionOffset; + entry.instructions.iSize = 0; // Will calculate later + + // std::make_unique(typeName, aliasTypeAsOpCode); + if (auto opCode = prp::fromString(typeName); OPCODE_VALID(opCode)) + { + // presented as opcode + entry.views.emplace_back(varName, opCode, nullptr); // name, opcode, no owner type + entry.instructions.iSize = 1; // presented as single opcode + + // store opcode as instruction into info + produceOpCode(info, opCode); + } + else + { + // ok, let's try to find a type + if (const auto* pType = TypeRegistry::getInstance().findTypeByName(typeName)) + { + if (pType->getKind() == TypeKind::COMPLEX && reinterpret_cast(pType)->areUnexposedInstructionsAllowed()) + { + // Whoops, it's not allowed to be here! ZScriptC can not refs to ZScriptC! + return false; + } + + entry.views.emplace_back(varName, pType, nullptr); // name, known type, no owner type + entry.instructions.iSize = static_cast(pType->makeDefaultPropertiesPack().getInstructions().size()); // weird way but why not? + + // produce & copy + auto defInstru = pType->makeDefaultPropertiesPack().getInstructions(); + std::copy(defInstru.begin(), defInstru.end(), std::back_inserter(info.initialInstructions)); + } + else + { + // type not found + return false; + } + } + + // And continue work + if (entry.instructions.iSize > 0) + { + instructionOffset += static_cast(entry.instructions.iSize); + info.entries.insert(info.entries.end(), entry); + } // otherwise no reason to save this entry + } + } + } + + return true; + } + + void TypeRegistry::registerScripts(std::unordered_map &&scriptInfoMap) + { + for (const auto& [scriptId, scriptDef] : scriptInfoMap) + { + // Ok, here we need to check if we have parent scripts - we need to collect all parameters from that scripts + ScriptInfo info {}; + info.name = scriptId; + int ip = 0; // base is 0, for the future usage user must add base ip to another ip + if (!collectParameters(scriptInfoMap, scriptDef, info, ip)) + { + // maybe throw something? + continue; + } + + m_scriptsByName[scriptId] = info; + } + } + + std::optional TypeRegistry::getScriptInfo(const std::string &scriptName) + { + if (auto it = m_scriptsByName.find(scriptName); it != m_scriptsByName.end()) + { + return it->second; + } + + return std::nullopt; + } + + bool TypeRegistry::hasScriptInfo(const std::string &scriptName) + { + return m_scriptsByName.contains(scriptName); + } + const Type *TypeRegistry::findTypeByName(const std::string &typeName) const { auto it = m_typesByName.find(typeName); diff --git a/BMEdit/GameLib/Source/GameLib/Value.cpp b/BMEdit/GameLib/Source/GameLib/Value.cpp index cfda6b7..bff5490 100644 --- a/BMEdit/GameLib/Source/GameLib/Value.cpp +++ b/BMEdit/GameLib/Source/GameLib/Value.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -55,7 +56,15 @@ namespace gamelib return *this; } - Span Value::operator[](const char* token) + Value &Value::operator+=(const gamelib::ValueEntry &ent) + { + m_entries.emplace_back(ent); + std::copy(ent.views.begin(), ent.views.end(), std::back_inserter(m_views)); // TODO: Remove? + + return *this; + } + + Span Value::operator[](const char* token) const { if (m_entries.empty()) { @@ -155,4 +164,13 @@ namespace gamelib return false; } + + void Value::removeEntriesAndViewsSince(size_t startIndex) + { + if (startIndex < m_entries.size()) + m_entries.erase(m_entries.begin() + static_cast(startIndex), m_entries.end()); + + if (startIndex < m_views.size()) + m_views.erase(m_views.begin() + static_cast(startIndex), m_views.end()); + } } \ No newline at end of file From db5c0fd680dc27c0425cda96d739940471c8d3c2 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 28 Apr 2024 19:55:41 +0300 Subject: [PATCH 55/80] Try fix build --- BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index f08a99b..be5a586 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -683,7 +683,7 @@ void BMEditMainWindow::loadTypesDataBase() { if (scriptInfos.contains(key)) { - qWarning() << "Duplicate script name " << key << " in " << path << " (script def)"; + qWarning() << "Duplicate script name " << QString::fromStdString(key) << " in " << path << " (script def)"; continue; } From 0d33920944611ea512472f7c1f0110d1ceb5695b Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 28 Apr 2024 20:32:09 +0300 Subject: [PATCH 56/80] Try to fix build again... And added more types to AllLevels and M12 --- Assets/scripts/AllLevels.json | 68 ++++++++++++ Assets/scripts/M12.json | 101 ++++++++++++++++++ .../Models/SceneObjectControllerModel.cpp | 1 + 3 files changed, 170 insertions(+) diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json index 58516cf..642ef70 100644 --- a/Assets/scripts/AllLevels.json +++ b/Assets/scripts/AllLevels.json @@ -547,5 +547,73 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknown" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHidePos" } ] + }, + "alllevels\\armed": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] + }, + "alllevels\\poodle": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rOwnerHuman" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue6" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue7" } + ] } } \ No newline at end of file diff --git a/Assets/scripts/M12.json b/Assets/scripts/M12.json index 925fdc0..97fa788 100644 --- a/Assets/scripts/M12.json +++ b/Assets/scripts/M12.json @@ -206,6 +206,15 @@ ] }, "m12\\m12_marinecameraguard": { "parent": "m12\\m12_marinebase" }, + "m12\\m12_marinexray": { + "parent": "m12\\m12_marinebase", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSecurityCheckpoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardPoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rList_IgnoreHitmanAs" } + ] + }, + "m12\\m12_marineentrance": { "parent": "m12\\m12_marinexray" }, "m12\\m12_suitcasebasementcontroller": { "parameters": [ { @@ -219,5 +228,97 @@ "name": "rIDK" } ] + }, + "m12\\m12_albinoassassin": { + "parent": "alllevels\\armed", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPathOvalOffice" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosMrXIntermezzo" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrX" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrX_CheckBox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosDialogue_Hitman" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosDialogue_Albino" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_HitmanNearOvalOffice_inside" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_HitmanNearOvalOffice_outside" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPoshitmanNearingOutsided" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHitmanEnterOvalOffice" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBombActivatorItem" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rVirtualAimPoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCoverBoxesList" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPathToTheRoof" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fSeeHitmanDistance" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBombsList" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rListVisibleBombs" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHMHighNoonScaffold" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosHighNoonShootScaffold" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWW_HMHighNoonClimbWall" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosHighNoonShootClimbWall" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBombController" } + ] + }, + "m12\\m12_mrx": { + "parent": "alllevels\\armed", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDog" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDogPath_PissOut" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDogPath_PathBack" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFirstLady" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPickUpDog" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMyOffice" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPath_PatrolOffice" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_MrXOfficeHitman" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_OfficeMrX" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHMAS_Carpenter" } + ] + }, + "m12\\m12_officebase": { "parent": "alllevels\\civilian" }, + "m12\\m12_carpenteralt": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownParameter_M12_0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownParameter_M12_1" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosDialog" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCarpenter_01" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rItem" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rToolbox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPathHammerMrX" } + ] + }, + "m12\\m12_justice": { "parent": "alllevels\\poodle" }, + "m12\\m12_firstlady": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIntercom" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIntercomMrX" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDog" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rInterludePos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrX" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rNSA_Guy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rNSA_Office" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWalk_Chamber" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrX_Office" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWalk_NSA" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWalkInterlude" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPathBackToChamber" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_DogExchangeBox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTrigger_StartMrX_FirstLady" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPathChamberWalk" } + ] + }, + "m12\\m12_securitycheckpointguard": { + "parent": "m12\\m12_marinebase", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTargetCivilian" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue_M12_SecurityCheckpointGuard_1" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSuitcaseStorage" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPickUpSuitcase" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardPoint21" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue_M12_SecurityCheckpointGuard_5" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue_M12_SecurityCheckpointGuard_6" } + ] } } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp index f0b0a9e..d20f534 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include From 47395dc1f88c38d77c09882a877ad4cbe39bbd97 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 1 May 2024 10:54:39 +0300 Subject: [PATCH 57/80] Refactored work with models. Added new widgets to select geoms by ZGEOMREF. Added ability to define specific tool to interact with specific game type (only for aliases for now). --- Assets/g1/ZGEOMREF.json | 3 +- .../Delegates/TypePropertyItemDelegate.h | 3 + BMEdit/Editor/Include/Models/ModelsLocator.h | 23 ++ BMEdit/Editor/Include/Types/QGlacierValue.h | 2 +- .../Include/Widgets/EditorToolFactory.h | 13 ++ .../Include/Widgets/TypePropertyWidget.h | 6 +- .../Delegates/TypePropertyItemDelegate.cpp | 76 +++++-- BMEdit/Editor/Source/Models/ModelsLocator.cpp | 7 + .../Source/Widgets/EditorToolFactory.cpp | 20 ++ .../Source/Widgets/SceneRenderWidget.cpp | 39 ++-- .../Source/Widgets/TypePropertyWidget.cpp | 5 + .../Widgets/TypeSimplePropertyWidget.cpp | 2 + BMEdit/Editor/UI/Include/BMEditMainWindow.h | 1 - .../Editor/UI/Include/SelectSceneObjectTool.h | 55 +++++ BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 15 +- .../UI/Source/SelectSceneObjectTool.cpp | 204 ++++++++++++++++++ BMEdit/Editor/UI/UI/SelectSceneObjectTool.ui | 34 +++ BMEdit/GameLib/Include/GameLib/TypeAlias.h | 7 + BMEdit/GameLib/Source/GameLib/TypeAlias.cpp | 15 ++ BMEdit/GameLib/Source/GameLib/TypeFactory.cpp | 20 +- 20 files changed, 499 insertions(+), 51 deletions(-) create mode 100644 BMEdit/Editor/Include/Models/ModelsLocator.h create mode 100644 BMEdit/Editor/Include/Widgets/EditorToolFactory.h create mode 100644 BMEdit/Editor/Source/Models/ModelsLocator.cpp create mode 100644 BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp create mode 100644 BMEdit/Editor/UI/Include/SelectSceneObjectTool.h create mode 100644 BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp create mode 100644 BMEdit/Editor/UI/UI/SelectSceneObjectTool.ui diff --git a/Assets/g1/ZGEOMREF.json b/Assets/g1/ZGEOMREF.json index c40d57e..12ef845 100644 --- a/Assets/g1/ZGEOMREF.json +++ b/Assets/g1/ZGEOMREF.json @@ -1,5 +1,6 @@ { "typename": "ZGEOMREF", "kind": "TypeKind.ALIAS", - "alias": "PRPOpCode.String" + "alias": "PRPOpCode.String", + "editor": "SelectGeomTool" } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Delegates/TypePropertyItemDelegate.h b/BMEdit/Editor/Include/Delegates/TypePropertyItemDelegate.h index dabc368..2b61107 100644 --- a/BMEdit/Editor/Include/Delegates/TypePropertyItemDelegate.h +++ b/BMEdit/Editor/Include/Delegates/TypePropertyItemDelegate.h @@ -25,6 +25,9 @@ namespace delegates const QStyleOptionViewItem &option, const QModelIndex &index) const override; + protected: + bool eventFilter(QObject* editor, QEvent* event) override; + private slots: void commitDataChunk(); void commitDataChunkAndCloseEditor(); diff --git a/BMEdit/Editor/Include/Models/ModelsLocator.h b/BMEdit/Editor/Include/Models/ModelsLocator.h new file mode 100644 index 0000000..ac46635 --- /dev/null +++ b/BMEdit/Editor/Include/Models/ModelsLocator.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + + +namespace models +{ + /** + * @brief This class holds all sharable models to share them between dialogs/widgets/etc. + */ + struct ModelsLocator final + { + ModelsLocator() = default; + ModelsLocator(const ModelsLocator&) = delete; + ModelsLocator(ModelsLocator&&) = delete; + + + // Instances + static std::unique_ptr s_SceneTreeModel; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Types/QGlacierValue.h b/BMEdit/Editor/Include/Types/QGlacierValue.h index c110a88..d1b509c 100644 --- a/BMEdit/Editor/Include/Types/QGlacierValue.h +++ b/BMEdit/Editor/Include/Types/QGlacierValue.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include namespace types diff --git a/BMEdit/Editor/Include/Widgets/EditorToolFactory.h b/BMEdit/Editor/Include/Widgets/EditorToolFactory.h new file mode 100644 index 0000000..1068030 --- /dev/null +++ b/BMEdit/Editor/Include/Widgets/EditorToolFactory.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + + +namespace widgets +{ + struct EditorToolFactory + { + static TypePropertyWidget* createToolById(QWidget* parent, const std::string &hintId); + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Widgets/TypePropertyWidget.h b/BMEdit/Editor/Include/Widgets/TypePropertyWidget.h index 53d7f30..112dc4b 100644 --- a/BMEdit/Editor/Include/Widgets/TypePropertyWidget.h +++ b/BMEdit/Editor/Include/Widgets/TypePropertyWidget.h @@ -17,8 +17,10 @@ namespace widgets public: explicit TypePropertyWidget(QWidget* parent = nullptr); - void setValue(const types::QGlacierValue &value); - [[nodiscard]] const types::QGlacierValue &getValue() const; + virtual void setValue(const types::QGlacierValue &value); + [[nodiscard]] virtual const types::QGlacierValue &getValue() const; + + virtual bool canHookFocus() const; signals: void valueChanged(); diff --git a/BMEdit/Editor/Source/Delegates/TypePropertyItemDelegate.cpp b/BMEdit/Editor/Source/Delegates/TypePropertyItemDelegate.cpp index 84998d4..f7f44d8 100644 --- a/BMEdit/Editor/Source/Delegates/TypePropertyItemDelegate.cpp +++ b/BMEdit/Editor/Source/Delegates/TypePropertyItemDelegate.cpp @@ -4,14 +4,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include #include @@ -53,7 +56,9 @@ namespace delegates static bool isRefTab(const types::QGlacierValue &data) { if (data.views.empty()) + { return false; + } const gamelib::ValueView& view = data.views.at(0); if (auto type = view.getType()) @@ -75,29 +80,46 @@ namespace delegates widgets::TypePropertyWidget *editor = nullptr; auto data = index.data(Qt::EditRole).value(); - if (data.instructions.empty()) - { - // Empty widget - editor = new widgets::TypePropertyWidget(parent); - } - else if (isValueCouldBePresentedBySimpleView(data)) - { - // If trivial thing (int, string, enum (?), bool) we may show simple widgets::TypeSimplePropertyWidget (inherited of widgets::TypePropertyWidget) - editor = new widgets::TypeSimplePropertyWidget(parent); - } - else if (isValueCouldBePresentedAsSimpleVectorWidget(data)) - { - // It's vector (3 elements) - editor = new widgets::TypeVector3PropertyWidget(parent); - } - else if (isValueCouldBePresentedAsSimpleMatrixWidget(data, Matrix3x3::Rows, Matrix3x3::Columns)) + if (data.views.size() == 1) { - // It's matrix 3x3 - editor = new widgets::TypeMatrixPropertyWidget(Matrix3x3::Rows, Matrix3x3::Columns, parent); + if (auto* pType = data.views[0].getType(); pType && pType->getKind() == gamelib::TypeKind::ALIAS) + { + const auto* pAsAlias = reinterpret_cast(pType); + if (pAsAlias->hasToolHint()) + { + // Nice! Use this hint! + const auto& toolHintId = pAsAlias->getToolHint(); + editor = widgets::EditorToolFactory::createToolById(parent, toolHintId); + } + } } - else if (isRefTab(data)) + + if (!editor) { - editor = new widgets::TypeRefTabPropertyWidget(parent); + if (data.instructions.empty()) + { + // Empty widget + editor = new widgets::TypePropertyWidget(parent); + } + else if (isValueCouldBePresentedBySimpleView(data)) + { + // If trivial thing (int, string, enum (?), bool) we may show simple widgets::TypeSimplePropertyWidget (inherited of widgets::TypePropertyWidget) + editor = new widgets::TypeSimplePropertyWidget(parent); + } + else if (isValueCouldBePresentedAsSimpleVectorWidget(data)) + { + // It's vector (3 elements) + editor = new widgets::TypeVector3PropertyWidget(parent); + } + else if (isValueCouldBePresentedAsSimpleMatrixWidget(data, Matrix3x3::Rows, Matrix3x3::Columns)) + { + // It's matrix 3x3 + editor = new widgets::TypeMatrixPropertyWidget(Matrix3x3::Rows, Matrix3x3::Columns, parent); + } + else if (isRefTab(data)) + { + editor = new widgets::TypeRefTabPropertyWidget(parent); + } } if (editor) @@ -193,6 +215,20 @@ namespace delegates } } + bool TypePropertyItemDelegate::eventFilter(QObject* editor, QEvent* event) + { + if (event->type() == QEvent::FocusOut) + { + if (auto* pEditor = qobject_cast(editor); pEditor && pEditor->canHookFocus()) + { + // Do not send this event to delegate. We can handle focus and it's ok + return true; + } + } + + return QStyledItemDelegate::eventFilter(editor, event); + } + void TypePropertyItemDelegate::commitDataChunk() { auto editor = qobject_cast(sender()); diff --git a/BMEdit/Editor/Source/Models/ModelsLocator.cpp b/BMEdit/Editor/Source/Models/ModelsLocator.cpp new file mode 100644 index 0000000..24ceb8b --- /dev/null +++ b/BMEdit/Editor/Source/Models/ModelsLocator.cpp @@ -0,0 +1,7 @@ +#include + + +namespace models +{ + std::unique_ptr ModelsLocator::s_SceneTreeModel { nullptr }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp new file mode 100644 index 0000000..ca4902d --- /dev/null +++ b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp @@ -0,0 +1,20 @@ +#include + +// Widgets +#include + + +namespace widgets +{ + // Base factory + TypePropertyWidget *EditorToolFactory::createToolById(QWidget* parent, const std::string &hintId) + { + TypePropertyWidget* pResult = nullptr; + if (hintId == "SelectGeomTool") + { + pResult = SelectSceneObjectTool::Create(parent); + } + + return pResult; + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index b40ecc3..a869818 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1424,26 +1424,31 @@ namespace widgets if (m_camera.canSeeObject(glm::vec3(modelWorldBoundingBox.min), glm::vec3(modelWorldBoundingBox.max))) { // Add bounding box to render list - if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { - // Need to add mesh - render::RenderEntry &boundingBoxEntry = entries.emplace_back(); + { + std::optional bboxMesh = model.boundingBoxMesh; + // NOTE: Here we need to try locate inner bbox or construct it but it can drop FPS - // Render params - boundingBoxEntry.iPrimitiveId = 0; - boundingBoxEntry.iMeshIndex = 0; - boundingBoxEntry.iTrianglesNr = 0; - boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; + if (geom == m_pSelectedSceneObject && bboxMesh.has_value()) { + // Need to add mesh + render::RenderEntry &boundingBoxEntry = entries.emplace_back(); - // World params - boundingBoxEntry.vPosition = vPosition; - boundingBoxEntry.mWorldTransform = mWorldTransform; - boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); - boundingBoxEntry.pMesh = const_cast(&model.boundingBoxMesh.value()); + // Render params + boundingBoxEntry.iPrimitiveId = 0; + boundingBoxEntry.iMeshIndex = 0; + boundingBoxEntry.iTrianglesNr = 0; + boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; - // Material - render::RenderEntry::Material &material = boundingBoxEntry.material; - material.vDiffuseColor = glm::vec4(0.f, 0.f, 1.f, 1.f); - material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + // World params + boundingBoxEntry.vPosition = vPosition; + boundingBoxEntry.mWorldTransform = mWorldTransform; + boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); + boundingBoxEntry.pMesh = const_cast(&bboxMesh.value()); + + // Material + render::RenderEntry::Material &material = boundingBoxEntry.material; + material.vDiffuseColor = glm::vec4(0.f, 0.f, 1.f, 1.f); + material.pShader = &m_resources->m_shaders[m_resources->m_iGizmoShaderIdx]; + } } // increase allowed objects count diff --git a/BMEdit/Editor/Source/Widgets/TypePropertyWidget.cpp b/BMEdit/Editor/Source/Widgets/TypePropertyWidget.cpp index fd84680..47275fc 100644 --- a/BMEdit/Editor/Source/Widgets/TypePropertyWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/TypePropertyWidget.cpp @@ -29,6 +29,11 @@ namespace widgets { return m_value; } + bool TypePropertyWidget::canHookFocus() const + { + return false; + } + bool TypePropertyWidget::areSame(const types::QGlacierValue ¤t, const types::QGlacierValue &value) { if (current.instructions.size() != value.instructions.size()) return false; diff --git a/BMEdit/Editor/Source/Widgets/TypeSimplePropertyWidget.cpp b/BMEdit/Editor/Source/Widgets/TypeSimplePropertyWidget.cpp index 9316aa2..d9666cc 100644 --- a/BMEdit/Editor/Source/Widgets/TypeSimplePropertyWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/TypeSimplePropertyWidget.cpp @@ -1,9 +1,11 @@ #include #include #include +#include #include #include +#include // Layout #include diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 9825a8c..2ef1055 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -100,7 +100,6 @@ public slots: // Models QStringListModel *m_geomTypesModel { nullptr }; - models::SceneObjectsTreeModel *m_sceneTreeModel { nullptr }; models::SceneFilterModel* m_sceneTreeFilterModel { nullptr }; models::SceneObjectPropertiesModel *m_sceneObjectPropertiesModel { nullptr }; models::ScenePropertiesModel *m_scenePropertiesModel { nullptr }; diff --git a/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h b/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h new file mode 100644 index 0000000..4b067a8 --- /dev/null +++ b/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + + +class Frontend_SelectSceneObjectTool final : public widgets::TypePropertyWidget +{ + Q_OBJECT +public: + Frontend_SelectSceneObjectTool(QWidget* parent, widgets::TypePropertyWidget* pTarget); + + void setValue(const types::QGlacierValue &value) override; + [[nodiscard]] const types::QGlacierValue &getValue() const override; + + bool canHookFocus() const override; + +private: + void commitValue(const types::QGlacierValue &value); + +private: + widgets::TypePropertyWidget* m_pTarget { nullptr }; + QLabel* m_pLabel { nullptr }; +}; + + +namespace Ui { + class SelectSceneObjectTool; +} + +class SelectSceneObjectTool final : public widgets::TypePropertyWidget +{ + Q_OBJECT +public: + explicit SelectSceneObjectTool(QWidget* parent = nullptr); + ~SelectSceneObjectTool() override; + + void setValue(const types::QGlacierValue &value) override; + + static Frontend_SelectSceneObjectTool* Create(QWidget* parent); + +private: + void selectByPath(const QString& path); + +private slots: + void onAccepted(); + +protected: + // buildLayout and updateLayout not implemented because no dynamic layout here. I'm just handling setValue + void closeEvent(QCloseEvent* pEvent) override; + +private: + Ui::SelectSceneObjectTool *m_ui; +}; \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index be5a586..5879e09 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,7 @@ BMEditMainWindow::~BMEditMainWindow() delete m_geomTypesModel; delete m_typePropertyItemDelegate; delete m_sceneTreeFilterModel; - delete m_sceneTreeModel; + models::ModelsLocator::s_SceneTreeModel = nullptr; // reset me delete m_sceneObjectPropertiesModel; delete m_operationProgress; @@ -260,9 +261,9 @@ void BMEditMainWindow::onLevelLoadSuccess() ui->actionRenderMode_RenderRoomBoundingBoxes->setChecked(ui->sceneGLView->shouldRenderRoomBoundingBox()); // Setup models - if (m_sceneTreeModel) + if (models::ModelsLocator::s_SceneTreeModel) { - m_sceneTreeModel->setLevel(currentLevel); + models::ModelsLocator::s_SceneTreeModel->setLevel(currentLevel); } if (m_sceneTexturesModel) @@ -412,7 +413,7 @@ void BMEditMainWindow::onAssetExportFailed(const QString &reason) void BMEditMainWindow::onCloseLevel() { // Cleanup models - if (m_sceneTreeModel) m_sceneTreeModel->resetLevel(); + if (models::ModelsLocator::s_SceneTreeModel) models::ModelsLocator::s_SceneTreeModel->resetLevel(); if (m_sceneObjectPropertiesModel) m_sceneObjectPropertiesModel->resetLevel(); if (m_scenePropertiesModel) m_scenePropertiesModel->resetLevel(); if (m_sceneTexturesModel) m_sceneTexturesModel->resetLevel(); @@ -476,7 +477,7 @@ void BMEditMainWindow::onShowTexturesDialog() void BMEditMainWindow::onContextMenuRequestedForSceneTreeNode(const QPoint& point) { - if (!m_sceneTreeModel) + if (!models::ModelsLocator::s_SceneTreeModel) { return; } @@ -732,9 +733,9 @@ void BMEditMainWindow::resetStatusToDefault() void BMEditMainWindow::initSceneTree() { // Main model - m_sceneTreeModel = new models::SceneObjectsTreeModel(this); + models::ModelsLocator::s_SceneTreeModel = std::make_unique(this); m_sceneTreeFilterModel = new models::SceneFilterModel(this); - m_sceneTreeFilterModel->setSourceModel(m_sceneTreeModel); + m_sceneTreeFilterModel->setSourceModel(models::ModelsLocator::s_SceneTreeModel.get()); ui->sceneTreeView->header()->setSectionResizeMode(QHeaderView::Stretch); ui->sceneTreeView->setModel(m_sceneTreeFilterModel); diff --git a/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp b/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp new file mode 100644 index 0000000..8baa059 --- /dev/null +++ b/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp @@ -0,0 +1,204 @@ +#include "ui_SelectSceneObjectTool.h" +#include +#include +#include +#include +#include +#include + + +Frontend_SelectSceneObjectTool::Frontend_SelectSceneObjectTool(QWidget *parent, widgets::TypePropertyWidget* pTarget) + : widgets::TypePropertyWidget(parent) + , m_pTarget(pTarget) +{ + if (m_pTarget) + { + connect(m_pTarget, &widgets::TypePropertyWidget::valueChanged, [this]() { + commitValue(m_pTarget->getValue()); + emit valueChanged(); + }); + + connect(m_pTarget, &widgets::TypePropertyWidget::editFinished, [this]() { + auto v = m_pTarget->getValue(); + commitValue(v); + emit editFinished(); + }); + + m_pTarget->setWindowModality(Qt::WindowModality::ApplicationModal); + m_pTarget->show(); + } + + // And build layout + auto* pLayout = new QHBoxLayout(this); + m_pLabel = new QLabel(this); + m_pLabel->setText("MAYBE"); + pLayout->addWidget(m_pLabel); + setLayout(pLayout); +} + +void Frontend_SelectSceneObjectTool::setValue(const types::QGlacierValue &value) +{ + if (m_pLabel && !value.instructions.empty() && value.instructions[0].isString()) + { + m_pLabel->setText(QString::fromStdString(value.instructions[0].getOperand().str)); + } + + if (m_pTarget) + { + m_pTarget->setValue(value); + } + + commitValue(value); +} + +const types::QGlacierValue &Frontend_SelectSceneObjectTool::getValue() const +{ + static const types::QGlacierValue s_Invalid {}; + if (!m_pTarget) return s_Invalid; + return m_pTarget->getValue(); +} + +bool Frontend_SelectSceneObjectTool::canHookFocus() const +{ + // Yep it can in this case + return true; +} + +void Frontend_SelectSceneObjectTool::commitValue(const types::QGlacierValue &value) +{ + widgets::TypePropertyWidget::setValue(value); +} + + +SelectSceneObjectTool::SelectSceneObjectTool(QWidget* parent) : widgets::TypePropertyWidget(parent), m_ui(new Ui::SelectSceneObjectTool) +{ + m_ui->setupUi(this); + + // Set model + m_ui->objectsTree->setModel(models::ModelsLocator::s_SceneTreeModel.get()); + m_ui->objectsTree->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + + // Connect signals + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, [this]() { + emit editFinished(); + close(); + }); + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectSceneObjectTool::onAccepted); +} + +SelectSceneObjectTool::~SelectSceneObjectTool() +{ + delete m_ui; +} + +Frontend_SelectSceneObjectTool *SelectSceneObjectTool::Create(QWidget *parent) +{ + auto* pEditor = new SelectSceneObjectTool(nullptr); + auto* pFrontend = new Frontend_SelectSceneObjectTool(parent, pEditor); + return pFrontend; +} + +void SelectSceneObjectTool::setValue(const types::QGlacierValue &value) +{ + // call for base + widgets::TypePropertyWidget::setValue(value); + + if (value.instructions.size() == 1 && value.instructions[0].isString()) + { + // Need to parse path + selectByPath(QString::fromStdString(value.instructions[0].getOperand().str)); + } +} + +void SelectSceneObjectTool::selectByPath(const QString &path) +{ + QStringList pathParts = path.split('\\'); + if (pathParts.empty()) return; + + if (pathParts[0] == "ROOT") + { + // remove ROOT because it literally does not exists :) + pathParts.removeFirst(); + } + + auto* model = qobject_cast(m_ui->objectsTree->model()); + if (!model) + { + return; + } + + QModelIndex currentIndex = QModelIndex(); + + foreach (const QString& part, pathParts) + { + bool found = false; + int rows = model->rowCount(currentIndex); + + for (int i = 0; i < rows; ++i) + { + QModelIndex childIndex = model->index(i, 0, currentIndex); + QString entryName = model->data(childIndex, Qt::DisplayRole).toString(); + + if (entryName == part) + { + currentIndex = childIndex; + found = true; + m_ui->objectsTree->expand(currentIndex); + m_ui->objectsTree->scrollTo(currentIndex); + break; + } + } + + if (!found) + { + return; + } + } + + m_ui->objectsTree->selectionModel()->select(currentIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); +} + +void SelectSceneObjectTool::onAccepted() +{ + // need to set value + const QModelIndexList selectedIndexes = m_ui->objectsTree->selectionModel()->selectedIndexes(); + if (selectedIndexes.isEmpty()) + { + // just do nothing + emit editFinished(); + return; + } + + QModelIndex currentIndex = selectedIndexes.first(); + QStringList pathParts {}; + while (currentIndex.isValid()) + { + pathParts.prepend(currentIndex.data().toString()); + currentIndex = currentIndex.parent(); + } + + // Always include ROOT subject here + pathParts.prepend("ROOT"); + + const auto fullPath = pathParts.join('\\').toStdString(); + auto newVal = getValue(); + if (!newVal.instructions.empty()) + { + // weird but ok + newVal.instructions[0] = gamelib::prp::PRPInstruction(newVal.instructions[0].getOpCode(), gamelib::prp::PRPOperandVal(fullPath)); + + // use base to avoid of extra selector iteration + widgets::TypePropertyWidget::setValue(newVal); + emit editFinished(); + } + + // and close us + close(); +} + +void SelectSceneObjectTool::closeEvent(QCloseEvent *pEvent) +{ + emit editFinished(); + QWidget::closeEvent(pEvent); +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/UI/SelectSceneObjectTool.ui b/BMEdit/Editor/UI/UI/SelectSceneObjectTool.ui new file mode 100644 index 0000000..b8857d3 --- /dev/null +++ b/BMEdit/Editor/UI/UI/SelectSceneObjectTool.ui @@ -0,0 +1,34 @@ + + + SelectSceneObjectTool + + + Qt::NonModal + + + + 0 + 0 + 360 + 700 + + + + Select Scene Object... + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/BMEdit/GameLib/Include/GameLib/TypeAlias.h b/BMEdit/GameLib/Include/GameLib/TypeAlias.h index f0938f9..c58e611 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeAlias.h +++ b/BMEdit/GameLib/Include/GameLib/TypeAlias.h @@ -21,7 +21,14 @@ namespace gamelib [[nodiscard]] const Type* getFinalType() const; [[nodiscard]] prp::PRPOpCode getFinalOpCode() const; + + public: // Additional & tooling + [[nodiscard]] bool hasToolHint() const; + [[nodiscard]] const std::string& getToolHint() const; + void setToolHint(const std::string& toolHint); + private: TypeReference m_resultTypeInfo; + std::string m_toolHint {}; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp b/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp index 15a0fa8..7bd221d 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeAlias.cpp @@ -100,4 +100,19 @@ namespace gamelib return prp::PRPOpCode::ERR_UNKNOWN; } + + bool TypeAlias::hasToolHint() const + { + return !m_toolHint.empty(); + } + + const std::string &TypeAlias::getToolHint() const + { + return m_toolHint; + } + + void TypeAlias::setToolHint(const std::string &toolHint) + { + m_toolHint = toolHint; + } } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/TypeFactory.cpp b/BMEdit/GameLib/Source/GameLib/TypeFactory.cpp index 825e3b9..2b5f87e 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeFactory.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeFactory.cpp @@ -33,12 +33,28 @@ namespace gamelib auto aliasTypeName = alias.get(); auto aliasTypeAsOpCode = prp::fromString(aliasTypeName); + std::unique_ptr finalType = nullptr; + if (OPCODE_VALID(aliasTypeAsOpCode)) { - return std::make_unique(typeName, aliasTypeAsOpCode); + finalType = std::make_unique(typeName, aliasTypeAsOpCode); + } + else + { + finalType = std::make_unique(typeName, aliasTypeName); + } + + // read tool hint + if (json.contains("editor")) + { + const auto& toolHint = json["editor"].get(); + if (!toolHint.empty()) + { + finalType->setToolHint(toolHint); + } } - return std::make_unique(typeName, aliasTypeName); + return finalType; } case TypeKind::COMPLEX: { std::vector properties = {}; From 7813c45ef214668aa519c5d3168d15c4922b971e Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 1 May 2024 19:06:57 +0300 Subject: [PATCH 58/80] Fixed critical bug in SceneRenderWidget with broken reference. Added new assets for script selector dialog. Added script selector dialog (WIP) --- .../Include/Models/GameScriptsTreeModel.h | 50 +++++ BMEdit/Editor/Include/Models/ModelsLocator.h | 2 + BMEdit/Editor/Resources/BMEdit.qrc | 2 + BMEdit/Editor/Resources/Icons/Folder.png | Bin 0 -> 575 bytes .../Editor/Resources/Icons/Glacier/Script.png | Bin 0 -> 1926 bytes .../Source/Models/GameScriptsTreeModel.cpp | 182 ++++++++++++++++++ BMEdit/Editor/Source/Models/ModelsLocator.cpp | 1 + .../Source/Widgets/EditorToolFactory.cpp | 12 ++ .../Source/Widgets/SceneRenderWidget.cpp | 7 +- .../Editor/UI/Include/SelectSceneObjectTool.h | 5 + BMEdit/Editor/UI/Include/SelectScriptTool.h | 63 ++++++ BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 5 + .../UI/Source/SelectSceneObjectTool.cpp | 34 +++- BMEdit/Editor/UI/Source/SelectScriptTool.cpp | 179 +++++++++++++++++ BMEdit/Editor/UI/UI/SelectScriptTool.ui | 31 +++ BMEdit/GameLib/Include/GameLib/TypeRegistry.h | 1 + .../GameLib/Source/GameLib/TypeRegistry.cpp | 13 ++ 17 files changed, 572 insertions(+), 15 deletions(-) create mode 100644 BMEdit/Editor/Include/Models/GameScriptsTreeModel.h create mode 100644 BMEdit/Editor/Resources/Icons/Folder.png create mode 100644 BMEdit/Editor/Resources/Icons/Glacier/Script.png create mode 100644 BMEdit/Editor/Source/Models/GameScriptsTreeModel.cpp create mode 100644 BMEdit/Editor/UI/Include/SelectScriptTool.h create mode 100644 BMEdit/Editor/UI/Source/SelectScriptTool.cpp create mode 100644 BMEdit/Editor/UI/UI/SelectScriptTool.ui diff --git a/BMEdit/Editor/Include/Models/GameScriptsTreeModel.h b/BMEdit/Editor/Include/Models/GameScriptsTreeModel.h new file mode 100644 index 0000000..695112c --- /dev/null +++ b/BMEdit/Editor/Include/Models/GameScriptsTreeModel.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + + +namespace models +{ + class GameScriptsTreeModel : public QAbstractItemModel + { + Q_OBJECT + + public: + struct ScripTreeNode + { + enum class NodeType { + STN_ROOT, /// < Just a ROOT subject. No name, no selector + STN_BONE, /// < Bone (temp node). Unable to use as real path + STN_SCRIPT /// < Script itself. Could be used as final value + }; + + NodeType type { NodeType::STN_ROOT }; + QString name {}; + QString fullPath {}; + QWeakPointer parent {}; + QList> children {}; + + ScripTreeNode(); + ScripTreeNode(NodeType nt, QString&& sn ,QString&& sfn); + }; + + public: + explicit GameScriptsTreeModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + private: + void buildTree(); + + private: + QSharedPointer m_root { nullptr }; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/ModelsLocator.h b/BMEdit/Editor/Include/Models/ModelsLocator.h index ac46635..1bb6bfb 100644 --- a/BMEdit/Editor/Include/Models/ModelsLocator.h +++ b/BMEdit/Editor/Include/Models/ModelsLocator.h @@ -3,6 +3,7 @@ #include #include +#include namespace models @@ -19,5 +20,6 @@ namespace models // Instances static std::unique_ptr s_SceneTreeModel; + static std::unique_ptr s_GameScriptsTreeModel; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Resources/BMEdit.qrc b/BMEdit/Editor/Resources/BMEdit.qrc index e6a5813..34f2845 100644 --- a/BMEdit/Editor/Resources/BMEdit.qrc +++ b/BMEdit/Editor/Resources/BMEdit.qrc @@ -2,6 +2,7 @@ Icons/Remove.png Icons/Add.png + Icons/Folder.png Icons/Glacier/Geom.png Icons/Glacier/Group.png Icons/Glacier/Camera.png @@ -11,6 +12,7 @@ Icons/Glacier/Weapon.png Icons/Glacier/Cloth.png Icons/Glacier/Unknown.png + Icons/Glacier/Script.png Textures/unsupported_material.png Textures/missing_texture.png diff --git a/BMEdit/Editor/Resources/Icons/Folder.png b/BMEdit/Editor/Resources/Icons/Folder.png new file mode 100644 index 0000000000000000000000000000000000000000..ddef4b5c5008f5b184624cb055a47885924ec18e GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#Vf4nJ zFx~)R#$PeZihzQWC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NC8#v@pN$v zshIQj_FgZUM2Vvx{~KF(9$de|W1CQL#JY$TTjLrgh*T?Ud*mr_hILOh-^ix2Y46sk zm2;aSIIbPb;do`EX~M#!xcc25_YXfqgZDl!c$T`WQNW4g;mu9=`LF-}S}wtxz$a1h zpi=g@|G`_8iLcErTfOu=EhKs1YGUoxIsCSEyOWQ3E#0>9-s9rxQ}6EmNy}t(TO4#d zLi_cfik z1$qm_m{)MGILh#fDa){7S3?x}LJTphyg6c14$Rxt+Ov6j;=PdX6AZJ{D&L--b~wFs z`jtCBXZUZwRbMy5VugOAZu5EO=0ka@yN=vExMn6${FRYni$K#2{j1gCRx`db?F2?P NgQu&X%Q~loCICY#`uG3< literal 0 HcmV?d00001 diff --git a/BMEdit/Editor/Resources/Icons/Glacier/Script.png b/BMEdit/Editor/Resources/Icons/Glacier/Script.png new file mode 100644 index 0000000000000000000000000000000000000000..61188b67b2edf83a7f612cdf6adfa8084ef0cd5f GIT binary patch literal 1926 zcmV;12YL93P)a2>t?vSgEKl8e=E{MA%&l!aXxS z>@K}Mdl$}Kd$()smo#bjo^xitxifQS&e;O}=tn>P&!Dgq1X8}Qf*VCi1A|6ul1e5Z z`=be965sDZe*-%6S>Ac*o|~EXhbEqV&o7l5`^*3bVlu)5e;|Z03=0yyM?U+^(@Gj< z?uo7MEdwaqfdVP-WAb1USAcaZFYUUs=mywV!Hs=PKKr+;71q5rx_69V9RhnRy>A&b z;S>pljVH^Trb`ae-ri8e#M>7h6N9|R1lMO^PZTxG{{G*EgS7{+&?x^KjFLPM@aBOA zoi;-DGuMNxd+CaQ=LyyZ&`PTO?i(d}Ajj?Tz@FH84NPYe&lg%QD|u`2RjGzT<=A_G z5tC8FPd+QJsUz0PMAN%H0nj846V^OGt}r8nHbC~xM2(*Ex<8-O9&6Q#rZd6wg`}f_ zHSq$B5Xu04w~QL*j^fAMU9naMPiMmuw30PXPY7oO`y60wSy4k1lK$=ViB&(mCn7G4MP6!W>K$naPtni~>rjWdD8sVAk)$2W^jlcwGWD+Auv#lK0 zTA|apWE5b?S|&{J6+~0D;;DkF`~H)~MJ-CV7~NzGg)!8}ecFvmUyA_zCd~ zsO>B-k{RsqNNqy=9?si;$IIcmc(NnJ8@$ zA_NK(P%ISMtd(px7#4*#YbDzahDD*xTFG{UVPPJi*uuIOh^iZW;i{vJ+L6T!>}R5e z__oR&g{%{FfMSEfDnZLROD{-^a_ z%K$t5f)7y6gzzB%I|0i^A6uYYw^Mnlv-$F)4+V`8S50HW0Nrg|1px6n88~-Ra?1(F z`PE4#e|~gQ>Pl<*!10Ytm}ZdgWajJV z?yPJx*Aqqc{+3n~TKvzOzpR|Aw*kQ;k=2-K<#1_3%36gU> z;83Cx(1U{3B86!hY4>Lmu)+^ZzOCMqNA3tsApbtkijL*iPpr3TpqDDa2X_zrl!?|F ziEEe#`F!EHsYh5~6gkJ4@nEoiV!cggQP90Wkbpz& z6*Dxj+-REzG3euG$EBuZ$ITy~7{b70zsD#Ct>${aZ%M4u3QY$2qzAdB<&(jm=IYl? zKGI4spMW@sAOAk~u!bx);;}-MtT?5pgF7VTUbT+I+^UBHIkF-{#w+)p5$q-hQ9v2j|Xi( zoqmy4f?)(KXsddW70+Tqf(b{MF$YK&N=!1j$9Af6vwzpn>JORF$^_PgZ6Z>;VbhR~ z)5$FK-Xdf-XfGiCUCkSD$eN$(K`kw$0ia_TWJD(tOcJ)6KUfpVqIidy5cvgS~Sx(!90mGXPf? zzCtjHoC6BdF;Ya{4gNPSMk4P9OZj3X@@}xyFGeEo2LDSJBN29krJV!%FGv5+IUwSD z0rQ&~r3Ra3EeK&eAekl41Uxpe=U3MH5*OV4=Q&0tn6#Z6!UO0>{}~=B@AXnJ!bSHU z9Lb888I@qpIb=TAcY`8pMPY)kCz#5vH?`@yM8H?rat~0jsE-& +#include +#include + + +namespace models +{ + GameScriptsTreeModel::ScripTreeNode::ScripTreeNode() = default; + + GameScriptsTreeModel::ScripTreeNode::ScripTreeNode(models::GameScriptsTreeModel::ScripTreeNode::NodeType nt, QString &&sn, QString &&sfn) + : type(nt), name(std::move(sn)), fullPath(std::move(sfn)) + { + } + + GameScriptsTreeModel::GameScriptsTreeModel(QObject *parent) : QAbstractItemModel(parent) + { + buildTree(); + } + + QVariant GameScriptsTreeModel::data(const QModelIndex &index, int role) const + { + if (!m_root) return {}; + + const auto* pScript = reinterpret_cast(index.constInternalPointer()); + if (!pScript) return {}; + + if (role == Qt::DisplayRole) + { + return pScript->name; + } + + if (role == Qt::DecorationRole) + { + static QIcon kFolderIcon(":/bmedit/folder_icon.png"); + static QIcon kScriptIcon(":/bmedit/script_icon.png"); + static QIcon kUnknownIcon(":/bmedit/unknown_icon.png"); + + switch (pScript->type) + { + case ScripTreeNode::NodeType::STN_ROOT: + case ScripTreeNode::NodeType::STN_BONE: + return kFolderIcon; + break; + case ScripTreeNode::NodeType::STN_SCRIPT: + return kScriptIcon; + } + + return kUnknownIcon; + } + + if (role == Qt::ItemDataRole::ToolTipRole) + { + return pScript->fullPath; + } + + return {}; + } + + QModelIndex GameScriptsTreeModel::index(int row, int column, const QModelIndex &parent) const + { + if (!hasIndex(row, column, parent) || !m_root) + { + return QModelIndex {}; + } + + ScripTreeNode* pScript = nullptr; + if (!parent.isValid()) + { + pScript = m_root.get(); + } + else + { + pScript = static_cast(parent.internalPointer()); + } + + if (row >= 0 && row < pScript->children.size()) + { + return createIndex(row, column, (const void*)pScript->children[row].get()); + } + + return {}; + } + + QModelIndex GameScriptsTreeModel::parent(const QModelIndex &index) const + { + if (!index.isValid() || !m_root) + { + return {}; + } + + auto* child = static_cast(index.internalPointer()); + if (auto parent = child->parent.lock()) + { + if (parent == m_root) + { + return {}; + } + else + { + int row = 0; + + for (int i = 0; i < parent->children.size(); ++i) + { + if (parent->children[i].get() == child) + { + row = i; + break; + } + } + + return createIndex(row, 0, (const void*)parent.get()); + } + } + + return {}; + } + + int GameScriptsTreeModel::rowCount(const QModelIndex &parent) const + { + if (!m_root) + { + return 0; + } + + if (!parent.isValid()) + { + return m_root && !m_root->children.empty() ? static_cast(m_root->children.size()) : 0; + } + + return static_cast(static_cast(parent.internalPointer())->children.size()); + } + + int GameScriptsTreeModel::columnCount(const QModelIndex &parent) const + { + return m_root ? 1 : 0; + } + + QVariant GameScriptsTreeModel::headerData(int section, Qt::Orientation orientation, int role) const + { + return m_root ? QVariant::fromValue(QString("Game Scripts")) : QVariant {}; + } + + void insertIntoTree(const QString& str, const QSharedPointer& root) + { + // Split the string into parts based on the separator + QStringList parts = str.split('\\'); + + // Start from the root + QSharedPointer current = root; + + for (int i = 0; i < parts.size(); ++i) { + bool found = false; + // Check if the current part already exists as a child + foreach (const QSharedPointer& child, current->children) { + if (child->name == parts[i]) { + current = child; + found = true; + break; + } + } + + // If the part is not found, create a new node + if (!found) { + GameScriptsTreeModel::ScripTreeNode::NodeType type = (i < parts.size() - 1) ? GameScriptsTreeModel::ScripTreeNode::NodeType::STN_BONE : GameScriptsTreeModel::ScripTreeNode::NodeType::STN_SCRIPT; + QString fullPath = (current != root) ? current->fullPath + '\\' + parts[i] : parts[i]; + QSharedPointer newNode(new GameScriptsTreeModel::ScripTreeNode(type, std::move(parts[i]), std::move(fullPath))); + newNode->parent = current; + current->children.append(newNode); + current = newNode; + } + } + } + + void GameScriptsTreeModel::buildTree() + { + m_root = QSharedPointer::create(ScripTreeNode::NodeType::STN_ROOT, "ROOT", "ROOT"); + + gamelib::TypeRegistry::getInstance().forEachScript([this](const std::string& name, const gamelib::ScriptInfo& /*info*/) { + insertIntoTree(QString::fromStdString(name), m_root); + }); + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/ModelsLocator.cpp b/BMEdit/Editor/Source/Models/ModelsLocator.cpp index 24ceb8b..a02d806 100644 --- a/BMEdit/Editor/Source/Models/ModelsLocator.cpp +++ b/BMEdit/Editor/Source/Models/ModelsLocator.cpp @@ -4,4 +4,5 @@ namespace models { std::unique_ptr ModelsLocator::s_SceneTreeModel { nullptr }; + std::unique_ptr ModelsLocator::s_GameScriptsTreeModel { nullptr }; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp index ca4902d..51e797d 100644 --- a/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp +++ b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp @@ -2,6 +2,8 @@ // Widgets #include +#include +#include namespace widgets @@ -15,6 +17,16 @@ namespace widgets pResult = SelectSceneObjectTool::Create(parent); } + if (hintId == "SelectGameScript") + { + pResult = SelectScriptTool::Create(parent); + } + + if (!pResult) + { + qWarning() << "For hint '" << QString::fromStdString(hintId) << "' no editor created. Check name please"; + } + return pResult; } } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index a869818..8542003 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1425,10 +1425,7 @@ namespace widgets if (m_camera.canSeeObject(glm::vec3(modelWorldBoundingBox.min), glm::vec3(modelWorldBoundingBox.max))) { // Add bounding box to render list { - std::optional bboxMesh = model.boundingBoxMesh; - // NOTE: Here we need to try locate inner bbox or construct it but it can drop FPS - - if (geom == m_pSelectedSceneObject && bboxMesh.has_value()) { + if (geom == m_pSelectedSceneObject && model.boundingBoxMesh.has_value()) { // Need to add mesh render::RenderEntry &boundingBoxEntry = entries.emplace_back(); @@ -1442,7 +1439,7 @@ namespace widgets boundingBoxEntry.vPosition = vPosition; boundingBoxEntry.mWorldTransform = mWorldTransform; boundingBoxEntry.mLocalOriginalTransform = geom->getOriginalTransform(); - boundingBoxEntry.pMesh = const_cast(&bboxMesh.value()); + boundingBoxEntry.pMesh = const_cast(&model.boundingBoxMesh.value()); // Material render::RenderEntry::Material &material = boundingBoxEntry.material; diff --git a/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h b/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h index 4b067a8..3e849c7 100644 --- a/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h +++ b/BMEdit/Editor/UI/Include/SelectSceneObjectTool.h @@ -10,6 +10,7 @@ class Frontend_SelectSceneObjectTool final : public widgets::TypePropertyWidget Q_OBJECT public: Frontend_SelectSceneObjectTool(QWidget* parent, widgets::TypePropertyWidget* pTarget); + ~Frontend_SelectSceneObjectTool() override; void setValue(const types::QGlacierValue &value) override; [[nodiscard]] const types::QGlacierValue &getValue() const override; @@ -19,6 +20,10 @@ class Frontend_SelectSceneObjectTool final : public widgets::TypePropertyWidget private: void commitValue(const types::QGlacierValue &value); +private slots: + void onTargetValueChanged(); + void onTargetEditFinished(); + private: widgets::TypePropertyWidget* m_pTarget { nullptr }; QLabel* m_pLabel { nullptr }; diff --git a/BMEdit/Editor/UI/Include/SelectScriptTool.h b/BMEdit/Editor/UI/Include/SelectScriptTool.h new file mode 100644 index 0000000..70dac35 --- /dev/null +++ b/BMEdit/Editor/UI/Include/SelectScriptTool.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + + +class Frontend_SelectScriptTool final : public widgets::TypePropertyWidget +{ + Q_OBJECT +public: + Frontend_SelectScriptTool(QWidget* parent, widgets::TypePropertyWidget* pTarget); + ~Frontend_SelectScriptTool() override; + + void setValue(const types::QGlacierValue &value) override; + [[nodiscard]] const types::QGlacierValue &getValue() const override; + + bool canHookFocus() const override; + +private slots: + void onTargetValueChanged(); + void onTargetEditFinished(); + +private: + void commitValue(const types::QGlacierValue &value); + +private: + widgets::TypePropertyWidget* m_pTarget { nullptr }; + QLabel* m_pLabel { nullptr }; +}; + + +namespace Ui { + class SelectScriptTool; +} + +class SelectScriptTool : public widgets::TypePropertyWidget +{ + Q_OBJECT + +public: + explicit SelectScriptTool(QWidget *parent = nullptr); + ~SelectScriptTool(); + + static Frontend_SelectScriptTool* Create(QWidget* parent); + +protected: + // buildLayout and updateLayout not implemented because no dynamic layout here. I'm just handling setValue + void closeEvent(QCloseEvent* pEvent) override; + +private: + void disableAcceptButton(); + void enableAcceptButton(); + +private slots: + void onAccepted(); + void onRejected(); + void onScriptSelected(const QItemSelection &selected, const QItemSelection &deselected); + +private: + Ui::SelectScriptTool *m_ui; +}; \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 5879e09..c5ce38d 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -71,6 +72,7 @@ BMEditMainWindow::~BMEditMainWindow() delete m_typePropertyItemDelegate; delete m_sceneTreeFilterModel; models::ModelsLocator::s_SceneTreeModel = nullptr; // reset me + models::ModelsLocator::s_GameScriptsTreeModel = nullptr; // reset me delete m_sceneObjectPropertiesModel; delete m_operationProgress; @@ -703,6 +705,9 @@ void BMEditMainWindow::loadTypesDataBase() QStringList allAvailableTypes; gamelib::TypeRegistry::getInstance().forEachType([&allAvailableTypes](const gamelib::Type *type) { allAvailableTypes.push_back(QString::fromStdString(type->getName())); }); + // Runtime types + models::ModelsLocator::s_GameScriptsTreeModel = std::make_unique(this); + delete m_geomTypesModel; m_geomTypesModel = new QStringListModel(allAvailableTypes, this); ui->sceneObjectTypeCombo->setModel(m_geomTypesModel); diff --git a/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp b/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp index 8baa059..68d268e 100644 --- a/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp +++ b/BMEdit/Editor/UI/Source/SelectSceneObjectTool.cpp @@ -13,16 +13,8 @@ Frontend_SelectSceneObjectTool::Frontend_SelectSceneObjectTool(QWidget *parent, { if (m_pTarget) { - connect(m_pTarget, &widgets::TypePropertyWidget::valueChanged, [this]() { - commitValue(m_pTarget->getValue()); - emit valueChanged(); - }); - - connect(m_pTarget, &widgets::TypePropertyWidget::editFinished, [this]() { - auto v = m_pTarget->getValue(); - commitValue(v); - emit editFinished(); - }); + connect(m_pTarget, &widgets::TypePropertyWidget::valueChanged, this, &Frontend_SelectSceneObjectTool::onTargetEditFinished); + connect(m_pTarget, &widgets::TypePropertyWidget::editFinished, this, &Frontend_SelectSceneObjectTool::onTargetValueChanged); m_pTarget->setWindowModality(Qt::WindowModality::ApplicationModal); m_pTarget->show(); @@ -36,6 +28,11 @@ Frontend_SelectSceneObjectTool::Frontend_SelectSceneObjectTool(QWidget *parent, setLayout(pLayout); } +Frontend_SelectSceneObjectTool::~Frontend_SelectSceneObjectTool() +{ + m_pTarget = nullptr; +} + void Frontend_SelectSceneObjectTool::setValue(const types::QGlacierValue &value) { if (m_pLabel && !value.instructions.empty() && value.instructions[0].isString()) @@ -69,6 +66,23 @@ void Frontend_SelectSceneObjectTool::commitValue(const types::QGlacierValue &val widgets::TypePropertyWidget::setValue(value); } +void Frontend_SelectSceneObjectTool::onTargetValueChanged() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit editFinished(); + } +} + +void Frontend_SelectSceneObjectTool::onTargetEditFinished() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit valueChanged(); + } +} SelectSceneObjectTool::SelectSceneObjectTool(QWidget* parent) : widgets::TypePropertyWidget(parent), m_ui(new Ui::SelectSceneObjectTool) { diff --git a/BMEdit/Editor/UI/Source/SelectScriptTool.cpp b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp new file mode 100644 index 0000000..5fecd1e --- /dev/null +++ b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include "ui_SelectScriptTool.h" + + +Frontend_SelectScriptTool::Frontend_SelectScriptTool(QWidget *parent, widgets::TypePropertyWidget *pTarget) + : widgets::TypePropertyWidget(parent) + , m_pTarget(pTarget) +{ + if (m_pTarget) + { + connect(m_pTarget, &widgets::TypePropertyWidget::valueChanged, this, &Frontend_SelectScriptTool::onTargetValueChanged); + connect(m_pTarget, &widgets::TypePropertyWidget::editFinished, this, &Frontend_SelectScriptTool::onTargetEditFinished); + + m_pTarget->setWindowModality(Qt::WindowModality::ApplicationModal); + m_pTarget->show(); + } + + // And build layout + auto* pLayout = new QHBoxLayout(this); + m_pLabel = new QLabel(this); + m_pLabel->setText("MAYBE"); + pLayout->addWidget(m_pLabel); + setLayout(pLayout); +} + +Frontend_SelectScriptTool::~Frontend_SelectScriptTool() +{ + m_pTarget = nullptr; +} + +void Frontend_SelectScriptTool::onTargetValueChanged() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit valueChanged(); + } +} + +void Frontend_SelectScriptTool::onTargetEditFinished() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit editFinished(); + } +} + +void Frontend_SelectScriptTool::setValue(const types::QGlacierValue &value) +{ + if (m_pLabel && !value.instructions.empty() && value.instructions[0].isString()) + { + m_pLabel->setText(QString::fromStdString(value.instructions[0].getOperand().str)); + } + + if (m_pTarget) + { + m_pTarget->setValue(value); + } + + commitValue(value); +} + +const types::QGlacierValue &Frontend_SelectScriptTool::getValue() const +{ + static const types::QGlacierValue s_Invalid {}; + if (!m_pTarget) return s_Invalid; + return m_pTarget->getValue(); +} + +bool Frontend_SelectScriptTool::canHookFocus() const +{ + // Yep it can in this case + return true; +} + +void Frontend_SelectScriptTool::commitValue(const types::QGlacierValue &value) +{ + widgets::TypePropertyWidget::setValue(value); +} + + +SelectScriptTool::SelectScriptTool(QWidget *parent) + : widgets::TypePropertyWidget(parent) + , m_ui(new Ui::SelectScriptTool) +{ + m_ui->setupUi(this); + + disableAcceptButton(); + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectScriptTool::onAccepted); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectScriptTool::onRejected); + + // assign model and connect signals + m_ui->gameScripts->setModel(models::ModelsLocator::s_GameScriptsTreeModel.get()); + m_ui->gameScripts->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + connect(m_ui->gameScripts->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SelectScriptTool::onScriptSelected); +} + +SelectScriptTool::~SelectScriptTool() +{ + delete m_ui; +} + +void SelectScriptTool::closeEvent(QCloseEvent *pEvent) +{ + emit editFinished(); + QWidget::closeEvent(pEvent); +} + +void SelectScriptTool::disableAcceptButton() +{ + if (QPushButton* pOkButton = m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) + { + pOkButton->setEnabled(false); + } +} + +void SelectScriptTool::enableAcceptButton() +{ + if (QPushButton* pOkButton = m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) + { + pOkButton->setEnabled(true); + } +} + +void SelectScriptTool::onAccepted() +{ + // TODO: Need commit current value here and notify editFinished here +} + +void SelectScriptTool::onRejected() +{ + emit editFinished(); + close(); +} + +void SelectScriptTool::onScriptSelected(const QItemSelection &selected, const QItemSelection &deselected) +{ + if (selected.empty()) + { + disableAcceptButton(); + return; + } + + QModelIndex index = selected.first().indexes()[0]; + auto* pScript = reinterpret_cast(index.internalPointer()); + + if (!pScript || pScript->type != models::GameScriptsTreeModel::ScripTreeNode::NodeType::STN_SCRIPT) + { + disableAcceptButton(); + } + else + { + types::QGlacierValue temp = getValue(); + if (temp.instructions.empty()) + { + disableAcceptButton(); + } + else + { + temp.instructions[0] = gamelib::prp::PRPInstruction(temp.instructions[0].getOpCode(), gamelib::prp::PRPOperandVal(pScript->fullPath.toStdString())); + widgets::TypePropertyWidget::setValue(temp); + + emit valueChanged(); + enableAcceptButton(); + } + } +} + +Frontend_SelectScriptTool *SelectScriptTool::Create(QWidget *parent) +{ + auto* pEditor = new SelectScriptTool(nullptr); + auto* pFrontend = new Frontend_SelectScriptTool(parent, pEditor); + return pFrontend; +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/UI/SelectScriptTool.ui b/BMEdit/Editor/UI/UI/SelectScriptTool.ui new file mode 100644 index 0000000..d75e316 --- /dev/null +++ b/BMEdit/Editor/UI/UI/SelectScriptTool.ui @@ -0,0 +1,31 @@ + + + SelectScriptTool + + + + 0 + 0 + 490 + 890 + + + + Select Game Script... + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/BMEdit/GameLib/Include/GameLib/TypeRegistry.h b/BMEdit/GameLib/Include/GameLib/TypeRegistry.h index 93acdf1..ebf4c7e 100644 --- a/BMEdit/GameLib/Include/GameLib/TypeRegistry.h +++ b/BMEdit/GameLib/Include/GameLib/TypeRegistry.h @@ -50,6 +50,7 @@ namespace gamelib [[nodiscard]] const Type *findTypeByShortName(const std::string &typeName) const; void forEachType(const std::function &predicate); + void forEachScript(const std::function &predicate); void linkTypes(); void addHashAssociation(std::size_t hash, const std::string &typeName); diff --git a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp index cf9dbfe..3eabe5c 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp @@ -293,6 +293,19 @@ namespace gamelib } } + void TypeRegistry::forEachScript(const std::function &predicate) + { + if (!predicate) + { + return; + } + + for (const auto& [scriptName, scriptInfo] : m_scriptsByName) + { + predicate(scriptName, scriptInfo); + } + } + void TypeRegistry::linkTypes() { // Resolve links From 927c5e9e60fa68c739d1bf6a5a24c0b791d0cb44 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 1 May 2024 19:17:47 +0300 Subject: [PATCH 59/80] Finished script selector dialog. Added support of script selector in ZScriptC type info --- Assets/g1/ZScriptC.ScriptID.json | 6 +++++ Assets/g1/ZScriptC.json | 2 +- Assets/scripts/AllLevels.json | 27 +------------------- BMEdit/Editor/UI/Source/SelectScriptTool.cpp | 27 ++++++++++++++++---- 4 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 Assets/g1/ZScriptC.ScriptID.json diff --git a/Assets/g1/ZScriptC.ScriptID.json b/Assets/g1/ZScriptC.ScriptID.json new file mode 100644 index 0000000..9cf244a --- /dev/null +++ b/Assets/g1/ZScriptC.ScriptID.json @@ -0,0 +1,6 @@ +{ + "typename": "ZScriptC.ScriptID", + "kind": "TypeKind.ALIAS", + "alias": "PRPOpCode.String", + "editor": "SelectGameScript" +} \ No newline at end of file diff --git a/Assets/g1/ZScriptC.json b/Assets/g1/ZScriptC.json index bebcac4..4df43da 100644 --- a/Assets/g1/ZScriptC.json +++ b/Assets/g1/ZScriptC.json @@ -4,6 +4,6 @@ "kind": "TypeKind.COMPLEX", "skip_unexposed_properties": true, "properties": [ - { "name": "ScriptName", "typename": "PRPOpCode.String", "__note": "DO NOT MODIFY THIS NAME!!!" } + { "name": "ScriptName", "typename": "ZScriptC.ScriptID", "__note": "DO NOT MODIFY THIS NAME!!!" } ] } \ No newline at end of file diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json index 642ef70..b66f215 100644 --- a/Assets/scripts/AllLevels.json +++ b/Assets/scripts/AllLevels.json @@ -549,33 +549,8 @@ ] }, "alllevels\\armed": { + "parent": "alllevels\\civilian", "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, diff --git a/BMEdit/Editor/UI/Source/SelectScriptTool.cpp b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp index 5fecd1e..7e3674b 100644 --- a/BMEdit/Editor/UI/Source/SelectScriptTool.cpp +++ b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp @@ -129,7 +129,28 @@ void SelectScriptTool::enableAcceptButton() void SelectScriptTool::onAccepted() { - // TODO: Need commit current value here and notify editFinished here + // need to set value + const QModelIndexList selectedIndexes = m_ui->gameScripts->selectionModel()->selectedIndexes(); + if (selectedIndexes.isEmpty()) + { + // just do nothing + return; + } + + QModelIndex currentIndex = selectedIndexes.first(); + auto* pScript = reinterpret_cast(currentIndex.internalPointer()); + if (!pScript || pScript->type != models::GameScriptsTreeModel::ScripTreeNode::NodeType::STN_SCRIPT || m_value.instructions.empty()) + { + // Just do nothing + return; + } + + auto newVal = getValue(); + newVal.instructions[0] = gamelib::prp::PRPInstruction(newVal.instructions[0].getOpCode(), gamelib::prp::PRPOperandVal(pScript->fullPath.toStdString())); + widgets::TypePropertyWidget::setValue(newVal); + emit editFinished(); + + close(); } void SelectScriptTool::onRejected() @@ -162,10 +183,6 @@ void SelectScriptTool::onScriptSelected(const QItemSelection &selected, const QI } else { - temp.instructions[0] = gamelib::prp::PRPInstruction(temp.instructions[0].getOpCode(), gamelib::prp::PRPOperandVal(pScript->fullPath.toStdString())); - widgets::TypePropertyWidget::setValue(temp); - - emit valueChanged(); enableAcceptButton(); } } From 741a3d9002b977ec7c1ec1def4e081aaadcde611 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 1 May 2024 19:27:11 +0300 Subject: [PATCH 60/80] M13: Class merge --- Assets/scripts/M13.json | 178 +++------------------------------------- 1 file changed, 10 insertions(+), 168 deletions(-) diff --git a/Assets/scripts/M13.json b/Assets/scripts/M13.json index 36ab810..8cc3943 100644 --- a/Assets/scripts/M13.json +++ b/Assets/scripts/M13.json @@ -1,67 +1,14 @@ { "m13\\m13_levelcontrol": {}, "m13\\journalist": { + "parent": "alllevels\\civilian", "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rHelpPointsList" - }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rVoiceRecorder" - }, - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rWheelchairGuy" - }, - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rWheelchairGuy_ForGame" - }, - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rWheelchairGuy_MOVETOCREATION" - }, - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rLookout" - }, - { - "kind": "VARIABLE", - "typename": "ZGEOMREF", - "name": "rWaypointsList" - } + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rVoiceRecorder" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy_ForGame" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelchairGuy_MOVETOCREATION" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rLookout" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypointsList" } ] }, "m13\\wheelchair": { @@ -84,43 +31,8 @@ ] }, "m13\\wheelchairguy": { + "parent": "alllevels\\armed", "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue3" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_0" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWheelChair_for_game_1" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rJournalist_01" }, @@ -131,86 +43,16 @@ ] }, "m13\\priest": { + "parent": "alllevels\\civilian", "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHelpPointsList" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBible" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_Podium" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaypoint_preach" } ] }, "m13\\secretservice": { + "parent": "alllevels\\guard", "parameters": [ - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue0" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue1" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue2" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue3" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue4" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue5" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue6" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPrimaryWeapon" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue8" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue9" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGuardQuarter" }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue11" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnknownValue12" }, - { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCinematicTargets" }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rInsideChurchRoom" }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "sUnknownValue15" }, From 690ac53de2ea4251fcdddaf55de47b2a2f4316a8 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 2 May 2024 21:41:38 +0300 Subject: [PATCH 61/80] Removed highlight of virtual properties. Fixed bug with post-mapping of properties after script changed. Added auto-select in select script tool. Fixed empty properties in produced opCodes in scripts (initial instructions, now they are inited from start by zero/default values) --- .../Models/SceneObjectControllerModel.h | 16 ------ .../Models/SceneObjectControllerModel.cpp | 36 ++---------- BMEdit/Editor/UI/Include/SelectScriptTool.h | 3 + BMEdit/Editor/UI/Source/SelectScriptTool.cpp | 56 +++++++++++++++++++ .../GameLib/Source/GameLib/TypeRegistry.cpp | 3 +- 5 files changed, 66 insertions(+), 48 deletions(-) diff --git a/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h b/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h index c8a9ba5..627bb0e 100644 --- a/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h +++ b/BMEdit/Editor/Include/Models/SceneObjectControllerModel.h @@ -13,19 +13,6 @@ namespace models { Q_OBJECT - private: - struct SpecialRow - { - int startRow { 0 }; - int endRow { 0 }; - QColor backgroundColor {}; - - bool includes(int row) const - { - return row >= startRow && row <= endRow; - } - }; - public: SceneObjectControllerModel(QObject *parent = nullptr); @@ -34,8 +21,6 @@ namespace models void setControllerIndex(int controllerIndex); void resetController(); - QVariant data(const QModelIndex &index, int role) const override; - private: void addSugarViews(const gamelib::Type* pControllerType, gamelib::Value& v, const std::string& scriptName); void removeSugarViews(const gamelib::Type* pControllerType, gamelib::Value& v); @@ -48,6 +33,5 @@ namespace models gamelib::scene::SceneObject *m_geom { nullptr }; int m_currentControllerIndex = kUnset; - std::vector m_specialRows {}; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp index d20f534..a996282 100644 --- a/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp +++ b/BMEdit/Editor/Source/Models/SceneObjectControllerModel.cpp @@ -21,7 +21,6 @@ void SceneObjectControllerModel::setGeom(gamelib::scene::SceneObject *geom) const bool isNewGeom = m_geom != geom; beginResetModel(); - m_specialRows.clear(); m_geom = geom; m_currentControllerIndex = kUnset; endResetModel(); @@ -37,7 +36,6 @@ void SceneObjectControllerModel::resetGeom() beginResetModel(); // and after that we've ready to do smth else - m_specialRows.clear(); m_geom = nullptr; m_currentControllerIndex = kUnset; endResetModel(); @@ -56,9 +54,6 @@ void SceneObjectControllerModel::setControllerIndex(int controllerIndex) beginResetModel(); - // reset special rows info - m_specialRows.clear(); - // reset controller index m_currentControllerIndex = controllerIndex; endResetModel(); @@ -90,23 +85,6 @@ void SceneObjectControllerModel::resetController() resetValue(); } -QVariant SceneObjectControllerModel::data(const QModelIndex &index, int role) const -{ - if (role == Qt::BackgroundRole) - { - for (const auto& specialRow : m_specialRows) - { - if (specialRow.includes(index.row())) - { - return specialRow.backgroundColor; - } - } - } - - // other requests redirect to root logic - return ValueModelBase::data(index, role); -} - void SceneObjectControllerModel::addSugarViews(const gamelib::Type* pControllerType, gamelib::Value &v, const std::string &scriptName) { // Ok, it must be pretty easy. First of all we need to find a script description in TypeRegistry @@ -115,10 +93,6 @@ void SceneObjectControllerModel::addSugarViews(const gamelib::Type* pControllerT const int baseViewsNr = asDefault.getEntries().size(); const int baseSize = asDefault.getInstructions().size(); - SpecialRow row {}; - row.endRow = row.startRow = baseSize; - row.backgroundColor = QColor(26, 188, 156); - if (auto scriptInfo = gamelib::TypeRegistry::getInstance().getScriptInfo(scriptName); scriptInfo.has_value()) { // Ok, let's insert that data @@ -128,11 +102,7 @@ void SceneObjectControllerModel::addSugarViews(const gamelib::Type* pControllerT gamelib::ValueEntry temp = ent; temp.instructions.iOffset += baseSize; v += temp; - ++row.endRow; // I'm not sure that +1 is enough here, but += temp.instructions.iSize is not valid too! } - - // save new row - m_specialRows.emplace_back(row); } } @@ -190,7 +160,11 @@ void SceneObjectControllerModel::onValueChanged() // And add extra stubs for (const auto& ent : scriptInfo->entries) - newProperties += ent; + { + auto newEnt = ent; + newEnt.instructions.iOffset += 1; // Add +1 because instruction #0 - our root instruction + newProperties += newEnt; + } // Done setValue(newProperties); diff --git a/BMEdit/Editor/UI/Include/SelectScriptTool.h b/BMEdit/Editor/UI/Include/SelectScriptTool.h index 70dac35..7e32768 100644 --- a/BMEdit/Editor/UI/Include/SelectScriptTool.h +++ b/BMEdit/Editor/UI/Include/SelectScriptTool.h @@ -45,6 +45,8 @@ class SelectScriptTool : public widgets::TypePropertyWidget static Frontend_SelectScriptTool* Create(QWidget* parent); + void setValue(const types::QGlacierValue &value) override; + protected: // buildLayout and updateLayout not implemented because no dynamic layout here. I'm just handling setValue void closeEvent(QCloseEvent* pEvent) override; @@ -52,6 +54,7 @@ class SelectScriptTool : public widgets::TypePropertyWidget private: void disableAcceptButton(); void enableAcceptButton(); + void selectByPath(const QString& path); private slots: void onAccepted(); diff --git a/BMEdit/Editor/UI/Source/SelectScriptTool.cpp b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp index 7e3674b..4f450dd 100644 --- a/BMEdit/Editor/UI/Source/SelectScriptTool.cpp +++ b/BMEdit/Editor/UI/Source/SelectScriptTool.cpp @@ -127,6 +127,50 @@ void SelectScriptTool::enableAcceptButton() } } +void SelectScriptTool::selectByPath(const QString &path) +{ + QSignalBlocker blocker { m_ui->gameScripts->selectionModel() }; + + QStringList pathParts = path.split('\\'); + if (pathParts.empty()) return; + + auto* model = qobject_cast(m_ui->gameScripts->model()); + if (!model) + { + return; + } + + QModelIndex currentIndex = QModelIndex(); + + foreach (const QString& part, pathParts) + { + bool found = false; + int rows = model->rowCount(currentIndex); + + for (int i = 0; i < rows; ++i) + { + QModelIndex childIndex = model->index(i, 0, currentIndex); + QString entryName = model->data(childIndex, Qt::DisplayRole).toString(); + + if (entryName == part) + { + currentIndex = childIndex; + found = true; + m_ui->gameScripts->expand(currentIndex); + m_ui->gameScripts->scrollTo(currentIndex); + break; + } + } + + if (!found) + { + return; + } + } + + m_ui->gameScripts->selectionModel()->select(currentIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); +} + void SelectScriptTool::onAccepted() { // need to set value @@ -188,6 +232,18 @@ void SelectScriptTool::onScriptSelected(const QItemSelection &selected, const QI } } +void SelectScriptTool::setValue(const types::QGlacierValue &value) +{ + // call for base + widgets::TypePropertyWidget::setValue(value); + + if (value.instructions.size() == 1 && value.instructions[0].isString()) + { + // Need to parse path + selectByPath(QString::fromStdString(value.instructions[0].getOperand().str)); + } +} + Frontend_SelectScriptTool *SelectScriptTool::Create(QWidget *parent) { auto* pEditor = new SelectScriptTool(nullptr); diff --git a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp index 3eabe5c..3f9b90c 100644 --- a/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp +++ b/BMEdit/GameLib/Source/GameLib/TypeRegistry.cpp @@ -62,7 +62,8 @@ namespace gamelib void produceOpCode(ScriptInfo& si, prp::PRPOpCode opCode) { - prp::PRPInstruction instruction { opCode }; // idk is it ok or not + const gamelib::prp::PRPOperandVal kNullOperand(0); // initialized with zero but it's not typed holder + prp::PRPInstruction instruction { opCode, kNullOperand }; si.initialInstructions.emplace_back(instruction); } From 2779275efb7ecf26ef999b37ea888224c9565907 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 6 Jun 2024 21:31:05 +0300 Subject: [PATCH 62/80] Rendering of dynamic objects --- .../Source/Widgets/SceneRenderWidget.cpp | 123 +++++++----------- .../Source/GameLib/Scene/SceneObject.cpp | 8 +- 2 files changed, 49 insertions(+), 82 deletions(-) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 8542003..72a3834 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1213,94 +1213,27 @@ namespace widgets else { // Try to render + std::set visitedDynamics {}; + for (const auto& pRoom : m_cameraInRooms) { for (const SeebleObject& sObject : pRoom->vObjects) { + if (sObject.ePrio == EObjectPriority::EP_DYNAMIC_OBJECT && visitedDynamics.contains(sObject.pObject.get())) + continue; // Skip because it's in render list already + if (m_camera.canSeeObject(sObject.sBoundingBox)) { // Need to render it collectRenderEntriesIntoRenderList(sObject.pObject.get(), entries, stats, bIgnoreVisibility, true); + + if (sObject.ePrio == EObjectPriority::EP_DYNAMIC_OBJECT) + { + visitedDynamics.insert(sObject.pObject.get()); + } } } } -// if (m_pLastRoom) -// { -// if (!m_pLastRoom->rRoom.expired()) -// { -// // First of all let's render only current room -// collectRenderEntriesIntoRenderList(m_pLastRoom->rRoom.lock().get(), entries, stats, bIgnoreVisibility); -// } -// -// // Let's check if we can see any portal - we need to render that room. -// if (!m_pLastRoom->aExists.empty()) -// { -// // Iterate over all exits and check what exit camera can see right now -// for (const auto& eXit : m_pLastRoom->aExists) -// { -// gamelib::Plane sPlane { eXit.v0, eXit.v1, eXit.v2, eXit.v3 }; -// -// if (m_camera.canSeeObject(sPlane)) -// { -// const auto& pRoom = m_pLevel->getSceneObjectByInstanceID(eXit.iRoomREF); -// if (pRoom) -// { -// // We can see another room - save it -// collectRenderEntriesIntoRenderList(pRoom.get(), entries, stats, bIgnoreVisibility); -// // in theory 1 room is enough, but... idk) -// } -// } -// } -// } - - // Try to render dynamic objects -// const auto& vObjects = m_pLevel->getSceneObjects(); -// auto it = std::find_if(vObjects.begin(), vObjects.end(), [](const gamelib::scene::SceneObject::Ptr& pObject) -> bool { -// return pObject && pObject->getName().ends_with("_CHARACTERS.zip"); -// }); -// -// gamelib::scene::SceneObject* pDynRoot = nullptr; -// -// if (it != vObjects.end()) -// { -// // Add this 'ROOM' as another thing to visit -// pDynRoot = it->get(); -// } -// else -// { -// // Need to collect every ZActor, ZHM3Actor, ZItem things from the whole scene :( -// using R = gamelib::scene::SceneObject::EVisitResult; -// -// pDynRoot = m_pLevel->getSceneObjects()[0].get(); -// } -// -// // Visit dynamic root -// using R = gamelib::scene::SceneObject::EVisitResult; -// -// pDynRoot->visitChildren([&entries, &stats, this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { -// static const std::array kAllowedTypes = { "ZActor", "ZPlayer", "ZItem", "ZItemAmmo", "ZLNKOBJ" }; -// -// for (const auto& sEntBaseName : kAllowedTypes) -// { -// if (pObject->isInheritedOf("ZROOM")) -// { -// return R::VR_NEXT; // Skip all rooms -// } -// -// // If object based on ... -// if (pObject->isInheritedOf(sEntBaseName)) -// { -// if (isGameObjectInActiveRoom(pObject) || m_rooms.empty()) -// { -// collectRenderEntriesIntoRenderList(pObject.get(), entries, stats, false); -// return R::VR_NEXT; // accepted, jump to next -// } -// } -// } -// -// // Go deeper -// return R::VR_CONTINUE; -// }); } // Add debug stuff @@ -1936,7 +1869,6 @@ namespace widgets return R::VR_CONTINUE; // go deeper }); - // TODO: Collect dynamic objects (remember: when collecting remember to check that object is in room bounding box) return R::VR_NEXT; } @@ -1984,6 +1916,41 @@ namespace widgets }); } + if (m_rooms.size() > 1 && !m_rooms.begin()->bIsVirtualBigRoom) + { + // Visit all objects before any rooms + m_pLevel->getSceneObjects()[0]->visitChildren([this](const gamelib::scene::SceneObject::Ptr& pObject) -> R { + if (pObject->getName() == "Scar!scar") + { + printf("DEBUG\n"); + } + if (pObject->isInheritedOf("ZROOM")) return R::VR_NEXT; // Skip current branch + + if (auto rBBOX = getGameObjectBoundingBox(pObject, true); rBBOX.has_value()) + { + // Need to find in which room this subject should be + for (auto& sRoom : m_rooms) + { + if (sRoom.vBoundingBox.intersect(rBBOX.value())) + { + // Nice, save here + SeebleObject& sObject = sRoom.vObjects.emplace_back(); + sObject.pObject = pObject; + sObject.sBoundingBox = rBBOX.value(); + sObject.ePrio = EObjectPriority::EP_DYNAMIC_OBJECT; // mark as dynamic + + // break; // DronCode: Need to fix global bboxes before work with it. + } + } + + // Skip subtree (no visible inside) + return R::VR_NEXT; + } // skipped, no real reason to handle invisible object (but object could be visible after prop changed. be aware) + + return R::VR_CONTINUE; // go deeper + }); + } + // Upload debug geom for (auto& room : m_rooms) { diff --git a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp index a053a55..cef68ac 100644 --- a/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp +++ b/BMEdit/GameLib/Source/GameLib/Scene/SceneObject.cpp @@ -188,16 +188,16 @@ namespace gamelib::scene return getProperties().getObject("Matrix", glm::mat3(1.f)); } - glm::mat4 SceneObject::getWorldTransform() const + glm::mat4 SceneObject::getWorldTransform() const // NOLINT(*-no-recursion) { - glm::mat4 mWorldMartix = getLocalTransform(); + glm::mat4 mMatrix = getLocalTransform(); if (!getParent().expired()) { - mWorldMartix = getParent().lock()->getWorldTransform() * mWorldMartix; + mMatrix = getParent().lock()->getWorldTransform() * mMatrix; } - return mWorldMartix; + return mMatrix; } void SceneObject::visitChildren(const std::function& pred) const From 29c3951c77b90f3c2d2e37caa3d9114585b597a2 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 6 Jun 2024 21:32:47 +0300 Subject: [PATCH 63/80] Fix CI: add support of 'scripts' folder --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d068a3..c6e70fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,6 +71,7 @@ jobs: mv build/Release/imageformats dist/imageformats mv build/Release/iconengines dist/iconengines mv Assets/g1 dist/g1 + mv Assets/scripts dist/scripts mv Assets/TypesRegistry.json dist mv README.md dist From f7e57ca1a6563ea4c1a004aa2845c18c753c3f20 Mon Sep 17 00:00:00 2001 From: DronCode Date: Fri, 7 Jun 2024 20:46:42 +0300 Subject: [PATCH 64/80] Added far distance limitation to render queue (do not render far objects) Some scripts from M00 reversed --- Assets/scripts/M00.json | 126 ++++++++++++++++++ BMEdit/Editor/Include/Render/Camera.h | 2 +- BMEdit/Editor/Source/Render/Camera.cpp | 3 +- .../Source/Widgets/SceneRenderWidget.cpp | 9 +- 4 files changed, 132 insertions(+), 8 deletions(-) diff --git a/Assets/scripts/M00.json b/Assets/scripts/M00.json index 3aecc99..3011b9d 100644 --- a/Assets/scripts/M00.json +++ b/Assets/scripts/M00.json @@ -42,5 +42,131 @@ "name": "rDruglabActorsList" } ] + }, + "m00\\m00_sniper": { "parent": "alllevels\\guard" }, + "m00\\m00_scoopsguards": { + "parent": "alllevels\\guard", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rIdlePos" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rShootPos" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoop" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoopsVictim" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuardSearchZone_0" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuardSearchZone_1" + } + ] + }, + "m00\\m00_scoopsbiatch": { + "parent": "alllevels\\civilian", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rBlowPos" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoop" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rVictim" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rStandIdlePos" + } + ] + }, + "m00\\scoop": { + "parent": "alllevels\\guard", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rBed" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoopsVictim" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rDesertEagleItem" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGunPos" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rSnoopsTroops" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rFluffer" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rAlertPos" + } + ] + }, + "m00\\m00_scoopsvictim": { + "parent": "alllevels\\guard", + "parameters": [ + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rVictimStand" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rScoop" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuard0" + }, + { + "kind": "VARIABLE", + "typename": "ZGEOMREF", + "name": "rGuard1" + } + ] } } \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index 6ee322d..c326c2d 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -79,7 +79,7 @@ namespace render float m_fSpeed { 2.5f }; float m_fSensitivity { 0.1f }; float m_fNearPlane { .01f }; - float m_fFarPlane { 10'000.f }; + float m_fFarPlane { 3'000.f }; // Calculated things glm::vec3 m_vPosition { .0f }; diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 3e9b724..ce6e5d4 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -112,7 +112,8 @@ namespace render bool Camera::canSeeObject(const gamelib::BoundingBox& bbox) const { - return m_sFrustum.isBoxVisible(bbox.min, bbox.max); + return glm::distance(m_vPosition, bbox.getCenter()) <= m_fFarPlane && + m_sFrustum.isBoxVisible(bbox.min, bbox.max); } bool Camera::canSeeObject(const gamelib::Plane& plane) const diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 72a3834..7a3d755 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1213,13 +1213,13 @@ namespace widgets else { // Try to render - std::set visitedDynamics {}; + std::set visitedObjects {}; for (const auto& pRoom : m_cameraInRooms) { for (const SeebleObject& sObject : pRoom->vObjects) { - if (sObject.ePrio == EObjectPriority::EP_DYNAMIC_OBJECT && visitedDynamics.contains(sObject.pObject.get())) + if (visitedObjects.contains(sObject.pObject.get())) continue; // Skip because it's in render list already if (m_camera.canSeeObject(sObject.sBoundingBox)) @@ -1227,10 +1227,7 @@ namespace widgets // Need to render it collectRenderEntriesIntoRenderList(sObject.pObject.get(), entries, stats, bIgnoreVisibility, true); - if (sObject.ePrio == EObjectPriority::EP_DYNAMIC_OBJECT) - { - visitedDynamics.insert(sObject.pObject.get()); - } + visitedObjects.insert(sObject.pObject.get()); } } } From d3fecdabbaf9b488d742fce8d008134dafed0351 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 8 Jun 2024 16:51:47 +0300 Subject: [PATCH 65/80] Removed BoundingBox caching to fix issues with object positioning & visibility (exta culling) --- .../Editor/Include/Widgets/SceneRenderWidget.h | 1 - BMEdit/Editor/Source/Render/Camera.cpp | 3 +-- .../Editor/Source/Widgets/SceneRenderWidget.cpp | 17 ++++++++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index b62b4f8..7880711 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -68,7 +68,6 @@ namespace widgets struct SeebleObject { EObjectPriority ePrio { EObjectPriority::EP_STATIC_OBJECT }; - gamelib::BoundingBox sBoundingBox {}; gamelib::scene::SceneObject::Ptr pObject {}; }; diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index ce6e5d4..3b7adbc 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -112,8 +112,7 @@ namespace render bool Camera::canSeeObject(const gamelib::BoundingBox& bbox) const { - return glm::distance(m_vPosition, bbox.getCenter()) <= m_fFarPlane && - m_sFrustum.isBoxVisible(bbox.min, bbox.max); + return glm::distance(m_vPosition, bbox.getCenter()) <= m_fFarPlane && m_sFrustum.isBoxVisible(bbox.min, bbox.max); } bool Camera::canSeeObject(const gamelib::Plane& plane) const diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 7a3d755..b6a51bf 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -684,7 +684,17 @@ namespace widgets if (!sceneObject || !m_pLevel || !m_resources) return; - m_resources->m_modelTransformCache[sceneObject] = sceneObject->getWorldTransform(); + // Visit limited subtree + int iDepth = 2; // max 2 objects, otherwise it's better to make full invalidation (in case when user wants to move some huge object) + sceneObject->visitChildren([this, &iDepth](const gamelib::scene::SceneObject::Ptr& pObject) -> gamelib::scene::SceneObject::EVisitResult { + // Update transform + m_resources->m_modelTransformCache[pObject.get()] = pObject->getWorldTransform(); + + --iDepth; + return iDepth > 0 ? gamelib::scene::SceneObject::EVisitResult::VR_CONTINUE // Go deeper + : gamelib::scene::SceneObject::EVisitResult::VR_STOP_ALL; // Out of limit + }); + invalidateRenderList(); //TODO: Need invalidate only object, not whole list! repaint(); } @@ -1222,7 +1232,7 @@ namespace widgets if (visitedObjects.contains(sObject.pObject.get())) continue; // Skip because it's in render list already - if (m_camera.canSeeObject(sObject.sBoundingBox)) + if (auto bbox = getGameObjectBoundingBox(sObject.pObject, true); bbox.has_value() && m_camera.canSeeObject(bbox.value())) { // Need to render it collectRenderEntriesIntoRenderList(sObject.pObject.get(), entries, stats, bIgnoreVisibility, true); @@ -1860,7 +1870,6 @@ namespace widgets SeebleObject& sObject = room.vObjects.emplace_back(); sObject.pObject = pObj; sObject.ePrio = EObjectPriority::EP_STATIC_OBJECT; - sObject.sBoundingBox = bbox.value(); return R::VR_NEXT; // Go to next } return R::VR_CONTINUE; // go deeper @@ -1905,7 +1914,6 @@ namespace widgets SeebleObject& sObject = sVirtualRoom.vObjects.emplace_back(); sObject.pObject = pObj; sObject.ePrio = EObjectPriority::EP_STATIC_OBJECT; // Idk, but in this case all objects are STATIC - sObject.sBoundingBox = bbox.value(); return R::VR_NEXT; // Go to next } @@ -1933,7 +1941,6 @@ namespace widgets // Nice, save here SeebleObject& sObject = sRoom.vObjects.emplace_back(); sObject.pObject = pObject; - sObject.sBoundingBox = rBBOX.value(); sObject.ePrio = EObjectPriority::EP_DYNAMIC_OBJECT; // mark as dynamic // break; // DronCode: Need to fix global bboxes before work with it. From 17796bf34505cabf62d66bccbfeed5f69c9f521c Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 8 Jun 2024 17:10:13 +0300 Subject: [PATCH 66/80] Code cleanup --- .../GameLib/Source/GameLib/GMS/GMSHeader.cpp | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/BMEdit/GameLib/Source/GameLib/GMS/GMSHeader.cpp b/BMEdit/GameLib/Source/GameLib/GMS/GMSHeader.cpp index fbef468..6dfc614 100644 --- a/BMEdit/GameLib/Source/GameLib/GMS/GMSHeader.cpp +++ b/BMEdit/GameLib/Source/GameLib/GMS/GMSHeader.cpp @@ -41,7 +41,7 @@ namespace gamelib::gms { // Validate format BinaryReaderSeekScope rootScope { gmsFileReader }; - gmsFileReader->seek(4); + gmsFileReader->seek(4); // skip first block (Entities) static constexpr std::array kExpectedSignature = { 0, 0, 4 }; std::array sections = { 0, 0, 0 }; @@ -215,53 +215,4 @@ namespace gamelib::gms } } } - - CachedRuntimeTypes::CachedRuntimeTypes() - { - auto &rttiRegistry = TypeRegistry::getInstance(); - -#define IS_VALID_TYPE_INSTANCE(x) ((x) && (x->getKind() == TypeKind::COMPLEX) && reinterpret_cast((x))->hasGeomInfo()) - - ZSHAPE = rttiRegistry.findTypeByName("ZSHAPE"); - if (IS_VALID_TYPE_INSTANCE(ZSHAPE)) { - ZSHAPE_Runtime = &reinterpret_cast(ZSHAPE)->getGeomInfo(); - } - - ZSTDOBJ = rttiRegistry.findTypeByName("ZSTDOBJ"); - if (IS_VALID_TYPE_INSTANCE(ZSTDOBJ)) { - ZSTDOBJ_Runtime = &reinterpret_cast(ZSTDOBJ)->getGeomInfo(); - } - - ZBOUND = rttiRegistry.findTypeByName("ZBOUND"); - if (IS_VALID_TYPE_INSTANCE(ZBOUND)) { - ZBOUND_Runtime = &reinterpret_cast(ZBOUND)->getGeomInfo(); - } - - ZSNDOBJ = rttiRegistry.findTypeByName("ZSNDOBJ"); - if (IS_VALID_TYPE_INSTANCE(ZSNDOBJ)) { - ZSNDOBJ_Runtime = &reinterpret_cast(ZSNDOBJ)->getGeomInfo(); - } - - ZGROUP = rttiRegistry.findTypeByName("ZGROUP"); - if (IS_VALID_TYPE_INSTANCE(ZGROUP)) { - ZGROUP_Runtime = &reinterpret_cast(ZGROUP)->getGeomInfo(); - } - - ZLIGHT = rttiRegistry.findTypeByName("ZLIGHT"); - if (IS_VALID_TYPE_INSTANCE(ZLIGHT)) { - ZLIGHT_Runtime = &reinterpret_cast(ZLIGHT)->getGeomInfo(); - } - -#undef IS_VALID_TYPE_INSTANCE - } - - CachedRuntimeTypes::operator bool() const noexcept - { - return ZSHAPE && ZSHAPE_Runtime - && ZSTDOBJ && ZSTDOBJ_Runtime - && ZBOUND && ZBOUND_Runtime - && ZSNDOBJ && ZSNDOBJ_Runtime - && ZGROUP && ZGROUP_Runtime - && ZLIGHT && ZLIGHT_Runtime; - } } \ No newline at end of file From 973969e527d2864cdc0b66db192ad1735859f483 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 8 Jun 2024 19:46:41 +0300 Subject: [PATCH 67/80] Added levels drag & drop feature. Reversed few types from M00 & AllLevels --- Assets/scripts/AllLevels.json | 30 ++++ Assets/scripts/M00.json | 141 +++++++++++++++++++ BMEdit/Editor/UI/Include/BMEditMainWindow.h | 4 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 39 +++++ 4 files changed, 214 insertions(+) diff --git a/Assets/scripts/AllLevels.json b/Assets/scripts/AllLevels.json index b66f215..7f542e2 100644 --- a/Assets/scripts/AllLevels.json +++ b/Assets/scripts/AllLevels.json @@ -590,5 +590,35 @@ { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue6" }, { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue7" } ] + }, + "alllevels\\useable\\useable_playanim_pickup": { + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rANIM_0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iPlayCount_0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rANIM_1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iPlayCount_1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "rANIM_2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iPlayCount_2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue5" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.String", "name": "iUnknownValue6" } + ] } } \ No newline at end of file diff --git a/Assets/scripts/M00.json b/Assets/scripts/M00.json index 3011b9d..00d7553 100644 --- a/Assets/scripts/M00.json +++ b/Assets/scripts/M00.json @@ -168,5 +168,146 @@ "name": "rGuard1" } ] + }, + "m00\\entrancebirdsgroundbox": { + "parameters": [{ "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBirdList" }] + }, + "m00\\swingking": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSecretary" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknown" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCameraTarget" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rChair" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosBegging" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rItem_Photo" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rItem_Phone" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPhoneLoc" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosTalkPhone" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosBuzzerSound" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosBegDesk" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosHitmanDialog" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSwingKingPosForDialog" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknownFlags_0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknownFlags_1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] + }, + "m00\\flirtingguard": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSecretary" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrSwingKing" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosGuardFlirt" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosGuardWindow" } + ] + }, + "m00\\secretary": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFlirtingGuard" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMrSwingKing" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosSecretarySitDown" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosSecretaryTalkToSwingKing" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosSecretaryFlirt" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSodaItem" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDrinkPlacePos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosDrink" } + ] + }, + "m00\\m00_playinggangster": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__m00_playinggangster__Unk0" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCardsItem" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPlayingGuard" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__m00_playinggangster__Unk1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPissingGuard" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rChair" } + ] + }, + "m00\\m00_tensiongangster": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTensionGuardPos" } + ] + }, + "m00\\m00_friskguard": { + "parent": "alllevels\\guard", + "parameters": [] + }, + "m00\\m00_druglabgangster": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUtilitiesBox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rRepairUtilBox_Guy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rStandGuard" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rStandGuardInDarkness" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__m00_druglabgangster__Unk0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIgnoreSoundsBox" } + ] + }, + "m00\\guardinelevator": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosInElevator" } + ] + }, + "m00\\druggirl": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMakeDrugs_UsePoint0" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMakeDrugs_UsePoint1" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rMakeDrugs_UsePoint2" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk0" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk1" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] + }, + "m00\\lawyer": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk3" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk4" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSitPosition" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPsychoBob" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk5" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__UnkMask0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] } } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 2ef1055..297e4f4 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -88,6 +88,10 @@ public slots: void onTextureChanged(uint32_t textureIndex); void onSceneFramePresented(const widgets::RenderStats& stats); +protected: + void dragEnterEvent(QDragEnterEvent* pEvent) override; + void dropEvent(QDropEvent* pEvent) override; + private: // UI Ui::BMEditMainWindow *ui; diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index c5ce38d..2bbdcf5 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #include #include @@ -64,6 +67,9 @@ BMEditMainWindow::BMEditMainWindow(QWidget *parent) : connectDockWidgetActions(); connectEditorSignals(); loadTypesDataBase(); + + // Drag & Drop for levels + setAcceptDrops(true); } BMEditMainWindow::~BMEditMainWindow() @@ -583,6 +589,39 @@ void BMEditMainWindow::onSceneFramePresented(const widgets::RenderStats& stats) .arg(iApproxFPS)); } +void BMEditMainWindow::dragEnterEvent(QDragEnterEvent *pEvent) +{ + if (pEvent->mimeData()->hasUrls()) + { + for (const QUrl& url : pEvent->mimeData()->urls()) + { + if (QFileInfo(url.toLocalFile()).suffix().toLower() == "zip") + { + pEvent->acceptProposedAction(); + return; + } + } + } +} + +void BMEditMainWindow::dropEvent(QDropEvent *pEvent) +{ + if (pEvent->mimeData()->hasUrls()) + { + QString info; + + for (const QUrl& url : pEvent->mimeData()->urls()) + { + QString fileName = url.toLocalFile(); + if (QFileInfo(fileName).suffix().toLower() == "zip") + { + editor::EditorInstance::getInstance().openLevelFromZIP(fileName.toStdString()); + return; + } + } + } +} + void BMEditMainWindow::loadTypesDataBase() { m_operationProgress->setValue(OperationToProgress::DISCOVER_TYPES_DATABASE); From 740026b0b321182fd7fdecd0a39ebefa474d456f Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 8 Jun 2024 20:21:01 +0300 Subject: [PATCH 68/80] Fix regression: bad rendering on M13 --- BMEdit/Editor/Include/Render/Camera.h | 2 +- BMEdit/Editor/Source/Render/Camera.cpp | 2 +- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BMEdit/Editor/Include/Render/Camera.h b/BMEdit/Editor/Include/Render/Camera.h index c326c2d..6ee322d 100644 --- a/BMEdit/Editor/Include/Render/Camera.h +++ b/BMEdit/Editor/Include/Render/Camera.h @@ -79,7 +79,7 @@ namespace render float m_fSpeed { 2.5f }; float m_fSensitivity { 0.1f }; float m_fNearPlane { .01f }; - float m_fFarPlane { 3'000.f }; + float m_fFarPlane { 10'000.f }; // Calculated things glm::vec3 m_vPosition { .0f }; diff --git a/BMEdit/Editor/Source/Render/Camera.cpp b/BMEdit/Editor/Source/Render/Camera.cpp index 3b7adbc..3e9b724 100644 --- a/BMEdit/Editor/Source/Render/Camera.cpp +++ b/BMEdit/Editor/Source/Render/Camera.cpp @@ -112,7 +112,7 @@ namespace render bool Camera::canSeeObject(const gamelib::BoundingBox& bbox) const { - return glm::distance(m_vPosition, bbox.getCenter()) <= m_fFarPlane && m_sFrustum.isBoxVisible(bbox.min, bbox.max); + return m_sFrustum.isBoxVisible(bbox.min, bbox.max); } bool Camera::canSeeObject(const gamelib::Plane& plane) const diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index b6a51bf..b456fd7 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -1346,7 +1346,7 @@ namespace widgets if (bInvisible) return; - if (g_bannedObjectIds.contains(std::string_view{geom->getName()})) + if (g_bannedObjectIds.contains(std::string_view{geom->getName()}) || geom->getName().starts_with("CloneGroup_")) return; // Check that our 'object' is not a collision box From 31e93ced50210e14bc88139348643cfc2af20dab Mon Sep 17 00:00:00 2001 From: DronCode Date: Sat, 8 Jun 2024 20:52:16 +0300 Subject: [PATCH 69/80] M00: All scripts are reversed --- Assets/scripts/M00.json | 133 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 11 deletions(-) diff --git a/Assets/scripts/M00.json b/Assets/scripts/M00.json index 00d7553..20e86b3 100644 --- a/Assets/scripts/M00.json +++ b/Assets/scripts/M00.json @@ -190,11 +190,11 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSwingKingPosForDialog" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknownFlags_0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "rUnknownFlags_0" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnknownFlags_1" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "rUnknownFlags_1" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, @@ -228,7 +228,7 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPlayingGuard" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__m00_playinggangster__Unk1" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__m00_playinggangster__Unk1" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPissingGuard" }, @@ -254,7 +254,7 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rStandGuardInDarkness" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__m00_druglabgangster__Unk0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__m00_druglabgangster__Unk0" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIgnoreSoundsBox" } @@ -276,7 +276,7 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk1" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk2" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__druggirl__Unk2" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] }, @@ -284,21 +284,21 @@ "parent": "alllevels\\civilian", "parameters": [ { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__lawyer__Unk0" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk1" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__lawyer__Unk1" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk2" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__lawyer__Unk2" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__druggirl__Unk3" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__druggirl__Unk3" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk4" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__lawyer__Unk4" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSitPosition" }, @@ -306,7 +306,118 @@ { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__Unk5" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, - { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "__lawyer__UnkMask0" }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "__lawyer__UnkMask0" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } + ] + }, + "m00\\chemist": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rDoor" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rStandPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTakeMoneyPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rKnockSound" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rKeycard_Item" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rLevelControl" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rWaitTillDoor" } + ] + }, + "m00\\m00_testergangster_01": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSecondGuy" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rChair" } + ] + }, + "m00\\m00_gateguard": { + "parent": "alllevels\\civilian", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rHitmanPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnk" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPosAtGate" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rCutSequencePlayer" } + ] + }, + "m00\\m00_talkinggangstab": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTalkingGuardC" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFaceThisWayAfterDistraction" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIgnoreDistractionsThatAreInsideThisBox" } + ] + }, + "m00\\m00_talkinggangstac": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rTalkingGuardC" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFaceThisWayAfterDistraction" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rPos" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rIgnoreDistractionsThatAreInsideThisBox" } + ] + }, + "m00\\torturegangster": { + "parent": "alllevels\\guard", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rLawyer" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rInterrogatePoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rSmokePoint" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGasolineItem" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGasolineEmitter" } + ] + }, + "m00\\m00_basebird": { + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rFlyBox" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rUnk0" }, + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rGroundPoints" }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnk1" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnk2" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnk3" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Float32", "name": "fUnk4" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnk5" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnk6" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnk7" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "iUnk8" }, + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] }, + + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rBirdEffectList" } + ] + }, + "m00\\peckingbird": { + "parent": "m00\\m00_basebird", + "parameters": [ + { "kind": "VARIABLE", "typename": "ZGEOMREF", "name": "rArrowDir" } + ] + }, + "m00\\entrancebird": { + "parent": "m00\\m00_basebird", + "parameters": [ + { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.Array"] }, + { "kind": "VARIABLE", "typename": "PRPOpCode.Int32", "name": "peckingbird_Unk0" }, { "kind": "UNKNOWN", "opcodes": ["PRPOpCode.EndArray"] } ] } From fe767d3c0a0c72d7b07b3cce75245ffed04d6b26 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 9 Jun 2024 09:52:25 +0300 Subject: [PATCH 70/80] Added LOC file support (read & write) --- BMEdit/GameLib/Include/GameLib/LOC/LOC.h | 5 + .../GameLib/Include/GameLib/LOC/LOCReader.h | 22 +++ .../GameLib/Include/GameLib/LOC/LOCTreeNode.h | 74 +++++++++ .../GameLib/Include/GameLib/LOC/LOCWriter.h | 15 ++ BMEdit/GameLib/Include/GameLib/Level.h | 10 ++ BMEdit/GameLib/Include/GameLib/ZBioHelpers.h | 24 ++- BMEdit/GameLib/Source/GameLib/LOC/LOC.cpp | 1 + .../GameLib/Source/GameLib/LOC/LOCReader.cpp | 55 +++++++ .../Source/GameLib/LOC/LOCTreeNode.cpp | 155 ++++++++++++++++++ .../GameLib/Source/GameLib/LOC/LOCWriter.cpp | 54 ++++++ BMEdit/GameLib/Source/GameLib/Level.cpp | 37 +++++ 11 files changed, 444 insertions(+), 8 deletions(-) create mode 100644 BMEdit/GameLib/Include/GameLib/LOC/LOC.h create mode 100644 BMEdit/GameLib/Include/GameLib/LOC/LOCReader.h create mode 100644 BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h create mode 100644 BMEdit/GameLib/Include/GameLib/LOC/LOCWriter.h create mode 100644 BMEdit/GameLib/Source/GameLib/LOC/LOC.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/LOC/LOCReader.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp create mode 100644 BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOC.h b/BMEdit/GameLib/Include/GameLib/LOC/LOC.h new file mode 100644 index 0000000..e7347bf --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOC.h @@ -0,0 +1,5 @@ +#pragma once + +#include +#include +#include \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCReader.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCReader.h new file mode 100644 index 0000000..df90062 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCReader.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + + +namespace gamelib::loc +{ + class LOCReader + { + public: + LOCReader(); + + bool parse(const uint8_t *locFileBuffer, int64_t locFileSize); + + [[nodiscard]] const LOCTreeNode::Ptr& getRoot() const; + + private: + LOCTreeNode::Ptr m_pRoot { nullptr }; + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h new file mode 100644 index 0000000..e54ba12 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + + +namespace ZBio::ZBinaryReader +{ + class BinaryReader; +} + +namespace ZBio::ZBinaryWriter +{ + class BinaryWriter; +} + +namespace gamelib::loc +{ + enum LOCTreeNodeType : int8_t + { + EMPTY_BLOCK = 0x8, ///< Empty chunk, no data at all + LOCALIZED_STRING = 0x9, ///< Key value (string to aligned string) + CHILDREN = 0x10, ///< Container (amount & list of offsets) + SUBTITLES = 0x29, ///< Subtitles value (long text with extra parameters) | 0x20 mask means that extra data exists + }; + + enum LOCMissionObjectiveType : char + { + HBM_Target = 'T', + HBM_Retrieve = 'R', + HBM_Escape = 'E', + HBM_Dispose = 'D', + HBM_Protect = 'P', + HBM_Optional = 'O' + }; + + /** + * Notes: + * + * 1. Decompiler ref: https://github.com/ReGlacier/ReHitmanTools/blob/main/Tools/LOCC/source/Decompiler.cpp + * 2. Format notes + * File format; + * 1. Root node name omitted, starts from amount of nodes [u8] + * 2. If amount more than 1 - offsets segment starts there + * + * Node format: + * Name - CString + * +0x0 - kind of node (TreeNodeType : 0x0 - value or data (leaf), 0x10 - node with children (bone) + * +0x1 - how much child nodes coming (but +1) + * Then going list of offsets by 0x4 bytes each. Each offset starts after offsets block + * + * We should read + */ + struct LOCTreeNode + { + using Ptr = std::shared_ptr; + using Ref = std::weak_ptr; + + std::vector children {}; // When NODE_WITH_CHILDREN + LOCTreeNode::Ref parent {}; + LOCTreeNodeType type { LOCTreeNodeType::LOCALIZED_STRING }; + std::string name {}; + std::string value {}; // When SIMPLE_VALUE or PARAGRAPH_VALUE + struct SubtitleData + { + uint8_t unkData[8]; + } subtitle; + + static void deserialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader* binaryReader); + static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter); + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCWriter.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCWriter.h new file mode 100644 index 0000000..ce06d38 --- /dev/null +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCWriter.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + + +namespace gamelib::loc +{ + class LOCWriter + { + public: + static void write(const LOCTreeNode::Ptr& root, std::vector &outBuffer); + }; +} \ No newline at end of file diff --git a/BMEdit/GameLib/Include/GameLib/Level.h b/BMEdit/GameLib/Include/GameLib/Level.h index 20de052..2d7f436 100644 --- a/BMEdit/GameLib/Include/GameLib/Level.h +++ b/BMEdit/GameLib/Include/GameLib/Level.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -68,6 +69,11 @@ namespace gamelib RoomGroup inside {}; }; + struct LevelLocalization + { + loc::LOCTreeNode::Ptr localizationRoot { nullptr }; + }; + class Level { public: @@ -87,6 +93,8 @@ namespace gamelib [[nodiscard]] LevelMaterials* getLevelMaterials(); [[nodiscard]] const LevelRooms* getLevelRooms() const; [[nodiscard]] LevelRooms* getLevelRooms(); + [[nodiscard]] const LevelLocalization* getLevelLocalization() const; + [[nodiscard]] LevelLocalization* getLevelLocalization(); [[nodiscard]] const std::vector& getSceneObjects() const; @@ -108,6 +116,7 @@ namespace gamelib bool loadLevelTextures(); bool loadLevelMaterials(); bool loadLevelRooms(); + bool loadLevelLocalization(); private: // Core @@ -121,6 +130,7 @@ namespace gamelib LevelGeometry m_levelGeometry; LevelMaterials m_levelMaterials; LevelRooms m_levelRooms; + LevelLocalization m_levelLocalization; struct BUF { diff --git a/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h index 8387a01..6263387 100644 --- a/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h +++ b/BMEdit/GameLib/Include/GameLib/ZBioHelpers.h @@ -32,6 +32,14 @@ namespace gamelib } }; + template + concept TSeekable = requires (T t) + { + t.seek((int64_t)1337); + t.tell(); + }; + + template requires (TSeekable) struct ZBioSeekGuard { ZBioSeekGuard() = delete; @@ -40,27 +48,27 @@ namespace gamelib ZBioSeekGuard& operator=(const ZBioSeekGuard&) = delete; ZBioSeekGuard& operator=(ZBioSeekGuard&&) = delete; - explicit ZBioSeekGuard(ZBio::ZBinaryReader::BinaryReader* reader) + explicit ZBioSeekGuard(T* seekable) { - m_reader = reader; + m_seekable = seekable; - if (reader) + if (seekable) { - m_seekTo = reader->tell(); + m_seekTo = seekable->tell(); } } ~ZBioSeekGuard() { - if (m_reader) + if (m_seekable) { - m_reader->seek(m_seekTo); - m_reader = nullptr; + m_seekable->seek(m_seekTo); + m_seekable = nullptr; } } private: - ZBio::ZBinaryReader::BinaryReader* m_reader { nullptr }; + T* m_seekable { nullptr }; int64_t m_seekTo { 0 }; }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOC.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOC.cpp new file mode 100644 index 0000000..228cb42 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOC.cpp @@ -0,0 +1 @@ +#include \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCReader.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCReader.cpp new file mode 100644 index 0000000..6af546f --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCReader.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + + +namespace gamelib::loc +{ + LOCReader::LOCReader() = default; + + bool LOCReader::parse(const uint8_t *locFileBuffer, int64_t locFileSize) + { + // First node is always ROOT and starts from 1 byte 0x1 + ZBio::ZBinaryReader::BinaryReader reader { (const char*)locFileBuffer, (int64_t)locFileSize }; + + // Check first byte + auto rootChildNr = reader.read(); + if (rootChildNr < 1) return false; // no root nodes found + + // Create pseudo ROOT + m_pRoot = std::make_shared(); + m_pRoot->type = LOCTreeNodeType::CHILDREN; + m_pRoot->name = "ROOT"; + + std::vector offsets {}; + offsets.resize(rootChildNr); + offsets[0] = 0x0; // zero offset + + for (int i = 0; i < rootChildNr - 1; i++) + { + offsets[i + 1] = reader.read(); + } + + // Create children + m_pRoot->children.resize(rootChildNr); + + for (int i = 0; i < rootChildNr; i++) + { + ZBioSeekGuard guard { &reader }; + ZBioHelpers::seekBy(&reader, offsets[i]); + + m_pRoot->children[i] = std::make_shared(); + m_pRoot->children[i]->parent = m_pRoot; // store parent + + LOCTreeNode::deserialize(m_pRoot->children[i], &reader); + } + + return true; + } + + const LOCTreeNode::Ptr& LOCReader::getRoot() const + { + return m_pRoot; + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp new file mode 100644 index 0000000..48ae1c8 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include + + +namespace gamelib::loc +{ + void LOCTreeNode::deserialize(const gamelib::loc::LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader *binaryReader) + { + // Read self name + node->name = binaryReader->readCString(); + + const auto type = binaryReader->read(); + if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING && type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK) + { + throw std::runtime_error("Invalid LOC format: expected to have 0x0 or 0x10, got smth else"); + } + + // Store type + node->type = static_cast(type); + + if (node->type == LOCTreeNodeType::CHILDREN) + { + // Read amount + auto amount = binaryReader->read(); + if (amount) + { + node->children.reserve(amount); + + std::vector offsets {}; + offsets.resize(amount); + offsets[0] = 0x0; // zero offset since next nodes + + for (int i = 1; i < amount; i++) + { + offsets[i] = binaryReader->read(); + } + + // Now we've ready to iterate over offsets and read child nodes + for (const auto& offset : offsets) + { + ZBioSeekGuard guard { binaryReader }; + ZBioHelpers::seekBy(binaryReader, offset); + + auto childNode = std::make_shared(); + childNode->parent = node; + + // Read node + LOCTreeNode::deserialize(childNode, binaryReader); + + // Save node + node->children.emplace_back(childNode); + } + } + } + else if (node->type == LOCTreeNodeType::LOCALIZED_STRING) + { + // Read string value + node->value = binaryReader->readCString(); + ZBioHelpers::seekBy(binaryReader, 4); // Need seek by 4 because value strings are "aligned". + // Formula: len + 1 + 4 (+1 - zero terminator, 4 - "alignment") + } + else if (node->type == LOCTreeNodeType::SUBTITLES) + { + // Subtitles text + node->value = binaryReader->readCString(); + + // Read subtitle data. I really don't know what that value means, but as 2xu32 each same to each + binaryReader->read(&node->subtitle.unkData[0], 8); + + // Check that next byte is always valid for us + { + ZBioSeekGuard guard { binaryReader }; + auto lb = binaryReader->read(); + assert((lb >= 'a' && lb <='z') || (lb >= 'A' && lb <= 'Z') || (lb >= '0' && lb <= '9')); + } + } + else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) + { + // Do nothing here, it's just empty + } + else + { + throw std::runtime_error { "Unsupported block" }; + } + } + + void LOCTreeNode::serialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryWriter::BinaryWriter *binaryWriter) // NOLINT(*-no-recursion) + { + // Write name (not aligned) + binaryWriter->writeCString(node->name); + + // Write node type + binaryWriter->write(static_cast(node->type)); + + if (node->type == LOCTreeNodeType::CHILDREN) + { + // Write count + const auto childrenCount = node->children.size() > 0xFF ? 0xFF : static_cast(node->children.size()); + binaryWriter->write(childrenCount); + + // Write offsets + std::vector offsets{}; + + for (int i = 1; i < childrenCount; i++) + { + offsets.push_back(binaryWriter->tell()); + binaryWriter->write(0); // Fake offset (will be fixed) + } + + // Write node by node & restore offsets + const uint32_t baseOffset = binaryWriter->tell(); + + for (int i = 0; i < childrenCount; i++) + { + if (i > 0) + { + // Restore offset + uint32_t newOffset = binaryWriter->tell(); + + ZBioSeekGuard guard { binaryWriter }; + binaryWriter->seek(offsets[i - 1]); + + // Update offset + binaryWriter->write(newOffset - baseOffset); // Offset calculated from node #0 + } + + // Write node itself + LOCTreeNode::serialize(node->children[i], binaryWriter); + } + } + else if (node->type == LOCTreeNodeType::LOCALIZED_STRING) + { + // Write string + binaryWriter->writeCString(node->value); + + // Write 4 byte alignment + binaryWriter->write(0); + } + else if (node->type == LOCTreeNodeType::SUBTITLES) + { + // Write unaligned string + binaryWriter->writeCString(node->value); + + // Write subtitles data + binaryWriter->write(&node->subtitle.unkData[0], 8); + } + else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) + { + // Do nothing here + } + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp new file mode 100644 index 0000000..86568e3 --- /dev/null +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + + +namespace gamelib::loc +{ + void LOCWriter::write(const LOCTreeNode::Ptr &root, std::vector &outBuffer) + { + if (!root || root->children.empty()) return; + + auto writerSink = std::make_unique(); + auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink)); + + // Write base + binaryWriter.write(static_cast(root->children.size())); + + std::vector offsets {}; + + // Fill offsets if root child more than 1 + for (int i = 1; i < root->children.size(); i++) + { + // Here we need to know an offset of subject after first created node. Instead of that we will save current offset and then jump back + offsets.push_back(binaryWriter.tell()); + binaryWriter.write(0); // Temp value, will be replaced later + } + + // And now we've ready to write node by node + const uint32_t baseOffset = binaryWriter.tell(); // Save current offset (need to calculate relative offset) + + for (int i = 0; i < root->children.size(); i++) + { + if (i > 0) + { + // Save current offset + uint32_t newOffset = binaryWriter.tell(); + + // Seek back to origin + ZBioSeekGuard guard { &binaryWriter }; + binaryWriter.seek(offsets[i - 1]); + + // Update offset + binaryWriter.write(newOffset - baseOffset); // Offset calculated from node #0 + } + + // Write node itself + LOCTreeNode::serialize(root->children[i], &binaryWriter); + } + + // Done + auto raw = binaryWriter.release().value(); + std::copy(raw.begin(), raw.end(), std::back_inserter(outBuffer)); + } +} \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 896531a..03a9512 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -85,6 +85,11 @@ namespace gamelib return false; } + if (!loadLevelLocalization()) + { + return false; + } + // TODO: Load things (it's time to combine GMS, PRP & BUF files) m_isLevelLoaded = true; return true; @@ -162,6 +167,16 @@ namespace gamelib return &m_levelRooms; } + const LevelLocalization* Level::getLevelLocalization() const + { + return &m_levelLocalization; + } + + LevelLocalization* Level::getLevelLocalization() + { + return &m_levelLocalization; + } + const std::vector &Level::getSceneObjects() const { return m_sceneObjects; @@ -506,4 +521,26 @@ namespace gamelib return true; } + + bool Level::loadLevelLocalization() + { + int64_t locFileSize = 0; + auto locFileBuffer = m_assetProvider->getAsset(gamelib::io::AssetKind::LOCALIZATION, locFileSize); + + if (!locFileBuffer || !locFileSize) + { + return false; + } + + loc::LOCReader locReader {}; + const bool bParseResult = locReader.parse(locFileBuffer.get(), locFileSize); + + if (!bParseResult) + { + return false; + } + + m_levelLocalization.localizationRoot = locReader.getRoot(); + return m_levelLocalization.localizationRoot != nullptr && !m_levelLocalization.localizationRoot->children.empty(); + } } \ No newline at end of file From 8e7036ba3df0556124e62ce40061bf8dc228394e Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 9 Jun 2024 15:19:45 +0300 Subject: [PATCH 71/80] Added localization viewer --- .../Include/Models/LocalizationTreeModel.h | 34 ++++ BMEdit/Editor/Include/Models/ModelsLocator.h | 2 + .../Source/Models/LocalizationTreeModel.cpp | 180 ++++++++++++++++++ BMEdit/Editor/Source/Models/ModelsLocator.cpp | 1 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 12 ++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 22 +++ 6 files changed, 251 insertions(+) create mode 100644 BMEdit/Editor/Include/Models/LocalizationTreeModel.h create mode 100644 BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp diff --git a/BMEdit/Editor/Include/Models/LocalizationTreeModel.h b/BMEdit/Editor/Include/Models/LocalizationTreeModel.h new file mode 100644 index 0000000..d6e0e2f --- /dev/null +++ b/BMEdit/Editor/Include/Models/LocalizationTreeModel.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + + +namespace models +{ + class LocalizationTreeModel : public QAbstractItemModel + { + Q_OBJECT + public: + explicit LocalizationTreeModel(QObject *parent = nullptr); + explicit LocalizationTreeModel(const gamelib::Level *level, QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + void setLevel(const gamelib::Level *level); + void resetLevel(); + QModelIndex getRootIndex() const; + + private: + [[nodiscard]] bool isValidLevel() const; + + private: + const gamelib::Level *m_level { nullptr }; + }; +} \ No newline at end of file diff --git a/BMEdit/Editor/Include/Models/ModelsLocator.h b/BMEdit/Editor/Include/Models/ModelsLocator.h index 1bb6bfb..ad788a0 100644 --- a/BMEdit/Editor/Include/Models/ModelsLocator.h +++ b/BMEdit/Editor/Include/Models/ModelsLocator.h @@ -2,6 +2,7 @@ #include +#include #include #include @@ -21,5 +22,6 @@ namespace models // Instances static std::unique_ptr s_SceneTreeModel; static std::unique_ptr s_GameScriptsTreeModel; + static std::unique_ptr s_LocalizationTreeModel; }; } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp new file mode 100644 index 0000000..2e232be --- /dev/null +++ b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp @@ -0,0 +1,180 @@ +#include + + +namespace models +{ + LocalizationTreeModel::LocalizationTreeModel(QObject *parent) : QAbstractItemModel(parent) + { + } + + LocalizationTreeModel::LocalizationTreeModel(const gamelib::Level *level, QObject *parent) : QAbstractItemModel(parent) + { + setLevel(level); + } + + QVariant LocalizationTreeModel::data(const QModelIndex &index, int role) const + { + if (!isValidLevel()) + { + return QVariant {}; + } + + const auto* node = reinterpret_cast(index.constInternalPointer()); + if (!node) return {}; + + if (role == Qt::DisplayRole) + { + if (index.column() == 0) return QString::fromStdString(node->name); + if (index.column() == 1 && (node->type == gamelib::loc::LOCTreeNodeType::LOCALIZED_STRING || node->type == gamelib::loc::LOCTreeNodeType::SUBTITLES)) + { + return QString::fromStdString(node->value); + } + + return {}; + } + + if (role == Qt::ItemDataRole::ToolTipRole) + { + QStringList locationPath {}; + + auto currentNode = node; + while (currentNode) + { + locationPath.push_front(QString::fromStdString(currentNode->name)); + const auto& parent = currentNode->parent.lock(); + currentNode = parent ? parent.get() : nullptr; + } + + return locationPath.join('/'); + } + + return {}; + } + + QModelIndex LocalizationTreeModel::index(int row, int column, const QModelIndex &parent) const + { + if (!hasIndex(row, column, parent) || !isValidLevel()) + { + return QModelIndex {}; + } + + gamelib::loc::LOCTreeNode* node = nullptr; + if (!parent.isValid()) + { + node = m_level->getLevelLocalization()->localizationRoot.get(); + } + else + { + node = static_cast(parent.internalPointer()); + } + + if (row >= 0 && row < node->children.size()) + { + return createIndex(row, column, (const void*)node->children[row].get()); + } + + return {}; + } + + QModelIndex LocalizationTreeModel::parent(const QModelIndex &index) const + { + if (!index.isValid() || !isValidLevel()) + { + return {}; + } + + auto* child = static_cast(index.internalPointer()); + if (auto parent = child->parent.lock()) + { + if (parent == m_level->getLevelLocalization()->localizationRoot) + { + return {}; + } + else + { + int row = 0; + + for (int i = 0; i < parent->children.size(); ++i) + { + if (parent->children[i].get() == child) + { + row = i; + break; + } + } + + return createIndex(row, 0, (const void*)parent.get()); + } + } + + return {}; + } + + int LocalizationTreeModel::rowCount(const QModelIndex &parent) const + { + if (!isValidLevel()) return 0; + + if (!parent.isValid()) + { + const auto& root = m_level->getLevelLocalization()->localizationRoot; + + return root->children.empty() ? 0 : static_cast(root->children.size()); + } + + if (auto node = static_cast(parent.internalPointer())) + { + if (node->type == gamelib::loc::CHILDREN) + return static_cast(node->children.size()); + + return 0; // only value + } + + return 0; + } + + int LocalizationTreeModel::columnCount(const QModelIndex &parent) const + { + if (!isValidLevel()) return 0; + return 2; // 1 for tree, 1 for value + } + + QVariant LocalizationTreeModel::headerData(int section, Qt::Orientation orientation, int role) const + { + if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) + { + if (section == 0) return "Tree"; + if (section == 1) return "Value"; + } + + return QAbstractItemModel::headerData(section, orientation, role); + } + + void LocalizationTreeModel::setLevel(const gamelib::Level *level) + { + beginResetModel(); + m_level = level; + endResetModel(); + } + + void LocalizationTreeModel::resetLevel() + { + beginResetModel(); + m_level = nullptr; + endResetModel(); + } + + QModelIndex LocalizationTreeModel::getRootIndex() const + { + if (!m_level || !m_level->getLevelLocalization()) + { + return {}; + } + + return createIndex(0, 0, (const void*)m_level->getLevelLocalization()->localizationRoot.get()); + } + + bool LocalizationTreeModel::isValidLevel() const + { + return m_level && m_level->getLevelLocalization(); + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/ModelsLocator.cpp b/BMEdit/Editor/Source/Models/ModelsLocator.cpp index a02d806..eb2a7b6 100644 --- a/BMEdit/Editor/Source/Models/ModelsLocator.cpp +++ b/BMEdit/Editor/Source/Models/ModelsLocator.cpp @@ -5,4 +5,5 @@ namespace models { std::unique_ptr ModelsLocator::s_SceneTreeModel { nullptr }; std::unique_ptr ModelsLocator::s_GameScriptsTreeModel { nullptr }; + std::unique_ptr ModelsLocator::s_LocalizationTreeModel { nullptr }; } \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 2bbdcf5..317cf8f 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -79,6 +79,7 @@ BMEditMainWindow::~BMEditMainWindow() delete m_sceneTreeFilterModel; models::ModelsLocator::s_SceneTreeModel = nullptr; // reset me models::ModelsLocator::s_GameScriptsTreeModel = nullptr; // reset me + models::ModelsLocator::s_LocalizationTreeModel = nullptr; // reset me delete m_sceneObjectPropertiesModel; delete m_operationProgress; @@ -274,6 +275,11 @@ void BMEditMainWindow::onLevelLoadSuccess() models::ModelsLocator::s_SceneTreeModel->setLevel(currentLevel); } + if (models::ModelsLocator::s_LocalizationTreeModel) + { + models::ModelsLocator::s_LocalizationTreeModel->setLevel(currentLevel); + } + if (m_sceneTexturesModel) { m_sceneTexturesModel->setLevel(currentLevel); @@ -422,6 +428,7 @@ void BMEditMainWindow::onCloseLevel() { // Cleanup models if (models::ModelsLocator::s_SceneTreeModel) models::ModelsLocator::s_SceneTreeModel->resetLevel(); + if (models::ModelsLocator::s_LocalizationTreeModel) models::ModelsLocator::s_LocalizationTreeModel->resetLevel(); if (m_sceneObjectPropertiesModel) m_sceneObjectPropertiesModel->resetLevel(); if (m_scenePropertiesModel) m_scenePropertiesModel->resetLevel(); if (m_sceneTexturesModel) m_sceneTexturesModel->resetLevel(); @@ -778,9 +785,14 @@ void BMEditMainWindow::initSceneTree() { // Main model models::ModelsLocator::s_SceneTreeModel = std::make_unique(this); + models::ModelsLocator::s_LocalizationTreeModel = std::make_unique(this); m_sceneTreeFilterModel = new models::SceneFilterModel(this); m_sceneTreeFilterModel->setSourceModel(models::ModelsLocator::s_SceneTreeModel.get()); + // Fill LocalizationTree view + ui->localizationTree->setModel(models::ModelsLocator::s_LocalizationTreeModel.get()); + + // Fill SceneTree view ui->sceneTreeView->header()->setSectionResizeMode(QHeaderView::Stretch); ui->sceneTreeView->setModel(m_sceneTreeFilterModel); ui->sceneTreeView->setContextMenuPolicy(Qt::CustomContextMenu); diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index d5b0fb4..e4f8e03 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -371,6 +371,28 @@ + + + Localization + + + 1 + + + + + + + Search... + + + + + + + + + Open level From da6eb018a016985b22bb6f0faf24ef2c742632ca Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 9 Jun 2024 15:29:04 +0300 Subject: [PATCH 72/80] Fixed crash in extra checks. Fixed hints (in value section hint will show text, not path) --- .../Source/Models/LocalizationTreeModel.cpp | 24 ++++++++++++------- .../Source/GameLib/LOC/LOCTreeNode.cpp | 7 ------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp index 2e232be..b5b2296 100644 --- a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp +++ b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp @@ -35,17 +35,25 @@ namespace models if (role == Qt::ItemDataRole::ToolTipRole) { - QStringList locationPath {}; - - auto currentNode = node; - while (currentNode) + if (index.column() == 0) { - locationPath.push_front(QString::fromStdString(currentNode->name)); - const auto& parent = currentNode->parent.lock(); - currentNode = parent ? parent.get() : nullptr; + QStringList locationPath {}; + + auto currentNode = node; + while (currentNode) + { + locationPath.push_front(QString::fromStdString(currentNode->name)); + const auto& parent = currentNode->parent.lock(); + currentNode = parent ? parent.get() : nullptr; + } + + return locationPath.join('/'); } - return locationPath.join('/'); + if (index.column() == 1 && (node->type == gamelib::loc::LOCTreeNodeType::LOCALIZED_STRING || node->type == gamelib::loc::LOCTreeNodeType::SUBTITLES)) + { + return QString::fromStdString(node->value); + } } return {}; diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp index 48ae1c8..41f8c10 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp @@ -69,13 +69,6 @@ namespace gamelib::loc // Read subtitle data. I really don't know what that value means, but as 2xu32 each same to each binaryReader->read(&node->subtitle.unkData[0], 8); - - // Check that next byte is always valid for us - { - ZBioSeekGuard guard { binaryReader }; - auto lb = binaryReader->read(); - assert((lb >= 'a' && lb <='z') || (lb >= 'A' && lb <= 'Z') || (lb >= '0' && lb <= '9')); - } } else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) { From f815aee47ef7fd5395eac550563ee0877323361b Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 9 Jun 2024 18:13:22 +0300 Subject: [PATCH 73/80] Implemented base editor & added key sort in serializer --- BMEdit/Editor/Include/Editor/EditorInstance.h | 1 + .../Include/Models/LocalizationTreeModel.h | 2 + BMEdit/Editor/Source/Edtor/EditorInstance.cpp | 28 ++++++++++ .../Source/Models/LocalizationTreeModel.cpp | 54 ++++++++++++++++++- BMEdit/Editor/UI/Include/BMEditMainWindow.h | 1 + BMEdit/Editor/UI/Source/BMEditMainWindow.cpp | 27 ++++++++++ BMEdit/Editor/UI/UI/BMEditMainWindow.ui | 9 ++++ .../GameLib/Include/GameLib/LOC/LOCTreeNode.h | 3 ++ .../Source/GameLib/LOC/LOCTreeNode.cpp | 18 ++++++- .../GameLib/Source/GameLib/LOC/LOCWriter.cpp | 12 +++-- BMEdit/GameLib/Source/GameLib/Level.cpp | 4 ++ 11 files changed, 151 insertions(+), 8 deletions(-) diff --git a/BMEdit/Editor/Include/Editor/EditorInstance.h b/BMEdit/Editor/Include/Editor/EditorInstance.h index 4223a03..e3e77ac 100644 --- a/BMEdit/Editor/Include/Editor/EditorInstance.h +++ b/BMEdit/Editor/Include/Editor/EditorInstance.h @@ -31,6 +31,7 @@ namespace editor { void exportAsset(gamelib::io::AssetKind assetKind); bool exportPRP(const QString &filePath); + bool exportLOC(const QString &filePath); signals: void levelLoadSuccess(); diff --git a/BMEdit/Editor/Include/Models/LocalizationTreeModel.h b/BMEdit/Editor/Include/Models/LocalizationTreeModel.h index d6e0e2f..4dd84ca 100644 --- a/BMEdit/Editor/Include/Models/LocalizationTreeModel.h +++ b/BMEdit/Editor/Include/Models/LocalizationTreeModel.h @@ -15,11 +15,13 @@ namespace models explicit LocalizationTreeModel(const gamelib::Level *level, QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; void setLevel(const gamelib::Level *level); void resetLevel(); diff --git a/BMEdit/Editor/Source/Edtor/EditorInstance.cpp b/BMEdit/Editor/Source/Edtor/EditorInstance.cpp index 543eee5..632060c 100644 --- a/BMEdit/Editor/Source/Edtor/EditorInstance.cpp +++ b/BMEdit/Editor/Source/Edtor/EditorInstance.cpp @@ -215,4 +215,32 @@ namespace editor { return true; } + + bool EditorInstance::exportLOC(const QString &filePath) + { + std::vector locFileBuffer; + + if (!m_currentLevel) + { + return false; + } + + QFile locFile(filePath); + if (!locFile.open(QIODeviceBase::OpenModeFlag::WriteOnly | QIODeviceBase::OpenModeFlag::Truncate | QIODeviceBase::OpenModeFlag::Unbuffered)) + { + return false; + } + + m_currentLevel->dumpAsset(gamelib::io::AssetKind::LOCALIZATION, locFileBuffer); + + if (locFileBuffer.empty()) + { + return false; + } + + QByteArray raw(reinterpret_cast(locFileBuffer.data()), static_cast(locFileBuffer.size())); + locFile.write(raw); + + return true; + } } \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp index b5b2296..4260993 100644 --- a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp +++ b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp @@ -22,10 +22,23 @@ namespace models const auto* node = reinterpret_cast(index.constInternalPointer()); if (!node) return {}; + if (role == Qt::EditRole) + { + if (index.column() == 0) + { + return QString::fromStdString(node->name); + } + + if (index.column() == 1 && node->canHaveValue()) + { + return QString::fromStdString(node->value); + } + } + if (role == Qt::DisplayRole) { if (index.column() == 0) return QString::fromStdString(node->name); - if (index.column() == 1 && (node->type == gamelib::loc::LOCTreeNodeType::LOCALIZED_STRING || node->type == gamelib::loc::LOCTreeNodeType::SUBTITLES)) + if (index.column() == 1 && node->canHaveValue()) { return QString::fromStdString(node->value); } @@ -50,7 +63,7 @@ namespace models return locationPath.join('/'); } - if (index.column() == 1 && (node->type == gamelib::loc::LOCTreeNodeType::LOCALIZED_STRING || node->type == gamelib::loc::LOCTreeNodeType::SUBTITLES)) + if (index.column() == 1 && node->canHaveValue()) { return QString::fromStdString(node->value); } @@ -59,6 +72,28 @@ namespace models return {}; } + bool LocalizationTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) + { + if (!isValidLevel() || role != Qt::EditRole || !value.canConvert()) return false; + + auto* node = reinterpret_cast(index.internalPointer()); + if (!node) return false; + + if (index.column() == 0) + { + node->name = value.value().toStdString(); + return true; + } + + if (index.column() == 1 && node->canHaveValue()) + { + node->value = value.value().toStdString(); + return true; + } + + return false; + } + QModelIndex LocalizationTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent) || !isValidLevel()) @@ -157,6 +192,21 @@ namespace models return QAbstractItemModel::headerData(section, orientation, role); } + Qt::ItemFlags LocalizationTreeModel::flags(const QModelIndex &index) const + { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + const auto* node = reinterpret_cast(index.constInternalPointer()); + + if (!node) return flags; + + if (index.column() == 0 || (index.column() == 1 && node->canHaveValue())) + { + flags |= Qt::ItemIsEditable; + } + + return flags; + } + void LocalizationTreeModel::setLevel(const gamelib::Level *level) { beginResetModel(); diff --git a/BMEdit/Editor/UI/Include/BMEditMainWindow.h b/BMEdit/Editor/UI/Include/BMEditMainWindow.h index 297e4f4..d0f917a 100644 --- a/BMEdit/Editor/UI/Include/BMEditMainWindow.h +++ b/BMEdit/Editor/UI/Include/BMEditMainWindow.h @@ -80,6 +80,7 @@ public slots: void onAssetExportFailed(const QString &reason); void onCloseLevel(); void onExportPRP(); + void onExportLOC(); void onShowTexturesDialog(); void onContextMenuRequestedForSceneTreeNode(const QPoint& point); void onLevelAssetsLoaded(); diff --git a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp index 317cf8f..64d3d52 100644 --- a/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp +++ b/BMEdit/Editor/UI/Source/BMEditMainWindow.cpp @@ -116,6 +116,7 @@ void BMEditMainWindow::connectActions() connect(ui->actionTypes_Viewer, &QAction::triggered, this, &BMEditMainWindow::onShowTypesViewer); connect(ui->actionSave_properties, &QAction::triggered, this, &BMEditMainWindow::onExportProperties); connect(ui->actionExport_PRP_properties, &QAction::triggered, this, &BMEditMainWindow::onExportPRP); + connect(ui->actionExport_LOC_localization, &QAction::triggered, this, &BMEditMainWindow::onExportLOC); connect(ui->actionTextures, &QAction::triggered, this, &BMEditMainWindow::onShowTexturesDialog); connect(ui->actionView_whole_scene, &QAction::triggered, this, [this] { ui->sceneGLView->setWorldViewMode(); }); @@ -305,6 +306,7 @@ void BMEditMainWindow::onLevelLoadSuccess() // Export action ui->menuExport->setEnabled(true); ui->actionExport_PRP_properties->setEnabled(true); + ui->actionExport_LOC_localization->setEnabled(true); ui->actionTextures->setEnabled(true); //ui->actionSave_properties->setEnabled(true); //TODO: Uncomment when exporter to ZIP will be done @@ -442,6 +444,7 @@ void BMEditMainWindow::onCloseLevel() // Reset export menu ui->menuExport->setEnabled(false); ui->actionExport_PRP_properties->setEnabled(false); + ui->actionExport_LOC_localization->setEnabled(false); ui->actionTextures->setEnabled(false); // Reset world view mode @@ -485,6 +488,29 @@ void BMEditMainWindow::onExportPRP() QMessageBox::information(this, "Export PRP", QString("PRP file exported successfully to %1").arg(saveAsPath)); } +void BMEditMainWindow::onExportLOC() +{ + QFileDialog saveLOCDialog(this, QString("Save LOC"), QString(), QString("Localization (*.LOC)")); + saveLOCDialog.setViewMode(QFileDialog::ViewMode::Detail); + saveLOCDialog.setFileMode(QFileDialog::FileMode::AnyFile); + saveLOCDialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); + saveLOCDialog.selectFile(QString("%1.LOC").arg(QString::fromStdString(editor::EditorInstance::getInstance().getActiveLevel()->getLevelName()))); + if (!saveLOCDialog.exec()) + { + return; + } + + if (saveLOCDialog.selectedFiles().empty()) + { + return; + } + + const auto saveAsPath = saveLOCDialog.selectedFiles().first(); + editor::EditorInstance::getInstance().exportLOC(saveAsPath); + + QMessageBox::information(this, "Export LOC", QString("LOC file exported successfully to %1").arg(saveAsPath)); +} + void BMEditMainWindow::onShowTexturesDialog() { m_viewTexturesDialog.show(); @@ -791,6 +817,7 @@ void BMEditMainWindow::initSceneTree() // Fill LocalizationTree view ui->localizationTree->setModel(models::ModelsLocator::s_LocalizationTreeModel.get()); + ui->localizationTree->setSelectionMode(QAbstractItemView::SingleSelection); // Fill SceneTree view ui->sceneTreeView->header()->setSectionResizeMode(QHeaderView::Stretch); diff --git a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui index e4f8e03..e31f93a 100644 --- a/BMEdit/Editor/UI/UI/BMEditMainWindow.ui +++ b/BMEdit/Editor/UI/UI/BMEditMainWindow.ui @@ -42,6 +42,7 @@ Export + @@ -537,6 +538,14 @@ Render Room Bounding Boxes + + + false + + + Export LOC (localization) + + diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h index e54ba12..2e26fad 100644 --- a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h @@ -68,6 +68,9 @@ namespace gamelib::loc uint8_t unkData[8]; } subtitle; + [[nodiscard]] bool canHaveValue() const; + [[nodiscard]] bool canHaveChildren() const; + static void deserialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader* binaryReader); static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter); }; diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp index 41f8c10..79f4ec8 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp @@ -7,6 +7,16 @@ namespace gamelib::loc { + bool LOCTreeNode::canHaveValue() const + { + return type == LOCTreeNodeType::LOCALIZED_STRING || type == LOCTreeNodeType::SUBTITLES; + } + + bool LOCTreeNode::canHaveChildren() const + { + return type == LOCTreeNodeType::CHILDREN; + } + void LOCTreeNode::deserialize(const gamelib::loc::LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader *binaryReader) { // Read self name @@ -90,8 +100,12 @@ namespace gamelib::loc if (node->type == LOCTreeNodeType::CHILDREN) { + // Sort children + auto children = node->children; // Need copy + std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); + // Write count - const auto childrenCount = node->children.size() > 0xFF ? 0xFF : static_cast(node->children.size()); + const auto childrenCount = children.size() > 0xFF ? 0xFF : static_cast(children.size()); binaryWriter->write(childrenCount); // Write offsets @@ -121,7 +135,7 @@ namespace gamelib::loc } // Write node itself - LOCTreeNode::serialize(node->children[i], binaryWriter); + LOCTreeNode::serialize(children[i], binaryWriter); } } else if (node->type == LOCTreeNodeType::LOCALIZED_STRING) diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp index 86568e3..59aa01e 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp @@ -12,13 +12,17 @@ namespace gamelib::loc auto writerSink = std::make_unique(); auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink)); + // Sort children by name. It's required because game wants to interact with sorted tree + auto children = root->children; + std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); + // Write base - binaryWriter.write(static_cast(root->children.size())); + binaryWriter.write(static_cast(children.size())); std::vector offsets {}; // Fill offsets if root child more than 1 - for (int i = 1; i < root->children.size(); i++) + for (int i = 1; i < children.size(); i++) { // Here we need to know an offset of subject after first created node. Instead of that we will save current offset and then jump back offsets.push_back(binaryWriter.tell()); @@ -28,7 +32,7 @@ namespace gamelib::loc // And now we've ready to write node by node const uint32_t baseOffset = binaryWriter.tell(); // Save current offset (need to calculate relative offset) - for (int i = 0; i < root->children.size(); i++) + for (int i = 0; i < children.size(); i++) { if (i > 0) { @@ -44,7 +48,7 @@ namespace gamelib::loc } // Write node itself - LOCTreeNode::serialize(root->children[i], &binaryWriter); + LOCTreeNode::serialize(children[i], &binaryWriter); } // Done diff --git a/BMEdit/GameLib/Source/GameLib/Level.cpp b/BMEdit/GameLib/Source/GameLib/Level.cpp index 03a9512..049c616 100644 --- a/BMEdit/GameLib/Source/GameLib/Level.cpp +++ b/BMEdit/GameLib/Source/GameLib/Level.cpp @@ -245,6 +245,10 @@ namespace gamelib scene::SceneObjectPropertiesDumper dumper; dumper.dump(this, &outBuffer); } + else if (assetKind == io::AssetKind::LOCALIZATION) + { + loc::LOCWriter::write(m_levelLocalization.localizationRoot, outBuffer); + } else assert(false && "Unsupported"); } From 9445d6594b00a0be4fc0e01608dd14b4422fcaa8 Mon Sep 17 00:00:00 2001 From: DronCode Date: Sun, 9 Jun 2024 18:44:01 +0300 Subject: [PATCH 74/80] Added support of localized strings and custom tool to select localized strings --- Assets/g1/ZAction.json | 2 +- Assets/g1/ZLocalizedString.json | 6 + .../Source/Widgets/EditorToolFactory.cpp | 6 + .../UI/Include/SelectLocalizationTool.h | 66 +++++ .../UI/Source/SelectLocalizationTool.cpp | 267 ++++++++++++++++++ BMEdit/Editor/UI/UI/SelectLocalizationTool.ui | 31 ++ 6 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 Assets/g1/ZLocalizedString.json create mode 100644 BMEdit/Editor/UI/Include/SelectLocalizationTool.h create mode 100644 BMEdit/Editor/UI/Source/SelectLocalizationTool.cpp create mode 100644 BMEdit/Editor/UI/UI/SelectLocalizationTool.ui diff --git a/Assets/g1/ZAction.json b/Assets/g1/ZAction.json index b44b194..02787db 100644 --- a/Assets/g1/ZAction.json +++ b/Assets/g1/ZAction.json @@ -3,7 +3,7 @@ "parent": "ZEventBase", "kind": "TypeKind.COMPLEX", "properties": [ - { "name": "ActionName", "typename": "PRPOpCode.String" }, + { "name": "ActionName", "typename": "ZLocalizedString" }, { "name": "IsVisible", "typename": "PRPOpCode.Bool" }, { "name": "ActionType", "typename": "ZAction.EActionType", "offset": 44 }, { "name": "MessageID", "typename": "ZMsg", "offset": 48 }, diff --git a/Assets/g1/ZLocalizedString.json b/Assets/g1/ZLocalizedString.json new file mode 100644 index 0000000..affc011 --- /dev/null +++ b/Assets/g1/ZLocalizedString.json @@ -0,0 +1,6 @@ +{ + "typename": "ZLocalizedString", + "kind": "TypeKind.ALIAS", + "alias": "PRPOpCode.String", + "editor": "SelectLocalizationKey" +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp index 51e797d..11afb0e 100644 --- a/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp +++ b/BMEdit/Editor/Source/Widgets/EditorToolFactory.cpp @@ -1,6 +1,7 @@ #include // Widgets +#include #include #include #include @@ -22,6 +23,11 @@ namespace widgets pResult = SelectScriptTool::Create(parent); } + if (hintId == "SelectLocalizationKey") + { + pResult = SelectLocalizationTool::Create(parent); + } + if (!pResult) { qWarning() << "For hint '" << QString::fromStdString(hintId) << "' no editor created. Check name please"; diff --git a/BMEdit/Editor/UI/Include/SelectLocalizationTool.h b/BMEdit/Editor/UI/Include/SelectLocalizationTool.h new file mode 100644 index 0000000..469adf5 --- /dev/null +++ b/BMEdit/Editor/UI/Include/SelectLocalizationTool.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include + + +class Frontend_SelectLocalizationTool final : public widgets::TypePropertyWidget +{ + Q_OBJECT +public: + Frontend_SelectLocalizationTool(QWidget* parent, widgets::TypePropertyWidget* pTarget); + ~Frontend_SelectLocalizationTool() override; + + void setValue(const types::QGlacierValue &value) override; + [[nodiscard]] const types::QGlacierValue &getValue() const override; + + bool canHookFocus() const override; + +private slots: + void onTargetValueChanged(); + void onTargetEditFinished(); + +private: + void commitValue(const types::QGlacierValue &value); + +private: + widgets::TypePropertyWidget* m_pTarget { nullptr }; + QLabel* m_pLabel { nullptr }; +}; + + +namespace Ui { + class SelectLocalizationTool; +} + +class SelectLocalizationTool : public widgets::TypePropertyWidget +{ + Q_OBJECT + +public: + explicit SelectLocalizationTool(QWidget *parent = nullptr); + ~SelectLocalizationTool(); + + static Frontend_SelectLocalizationTool* Create(QWidget* parent); + + void setValue(const types::QGlacierValue &value) override; + +protected: + // buildLayout and updateLayout not implemented because no dynamic layout here. I'm just handling setValue + void closeEvent(QCloseEvent* pEvent) override; + +private: + void disableAcceptButton(); + void enableAcceptButton(); + void selectByPath(const QString& path); + +private slots: + void onAccepted(); + void onRejected(); + void onLocaleSelected(const QItemSelection &selected, const QItemSelection &deselected); + +private: + Ui::SelectLocalizationTool *m_ui; +}; \ No newline at end of file diff --git a/BMEdit/Editor/UI/Source/SelectLocalizationTool.cpp b/BMEdit/Editor/UI/Source/SelectLocalizationTool.cpp new file mode 100644 index 0000000..b0cee6a --- /dev/null +++ b/BMEdit/Editor/UI/Source/SelectLocalizationTool.cpp @@ -0,0 +1,267 @@ +#include "ui_SelectLocalizationTool.h" +#include +#include +#include +#include +#include +#include +#include + + +Frontend_SelectLocalizationTool::Frontend_SelectLocalizationTool(QWidget *parent, widgets::TypePropertyWidget* pTarget) + : widgets::TypePropertyWidget(parent) + , m_pTarget(pTarget) +{ + if (m_pTarget) + { + connect(m_pTarget, &widgets::TypePropertyWidget::valueChanged, this, &Frontend_SelectLocalizationTool::onTargetEditFinished); + connect(m_pTarget, &widgets::TypePropertyWidget::editFinished, this, &Frontend_SelectLocalizationTool::onTargetValueChanged); + + m_pTarget->setWindowModality(Qt::WindowModality::ApplicationModal); + m_pTarget->show(); + } + + // And build layout + auto* pLayout = new QHBoxLayout(this); + m_pLabel = new QLabel(this); + m_pLabel->setText("MAYBE"); + pLayout->addWidget(m_pLabel); + setLayout(pLayout); +} + +Frontend_SelectLocalizationTool::~Frontend_SelectLocalizationTool() +{ + m_pTarget = nullptr; +} + +void Frontend_SelectLocalizationTool::setValue(const types::QGlacierValue &value) +{ + if (m_pLabel && !value.instructions.empty() && value.instructions[0].isString()) + { + m_pLabel->setText(QString::fromStdString(value.instructions[0].getOperand().str)); + } + + if (m_pTarget) + { + m_pTarget->setValue(value); + } + + commitValue(value); +} + +const types::QGlacierValue &Frontend_SelectLocalizationTool::getValue() const +{ + static const types::QGlacierValue s_Invalid {}; + if (!m_pTarget) return s_Invalid; + return m_pTarget->getValue(); +} + +bool Frontend_SelectLocalizationTool::canHookFocus() const +{ + // Yep it can in this case + return true; +} + +void Frontend_SelectLocalizationTool::commitValue(const types::QGlacierValue &value) +{ + widgets::TypePropertyWidget::setValue(value); +} + +void Frontend_SelectLocalizationTool::onTargetValueChanged() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit editFinished(); + } +} + +void Frontend_SelectLocalizationTool::onTargetEditFinished() +{ + if (m_pTarget) + { + commitValue(m_pTarget->getValue()); + emit valueChanged(); + } +} + +SelectLocalizationTool::SelectLocalizationTool(QWidget* parent) : widgets::TypePropertyWidget(parent), m_ui(new Ui::SelectLocalizationTool) +{ + m_ui->setupUi(this); + + // Set model + m_ui->localizedStrings->setModel(models::ModelsLocator::s_LocalizationTreeModel.get()); + m_ui->localizedStrings->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + + // Connect signals + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, [this]() { + emit editFinished(); + close(); + }); + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectLocalizationTool::onAccepted); +} + +SelectLocalizationTool::~SelectLocalizationTool() +{ + delete m_ui; +} + +Frontend_SelectLocalizationTool *SelectLocalizationTool::Create(QWidget *parent) +{ + auto* pEditor = new SelectLocalizationTool(nullptr); + auto* pFrontend = new Frontend_SelectLocalizationTool(parent, pEditor); + return pFrontend; +} + +void SelectLocalizationTool::setValue(const types::QGlacierValue &value) +{ + // call for base + widgets::TypePropertyWidget::setValue(value); + + if (value.instructions.size() == 1 && value.instructions[0].isString()) + { + // Need to parse path + selectByPath(QString::fromStdString(value.instructions[0].getOperand().str)); + } +} + +void SelectLocalizationTool::disableAcceptButton() +{ + if (QPushButton* pOkButton = m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) + { + pOkButton->setEnabled(false); + } +} + +void SelectLocalizationTool::enableAcceptButton() +{ + if (QPushButton* pOkButton = m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) + { + pOkButton->setEnabled(true); + } +} + +void SelectLocalizationTool::selectByPath(const QString &path) +{ + QStringList pathParts = path.split('/'); + if (pathParts.empty()) return; + + if (pathParts[0] == "ROOT") + { + // remove ROOT because it literally does not exists :) + pathParts.removeFirst(); + } + + auto* model = qobject_cast(m_ui->localizedStrings->model()); + if (!model) + { + return; + } + + QModelIndex currentIndex = QModelIndex(); + + foreach (const QString& part, pathParts) + { + bool found = false; + int rows = model->rowCount(currentIndex); + + for (int i = 0; i < rows; ++i) + { + QModelIndex childIndex = model->index(i, 0, currentIndex); + QString entryName = model->data(childIndex, Qt::DisplayRole).toString(); + + if (entryName == part) + { + currentIndex = childIndex; + found = true; + m_ui->localizedStrings->expand(currentIndex); + m_ui->localizedStrings->scrollTo(currentIndex); + break; + } + } + + if (!found) + { + return; + } + } + + m_ui->localizedStrings->selectionModel()->select(currentIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); +} + +void SelectLocalizationTool::onAccepted() +{ + // need to set value + const QModelIndexList selectedIndexes = m_ui->localizedStrings->selectionModel()->selectedIndexes(); + if (selectedIndexes.isEmpty()) + { + // just do nothing + emit editFinished(); + return; + } + + QModelIndex currentIndex = selectedIndexes.first(); + QStringList pathParts {}; + while (currentIndex.isValid()) + { + pathParts.prepend(currentIndex.data().toString()); + currentIndex = currentIndex.parent(); + } + + const auto fullPath = pathParts.join('/').toStdString(); + auto newVal = getValue(); + if (!newVal.instructions.empty()) + { + // weird but ok + newVal.instructions[0] = gamelib::prp::PRPInstruction(newVal.instructions[0].getOpCode(), gamelib::prp::PRPOperandVal(fullPath)); + + // use base to avoid of extra selector iteration + widgets::TypePropertyWidget::setValue(newVal); + emit editFinished(); + } + + // and close us + close(); +} + +void SelectLocalizationTool::onRejected() +{ + emit editFinished(); + close(); +} + +void SelectLocalizationTool::onLocaleSelected(const QItemSelection &selected, const QItemSelection &deselected) +{ + if (selected.empty()) + { + disableAcceptButton(); + return; + } + + QModelIndex index = selected.first().indexes()[0]; + auto* node = reinterpret_cast(index.internalPointer()); + + if (!node) + { + disableAcceptButton(); + } + else + { + types::QGlacierValue temp = getValue(); + if (temp.instructions.empty()) + { + disableAcceptButton(); + } + else + { + enableAcceptButton(); + } + } +} + +void SelectLocalizationTool::closeEvent(QCloseEvent *pEvent) +{ + emit editFinished(); + QWidget::closeEvent(pEvent); +} \ No newline at end of file diff --git a/BMEdit/Editor/UI/UI/SelectLocalizationTool.ui b/BMEdit/Editor/UI/UI/SelectLocalizationTool.ui new file mode 100644 index 0000000..1b57134 --- /dev/null +++ b/BMEdit/Editor/UI/UI/SelectLocalizationTool.ui @@ -0,0 +1,31 @@ + + + SelectLocalizationTool + + + + 0 + 0 + 490 + 890 + + + + Select localized string... + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + From a774f9a2a382e1d7f5efc8aadda30ba8227be4bd Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 11 Jun 2024 19:35:09 +0300 Subject: [PATCH 75/80] Added support of M05 specific LOC tag --- .gitignore | 3 +- .../Source/Models/LocalizationTreeModel.cpp | 15 +++- .../GameLib/Include/GameLib/LOC/LOCTreeNode.h | 6 +- .../Source/GameLib/LOC/LOCTreeNode.cpp | 84 ++++++++++++++----- .../GameLib/Source/GameLib/LOC/LOCWriter.cpp | 30 ++++--- 5 files changed, 102 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 622cc72..a8a3d88 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ cmake-build-* BMEdit/Editor/UI/UI/*.autosave ThirdParty/zlib/zconf.h.included Test -venv \ No newline at end of file +venv +Notes.txt \ No newline at end of file diff --git a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp index 4260993..0c3e4b8 100644 --- a/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp +++ b/BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp @@ -43,6 +43,11 @@ namespace models return QString::fromStdString(node->value); } + if (index.column() == 1 && node->type == gamelib::loc::LOCTreeNodeType::EMPTY_BLOCK) + { + return QString("(EMPTY)"); + } + return {}; } @@ -199,11 +204,19 @@ namespace models if (!node) return flags; - if (index.column() == 0 || (index.column() == 1 && node->canHaveValue())) + if (index.column() == 0) { flags |= Qt::ItemIsEditable; } + if (index.column() == 1) + { + if (node->canHaveValue()) + { + flags |= Qt::ItemIsEditable; + } + } + return flags; } diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h index 2e26fad..5b0083b 100644 --- a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace ZBio::ZBinaryReader @@ -23,7 +24,9 @@ namespace gamelib::loc EMPTY_BLOCK = 0x8, ///< Empty chunk, no data at all LOCALIZED_STRING = 0x9, ///< Key value (string to aligned string) CHILDREN = 0x10, ///< Container (amount & list of offsets) + SUBTITLES_FIN = 0x28, ///< Subtitles finish string. NOTE: Maybe it's finish subtitle, will rename it later SUBTITLES = 0x29, ///< Subtitles value (long text with extra parameters) | 0x20 mask means that extra data exists + SUBTITLES_HINT = 0x2B ///< Another subtitles data with extra string hint }; enum LOCMissionObjectiveType : char @@ -66,12 +69,13 @@ namespace gamelib::loc struct SubtitleData { uint8_t unkData[8]; + std::string extraHint {}; } subtitle; [[nodiscard]] bool canHaveValue() const; [[nodiscard]] bool canHaveChildren() const; static void deserialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader* binaryReader); - static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter); + static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter, std::vector>& replacement); }; } \ No newline at end of file diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp index 79f4ec8..cd97584 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -9,7 +10,11 @@ namespace gamelib::loc { bool LOCTreeNode::canHaveValue() const { - return type == LOCTreeNodeType::LOCALIZED_STRING || type == LOCTreeNodeType::SUBTITLES; + return + type == LOCTreeNodeType::LOCALIZED_STRING || + type == LOCTreeNodeType::SUBTITLES || + type == LOCTreeNodeType::SUBTITLES_HINT || + type == LOCTreeNodeType::SUBTITLES_FIN; } bool LOCTreeNode::canHaveChildren() const @@ -23,9 +28,12 @@ namespace gamelib::loc node->name = binaryReader->readCString(); const auto type = binaryReader->read(); - if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING && type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK) + if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING && + type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK && + type != LOCTreeNodeType::SUBTITLES_FIN && type != LOCTreeNodeType::SUBTITLES_HINT) { - throw std::runtime_error("Invalid LOC format: expected to have 0x0 or 0x10, got smth else"); + auto errorMessage = fmt::format("Invalid LOC format: unexpected entity code 0x{:02X} at offset {} (0x{:X})", type, binaryReader->tell(), binaryReader->tell()); + throw std::runtime_error(errorMessage); } // Store type @@ -72,13 +80,38 @@ namespace gamelib::loc ZBioHelpers::seekBy(binaryReader, 4); // Need seek by 4 because value strings are "aligned". // Formula: len + 1 + 4 (+1 - zero terminator, 4 - "alignment") } - else if (node->type == LOCTreeNodeType::SUBTITLES) + else if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT || node->type == LOCTreeNodeType::SUBTITLES_FIN) { // Subtitles text node->value = binaryReader->readCString(); - // Read subtitle data. I really don't know what that value means, but as 2xu32 each same to each - binaryReader->read(&node->subtitle.unkData[0], 8); + if (node->type == LOCTreeNodeType::SUBTITLES_HINT) + { + // Read extra hint string + node->subtitle.extraHint = binaryReader->readCString(); + } + + if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT) + { + // Read subtitle data. In most cases there are 2xu32, but when first u32 zeroed next u32 not presented + // I love IOI because they don't give me an opportunity to relax... + uint32_t first = binaryReader->read(); + uint32_t second = 0; + if (first != 0) + { + // Ok, read second + second = binaryReader->read(); + } + + *reinterpret_cast(&node->subtitle.unkData[0]) = first; + *reinterpret_cast(&node->subtitle.unkData[4]) = second; + } + + if (node->type == LOCTreeNodeType::SUBTITLES_FIN) + { + // Always only 1 u32 + binaryReader->read(&node->subtitle.unkData[0], 4); + } } else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) { @@ -90,7 +123,7 @@ namespace gamelib::loc } } - void LOCTreeNode::serialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryWriter::BinaryWriter *binaryWriter) // NOLINT(*-no-recursion) + void LOCTreeNode::serialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryWriter::BinaryWriter *binaryWriter, std::vector>& replacement) // NOLINT(*-no-recursion) { // Write name (not aligned) binaryWriter->writeCString(node->name); @@ -102,19 +135,19 @@ namespace gamelib::loc { // Sort children auto children = node->children; // Need copy - std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); + //std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); // Write count const auto childrenCount = children.size() > 0xFF ? 0xFF : static_cast(children.size()); binaryWriter->write(childrenCount); // Write offsets - std::vector offsets{}; + std::vector offsets { 0u }; for (int i = 1; i < childrenCount; i++) { offsets.push_back(binaryWriter->tell()); - binaryWriter->write(0); // Fake offset (will be fixed) + binaryWriter->write(0xDDDDDDDDu); } // Write node by node & restore offsets @@ -124,18 +157,13 @@ namespace gamelib::loc { if (i > 0) { - // Restore offset + // Save replacement instruction uint32_t newOffset = binaryWriter->tell(); - - ZBioSeekGuard guard { binaryWriter }; - binaryWriter->seek(offsets[i - 1]); - - // Update offset - binaryWriter->write(newOffset - baseOffset); // Offset calculated from node #0 + replacement.emplace_back(offsets[i], newOffset - baseOffset); } // Write node itself - LOCTreeNode::serialize(children[i], binaryWriter); + LOCTreeNode::serialize(children[i], binaryWriter, replacement); } } else if (node->type == LOCTreeNodeType::LOCALIZED_STRING) @@ -146,13 +174,29 @@ namespace gamelib::loc // Write 4 byte alignment binaryWriter->write(0); } - else if (node->type == LOCTreeNodeType::SUBTITLES) + else if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT) { // Write unaligned string binaryWriter->writeCString(node->value); + if (node->type == LOCTreeNodeType::SUBTITLES_HINT) + { + // For hinted string need to write extra hint + binaryWriter->writeCString(node->subtitle.extraHint); + } + + uint32_t first = *reinterpret_cast(&node->subtitle.unkData[0]); + uint32_t second = *reinterpret_cast(&node->subtitle.unkData[4]); + // Write subtitles data - binaryWriter->write(&node->subtitle.unkData[0], 8); + binaryWriter->write(first); + if (first) + binaryWriter->write(second); + } + else if (node->type == LOCTreeNodeType::SUBTITLES_FIN) + { + // Store tutorial data here + binaryWriter->write(&node->subtitle.unkData[0], 4); } else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) { diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp index 59aa01e..459157e 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp @@ -12,21 +12,23 @@ namespace gamelib::loc auto writerSink = std::make_unique(); auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink)); + std::vector> replacement {}; + // Sort children by name. It's required because game wants to interact with sorted tree auto children = root->children; - std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); + //std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; }); // Write base binaryWriter.write(static_cast(children.size())); - std::vector offsets {}; + std::vector offsets { 0u }; // zero stored by default // Fill offsets if root child more than 1 for (int i = 1; i < children.size(); i++) { // Here we need to know an offset of subject after first created node. Instead of that we will save current offset and then jump back offsets.push_back(binaryWriter.tell()); - binaryWriter.write(0); // Temp value, will be replaced later + binaryWriter.write(0xDDDDDDDDu); // Temp value, will be replaced later } // And now we've ready to write node by node @@ -36,23 +38,25 @@ namespace gamelib::loc { if (i > 0) { - // Save current offset + // Save replacement uint32_t newOffset = binaryWriter.tell(); - - // Seek back to origin - ZBioSeekGuard guard { &binaryWriter }; - binaryWriter.seek(offsets[i - 1]); - - // Update offset - binaryWriter.write(newOffset - baseOffset); // Offset calculated from node #0 + replacement.emplace_back(offsets[i], newOffset - baseOffset); } // Write node itself - LOCTreeNode::serialize(children[i], &binaryWriter); + LOCTreeNode::serialize(children[i], &binaryWriter, replacement); } - // Done + // Take value auto raw = binaryWriter.release().value(); + + // Apply replacements + for (const auto& [offset, value] : replacement) + { + *reinterpret_cast(raw.data() + offset) = value; + } + + // Done std::copy(raw.begin(), raw.end(), std::back_inserter(outBuffer)); } } \ No newline at end of file From 50927c31ca76db63a666a112165611fc608219cf Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 11 Jun 2024 20:26:15 +0300 Subject: [PATCH 76/80] Confirmed support of all levels to read & write (validation passed) --- .../GameLib/Include/GameLib/LOC/LOCTreeNode.h | 3 +- .../Source/GameLib/LOC/LOCTreeNode.cpp | 90 ++++++++++++++----- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h index 5b0083b..d1665dd 100644 --- a/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h +++ b/BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h @@ -24,7 +24,8 @@ namespace gamelib::loc EMPTY_BLOCK = 0x8, ///< Empty chunk, no data at all LOCALIZED_STRING = 0x9, ///< Key value (string to aligned string) CHILDREN = 0x10, ///< Container (amount & list of offsets) - SUBTITLES_FIN = 0x28, ///< Subtitles finish string. NOTE: Maybe it's finish subtitle, will rename it later + SUBTITLES_HINT_WITH_COMMENT = 0x0B, ///< Some subtitle bullshit with comment (key, value string, comment aligned CString) + SUBTITLES_FIN = 0x28, ///< Subtitles finish string SUBTITLES = 0x29, ///< Subtitles value (long text with extra parameters) | 0x20 mask means that extra data exists SUBTITLES_HINT = 0x2B ///< Another subtitles data with extra string hint }; diff --git a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp index cd97584..687e46a 100644 --- a/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp +++ b/BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp @@ -14,7 +14,8 @@ namespace gamelib::loc type == LOCTreeNodeType::LOCALIZED_STRING || type == LOCTreeNodeType::SUBTITLES || type == LOCTreeNodeType::SUBTITLES_HINT || - type == LOCTreeNodeType::SUBTITLES_FIN; + type == LOCTreeNodeType::SUBTITLES_FIN || + type == LOCTreeNodeType::SUBTITLES_HINT_WITH_COMMENT; } bool LOCTreeNode::canHaveChildren() const @@ -30,7 +31,8 @@ namespace gamelib::loc const auto type = binaryReader->read(); if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING && type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK && - type != LOCTreeNodeType::SUBTITLES_FIN && type != LOCTreeNodeType::SUBTITLES_HINT) + type != LOCTreeNodeType::SUBTITLES_FIN && type != LOCTreeNodeType::SUBTITLES_HINT && + type != LOCTreeNodeType::SUBTITLES_HINT_WITH_COMMENT) { auto errorMessage = fmt::format("Invalid LOC format: unexpected entity code 0x{:02X} at offset {} (0x{:X})", type, binaryReader->tell(), binaryReader->tell()); throw std::runtime_error(errorMessage); @@ -80,38 +82,64 @@ namespace gamelib::loc ZBioHelpers::seekBy(binaryReader, 4); // Need seek by 4 because value strings are "aligned". // Formula: len + 1 + 4 (+1 - zero terminator, 4 - "alignment") } - else if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT || node->type == LOCTreeNodeType::SUBTITLES_FIN) + else if (node->type == LOCTreeNodeType::SUBTITLES) { // Subtitles text node->value = binaryReader->readCString(); - if (node->type == LOCTreeNodeType::SUBTITLES_HINT) + // Read subtitle data. In most cases there are 2xu32, but when first u32 zeroed next u32 not presented + // I love IOI because they don't give me an opportunity to relax... + uint32_t first = binaryReader->read(); + uint32_t second = 0; + if (first != 0) { - // Read extra hint string - node->subtitle.extraHint = binaryReader->readCString(); + // Ok, read second + second = binaryReader->read(); } - if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT) - { - // Read subtitle data. In most cases there are 2xu32, but when first u32 zeroed next u32 not presented - // I love IOI because they don't give me an opportunity to relax... - uint32_t first = binaryReader->read(); - uint32_t second = 0; - if (first != 0) - { - // Ok, read second - second = binaryReader->read(); - } + *reinterpret_cast(&node->subtitle.unkData[0]) = first; + *reinterpret_cast(&node->subtitle.unkData[4]) = second; + } + else if (node->type == LOCTreeNodeType::SUBTITLES_HINT) + { + // Subtitles text + node->value = binaryReader->readCString(); - *reinterpret_cast(&node->subtitle.unkData[0]) = first; - *reinterpret_cast(&node->subtitle.unkData[4]) = second; - } + // Read extra hint string + node->subtitle.extraHint = binaryReader->readCString(); - if (node->type == LOCTreeNodeType::SUBTITLES_FIN) + // Read subtitle data. In most cases there are 2xu32, but when first u32 zeroed next u32 not presented + // I love IOI because they don't give me an opportunity to relax... + uint32_t first = binaryReader->read(); + uint32_t second = 0; + if (first != 0) { - // Always only 1 u32 - binaryReader->read(&node->subtitle.unkData[0], 4); + // Ok, read second + second = binaryReader->read(); } + + *reinterpret_cast(&node->subtitle.unkData[0]) = first; + *reinterpret_cast(&node->subtitle.unkData[4]) = second; + } + else if (node->type == LOCTreeNodeType::SUBTITLES_FIN) + { + // Always only 1 u32 + uint32_t first = binaryReader->read(); + *reinterpret_cast(&node->subtitle.unkData[0]) = first; + } + else if (node->type == LOCTreeNodeType::SUBTITLES_HINT_WITH_COMMENT) + { + // Read value + node->value = binaryReader->readCString(); + + // Read hint (?) + node->subtitle.extraHint = binaryReader->readCString(); + + // DronCode: I'm not sure that next few bytes always zeroed. + // As we remember in LOCTreeNodeType::SUBTITLES & LOCTreeNodeType::SUBTITLES_HINT we have extra 8 bytes (2xu32 but sometimes only 1xu32 when it's zeroed). + // This could be our case. Idk, let's use code from SUBTITLES_FIN (idk why) + uint32_t first = binaryReader->read(); + *reinterpret_cast(&node->subtitle.unkData[0]) = first; } else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) { @@ -193,10 +221,24 @@ namespace gamelib::loc if (first) binaryWriter->write(second); } + else if (node->type == LOCTreeNodeType::SUBTITLES_HINT_WITH_COMMENT) + { + // Write value + binaryWriter->writeCString(node->value); + + // Write hint + binaryWriter->writeCString(node->subtitle.extraHint); + + // Write u32 (see deserializer code for details) + uint32_t first = *reinterpret_cast(&node->subtitle.unkData[0]); + binaryWriter->write(first); + } else if (node->type == LOCTreeNodeType::SUBTITLES_FIN) { // Store tutorial data here - binaryWriter->write(&node->subtitle.unkData[0], 4); + uint32_t first = *reinterpret_cast(&node->subtitle.unkData[0]); + + binaryWriter->write(first); } else if (node->type == LOCTreeNodeType::EMPTY_BLOCK) { From fa8e06b715e264c27c5cd5669b23d4b9a09ba225 Mon Sep 17 00:00:00 2001 From: DronCode Date: Tue, 11 Jun 2024 20:50:29 +0300 Subject: [PATCH 77/80] Fixed transform propagation (again) --- BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index b456fd7..0021a17 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -684,6 +684,10 @@ namespace widgets if (!sceneObject || !m_pLevel || !m_resources) return; + + // Invalidate self + m_resources->m_modelTransformCache[sceneObject] = sceneObject->getWorldTransform(); + // Visit limited subtree int iDepth = 2; // max 2 objects, otherwise it's better to make full invalidation (in case when user wants to move some huge object) sceneObject->visitChildren([this, &iDepth](const gamelib::scene::SceneObject::Ptr& pObject) -> gamelib::scene::SceneObject::EVisitResult { From 000b482eef38c919b5acc3e46ff5361451a5be66 Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 2 Oct 2024 20:29:09 +0300 Subject: [PATCH 78/80] Added support of RenderDoc Added beer and drunk optimisations in render pipeline --- BMEdit/Editor/CMakeLists.txt | 5 +- BMEdit/Editor/Include/Render/RenderEntry.h | 4 + .../Include/Widgets/SceneRenderWidget.h | 5 + .../Source/Widgets/SceneRenderWidget.cpp | 423 +++++++--- .../ThirdParty/RenderDoc/CMakeLists.txt | 6 + .../include/RenderDoc/renderdoc_app.h | 741 ++++++++++++++++++ 6 files changed, 1083 insertions(+), 101 deletions(-) create mode 100644 BMEdit/Editor/ThirdParty/RenderDoc/CMakeLists.txt create mode 100644 BMEdit/Editor/ThirdParty/RenderDoc/include/RenderDoc/renderdoc_app.h diff --git a/BMEdit/Editor/CMakeLists.txt b/BMEdit/Editor/CMakeLists.txt index b9effc8..d0d1b3c 100644 --- a/BMEdit/Editor/CMakeLists.txt +++ b/BMEdit/Editor/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.17) project(Editor VERSION 1.0.0 LANGUAGES CXX) +# --- Deps +add_subdirectory(ThirdParty/RenderDoc) + # --- Language standard set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -63,7 +66,7 @@ target_compile_definitions(Editor # --- Required GameLib & Qt6 target_link_libraries(Editor PUBLIC Qt6::Widgets OpenGL::GL Qt6::OpenGL Qt6::OpenGLWidgets) -target_link_libraries(Editor PRIVATE GameLib) +target_link_libraries(Editor PRIVATE GameLib RenderDoc::AppInterface) target_link_libraries(Editor PRIVATE ${libzip_LIBRARIES} ${ZLIB_LIBRARIES} ${libsquish_LIBRARIES}) target_include_directories(Editor PRIVATE ${ZLIB_INCLUDE_DIRS} ${libzip_INCLUDE_DIRS} ${libsquish_INCLUDE_DIRS}) target_compile_definitions(Editor PRIVATE ${libzip_DEFINITIONS} ${libsquish_DEFINITIONS}) \ No newline at end of file diff --git a/BMEdit/Editor/Include/Render/RenderEntry.h b/BMEdit/Editor/Include/Render/RenderEntry.h index 1b3be78..fc47d6b 100644 --- a/BMEdit/Editor/Include/Render/RenderEntry.h +++ b/BMEdit/Editor/Include/Render/RenderEntry.h @@ -39,6 +39,10 @@ namespace render uint32_t iTrianglesNr { 0 }; // Count of triangles or elements RenderTopology renderTopology { RenderTopology::RT_TRIANGLES }; // Topology of geometry buffer +#ifdef QT_DEBUG + std::string debugGroupId {}; +#endif + // World params glm::vec3 vPosition { .0f }; // World position glm::mat4 mWorldTransform { 1.f }; // Converted and translated matrix (ready to use in OpenGL) diff --git a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h index 7880711..e97d504 100644 --- a/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h +++ b/BMEdit/Editor/Include/Widgets/SceneRenderWidget.h @@ -133,6 +133,7 @@ namespace widgets void onObjectMoved(gamelib::scene::SceneObject* sceneObject); protected: + void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; @@ -165,6 +166,10 @@ namespace widgets */ void updateCameraRoomAttachment(RenderStats& stats, bool bRejectLastResult = true); + private: + void beginDebugGroup(std::string_view groupName); + void endDebugGroup(); + private: // Data gamelib::Level* m_pLevel { nullptr }; diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 0021a17..45fb384 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -29,9 +29,57 @@ #include #include +#ifdef Q_OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# define NOMINMAX +# include +# include +# include + +bool g_bShouldCaptureFrame = false; +RENDERDOC_API_1_1_2* g_pRenderDoc = nullptr; +#endif + +#ifdef QT_DEBUG +# include +#endif + namespace widgets { + static render::Shader* g_pLastKnownShader = nullptr; + + struct RenderState + { + bool bHasBlend = false; + bool bHasAlphaTest = false; + bool bHasFog = false; + + gamelib::mat::MATBlendMode eBlendMode { gamelib::mat::MATBlendMode::BM_ADD }; + gamelib::mat::MATCullMode eCullMode { gamelib::mat::MATCullMode::CM_DontCare }; + + std::array aTextures { 0 }; + + void reset() + { + bHasFog = false; + bHasBlend = false; + bHasAlphaTest = false; + eBlendMode = gamelib::mat::MATBlendMode::BM_ADD; + eCullMode = gamelib::mat::MATCullMode::CM_DontCare; + + for (auto& tex : aTextures) + { + tex = std::numeric_limits::max(); + } + } + + void set(QOpenGLFunctions_3_3_Core* glFuncs, const gamelib::mat::MATRenderState& state); + void apply(QOpenGLFunctions_3_3_Core* glFuncs) const; + }; + + static RenderState g_RenderState{}; + // Here stored geom names (common) where editor should avoid any rendering (it's too expensive and unnecessary for us) static const std::set g_bannedObjectIds { "AdditionalResources", "AllLevels/mainsceneincludes.zip", "AllLevels/equipment.zip" @@ -134,10 +182,35 @@ namespace widgets format.setVersion(3, 3); format.setProfile(QSurfaceFormat::CoreProfile); setFormat(format); + +#ifdef Q_OS_WINDOWS + if (HMODULE pRenderDocMod = GetModuleHandleA("renderdoc.dll")) + { + auto RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(pRenderDocMod, "RENDERDOC_GetAPI"); + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&g_pRenderDoc); + if (ret != 1) + { + g_pRenderDoc = nullptr; + } + } +#endif } SceneRenderWidget::~SceneRenderWidget() noexcept = default; + void SceneRenderWidget::initializeGL() + { +#ifdef Q_OS_WINDOWS + if (g_pRenderDoc) + { + g_pRenderDoc->SetActiveWindow( + context()->nativeInterface()->nativeContext(), + (void*)winId() + ); + } +#endif + } + void SceneRenderWidget::paintGL() { RenderStats renderStats {}; @@ -150,6 +223,12 @@ namespace widgets return; } +#ifdef Q_OS_WINDOWS + const bool bCaptureStarted = g_bShouldCaptureFrame; + g_bShouldCaptureFrame = false; + if (g_pRenderDoc && bCaptureStarted) g_pRenderDoc->StartFrameCapture(nullptr, nullptr); +#endif + // Begin frame const auto vp = getViewportSize(); funcs->glViewport(0, 0, vp.x, vp.y); @@ -206,6 +285,10 @@ namespace widgets // Prepare invalidated stuff doPrepareInvalidatedResources(funcs); + // Reset render state + g_RenderState.reset(); + g_RenderState.apply(funcs); + // Render scene gamelib::scene::SceneObject* pRoot = nullptr; bool bIgnoreVisibility = false; @@ -223,8 +306,7 @@ namespace widgets } } - if (!pRoot) - return; + if (!pRoot) break; if (m_renderList.empty()) { @@ -239,13 +321,17 @@ namespace widgets // 2 pass rendering: first render only non-alpha objects if (m_renderMode & RenderMode::RM_NON_ALPHA_OBJECTS) { + beginDebugGroup("NON_ALPHA_OBJECTS"); performRender(funcs, m_renderList, m_camera, onlyNonAlpha); + endDebugGroup(); } // then render only alpha objects if (m_renderMode & RenderMode::RM_ALPHA_OBJECTS) { + beginDebugGroup("ALPHA_OBJECTS"); performRender(funcs, m_renderList, m_camera, onlyAlpha); + endDebugGroup(); } // Submit stats @@ -260,6 +346,13 @@ namespace widgets } break; } + +#ifdef Q_OS_WINDOWS + if (g_pRenderDoc && bCaptureStarted) g_pRenderDoc->EndFrameCapture(nullptr, nullptr); +#endif + + if (g_pLastKnownShader) g_pLastKnownShader->unbind(funcs); + g_pLastKnownShader = nullptr; } void SceneRenderWidget::resizeGL(int w, int h) @@ -276,6 +369,13 @@ namespace widgets void SceneRenderWidget::keyPressEvent(QKeyEvent* event) { +#ifdef Q_OS_WINDOWS + if (event->key() == Qt::Key_F8) + { + g_bShouldCaptureFrame = true; + } +#endif + if (m_pLevel) { render::CameraMovementMask movementMask {}; @@ -402,6 +502,11 @@ namespace widgets void SceneRenderWidget::resetLevel() { + // reset GPU caches & other optimisations + g_pLastKnownShader = nullptr; + g_RenderState.reset(); + + // drop resources if (m_pLevel != nullptr) { m_resources = nullptr; @@ -1378,6 +1483,9 @@ namespace widgets boundingBoxEntry.iMeshIndex = 0; boundingBoxEntry.iTrianglesNr = 0; boundingBoxEntry.renderTopology = render::RenderTopology::RT_LINES; +#ifdef QT_DEBUG + boundingBoxEntry.debugGroupId = "[BBOX] " + geom->getName(); +#endif // World params boundingBoxEntry.vPosition = vPosition; @@ -1505,6 +1613,10 @@ namespace widgets // Push or not? if (!std::all_of(material.textures.begin(), material.textures.end(), [](const auto &v) -> bool { return v == kInvalidResource; })) { +#ifdef QT_DEBUG + renderEntry.debugGroupId = "[Mesh " + std::to_string(iMeshIdx) + "/" + std::to_string(model.meshes.size()) + "] " + geom->getName(); +#endif + entries.emplace_back(renderEntry); } } @@ -1533,80 +1645,6 @@ namespace widgets { glm::ivec2 viewResolution = getViewportSize(); - auto applyRenderState = [](QOpenGLFunctions_3_3_Core* gapi, const gamelib::mat::MATRenderState& renderState) - { - // Enable or disable blending - if (renderState.isBlendEnabled()) { - gapi->glEnable(GL_BLEND); - - // Set blend mode based on your enum values - switch (renderState.getBlendMode()) - { - case gamelib::mat::MATBlendMode::BM_TRANS: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: - gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: - gapi->glBlendFunc(GL_ONE, GL_ONE); - break; - case gamelib::mat::MATBlendMode::BM_ADD: - gapi->glBlendFunc(GL_ONE, GL_ONE); - gapi->glEnable(GL_BLEND); - break; - default: - // Do nothing - break; - } - } else { - gapi->glDisable(GL_BLEND); - } - - // Enable or disable alpha testing - if (renderState.isAlphaTestEnabled()) { - gapi->glEnable(GL_ALPHA_TEST); - } else { - gapi->glDisable(GL_ALPHA_TEST); - } - - // Enable or disable fog - if (renderState.isFogEnabled()) { - gapi->glEnable(GL_FOG); - } else { - gapi->glDisable(GL_FOG); - } - -#if 0 - // Enable or disable depth offset (Z bias) - if (renderState.hasZBias()) { - gapi->glEnable(GL_POLYGON_OFFSET_FILL); - gapi->glPolygonOffset(2.0f, renderState.getZOffset()); - } else { - gapi->glDisable(GL_POLYGON_OFFSET_FILL); - } -#endif - - // Set cull mode based on your enum values - switch (renderState.getCullMode()) - { - case gamelib::mat::MATCullMode::CM_OneSided: - gapi->glCullFace(GL_BACK); - break; - case gamelib::mat::MATCullMode::CM_DontCare: - case gamelib::mat::MATCullMode::CM_TwoSided: - // please complete - gapi->glDisable(GL_CULL_FACE); - break; - } - }; - static constexpr std::array g_aTextureKindToLocation { "i_uMaterial.mapDiffuse", "i_uMaterial.mapSpecularMask", @@ -1622,26 +1660,40 @@ namespace widgets if (!filter(entry)) continue; // skipped by filter +#ifdef QT_DEBUG + const bool bStartGroup = !entry.debugGroupId.empty(); + if (bStartGroup) beginDebugGroup(entry.debugGroupId); +#endif + // Switch render state - applyRenderState(glFunctions, entry.material.renderState); + g_RenderState.set(glFunctions, entry.material.renderState); // Activate material & setup parameters render::Shader* shader = entry.material.pShader; - shader->bind(glFunctions); + bool bShaderChanged = false; + + if (shader != g_pLastKnownShader) + { + if (g_pLastKnownShader) g_pLastKnownShader->unbind(glFunctions); + g_pLastKnownShader = shader; + + if (g_pLastKnownShader) g_pLastKnownShader->bind(glFunctions); + bShaderChanged = true; + } // Setup parameters (common) - shader->setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mWorldTransform); - shader->setUniform(glFunctions, ShaderConstants::kCameraProjection, m_camera.getProjection()); - shader->setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getView()); - shader->setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); + g_pLastKnownShader->setUniform(glFunctions, ShaderConstants::kModelTransform, entry.mWorldTransform); + g_pLastKnownShader->setUniform(glFunctions, ShaderConstants::kCameraProjection, m_camera.getProjection()); + g_pLastKnownShader->setUniform(glFunctions, ShaderConstants::kCameraView, m_camera.getView()); + g_pLastKnownShader->setUniform(glFunctions, ShaderConstants::kCameraResolution, viewResolution); // TODO: Need to move into constants - shader->setUniform(glFunctions, "i_uMaterial.v4DiffuseColor", entry.material.vDiffuseColor); - shader->setUniform(glFunctions, "i_uMaterial.gm_vZBiasOffset", entry.material.renderState.hasZBias() ? entry.material.gm_vZBiasOffset : glm::vec4(0.f)); - shader->setUniform(glFunctions, "i_uMaterial.v4Opacity", entry.material.v4Opacity); - shader->setUniform(glFunctions, "i_uMaterial.v4Bias", entry.material.v4Bias); - shader->setUniform(glFunctions, "i_uMaterial.alphaREF", std::clamp(entry.material.iAlphaREF, 0, 255)); - shader->setUniform(glFunctions, "i_uMaterial.fZOffset", entry.material.renderState.getZOffset()); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.v4DiffuseColor", entry.material.vDiffuseColor); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.gm_vZBiasOffset", entry.material.renderState.hasZBias() ? entry.material.gm_vZBiasOffset : glm::vec4(0.f)); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.v4Opacity", entry.material.v4Opacity); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.v4Bias", entry.material.v4Bias); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.alphaREF", std::clamp(entry.material.iAlphaREF, 0, 255)); + g_pLastKnownShader->setUniform(glFunctions, "i_uMaterial.fZOffset", entry.material.renderState.getZOffset()); // Bind textures for (int slotIdx = render::TextureSlotId::kMapDiffuse; slotIdx < render::TextureSlotId::kMaxTextureSlot; slotIdx++) @@ -1651,9 +1703,17 @@ namespace widgets if (glTexture == kInvalidResource) continue; - glFunctions->glActiveTexture(GL_TEXTURE0 + slotIdx); - glFunctions->glBindTexture(GL_TEXTURE_2D, glTexture); - shader->setUniform(glFunctions, std::string(g_aTextureKindToLocation[slotIdx]), slotIdx); + if (g_RenderState.aTextures[slotIdx] != glTexture) + { + glFunctions->glActiveTexture(GL_TEXTURE0 + slotIdx); + glFunctions->glBindTexture(GL_TEXTURE_2D, glTexture); + g_RenderState.aTextures[slotIdx] = glTexture; + } + + if (bShaderChanged) + { + g_pLastKnownShader->setUniform(glFunctions, std::string(g_aTextureKindToLocation[slotIdx]), slotIdx); + } } if (m_renderMode & RenderMode::RM_TEXTURE) @@ -1668,15 +1728,9 @@ namespace widgets glFunctions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } - // Release stuff - for (int slotIdx = render::TextureSlotId::kMapDiffuse; slotIdx < render::TextureSlotId::kMaxTextureSlot; slotIdx++) - { - glFunctions->glActiveTexture(GL_TEXTURE0 + slotIdx); - glFunctions->glBindTexture(GL_TEXTURE_2D, 0); - } - - // And it's done - shader->unbind(glFunctions); +#ifdef QT_DEBUG + if (bStartGroup) endDebugGroup(); +#endif } } @@ -2074,4 +2128,173 @@ namespace widgets } #endif } + + void SceneRenderWidget::beginDebugGroup(std::string_view groupName) + { +#ifdef QT_DEBUG + QOpenGLContext::currentContext()->extraFunctions()->glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 1, -1, groupName.data()); +#endif + } + + void SceneRenderWidget::endDebugGroup() + { +#ifdef QT_DEBUG + QOpenGLContext::currentContext()->extraFunctions()->glPopDebugGroup(); +#endif + } + + void RenderState::set(QOpenGLFunctions_3_3_Core* gapi, const gamelib::mat::MATRenderState &state) + { + if (state.isBlendEnabled() != bHasBlend) + { + bHasBlend = state.isBlendEnabled(); + + if (bHasBlend) + { + gapi->glEnable(GL_BLEND); + + // Set blend mode based on your enum values + switch (eBlendMode) + { + case gamelib::mat::MATBlendMode::BM_TRANS: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD: + gapi->glBlendFunc(GL_ONE, GL_ONE); + gapi->glEnable(GL_BLEND); + break; + default: + // Do nothing + break; + } + } else { + gapi->glDisable(GL_BLEND); + } + } + + if (bHasAlphaTest != state.isAlphaTestEnabled()) + { + bHasAlphaTest = state.isAlphaTestEnabled(); + + if (bHasAlphaTest) { + gapi->glEnable(GL_ALPHA_TEST); + } else { + gapi->glDisable(GL_ALPHA_TEST); + } + } + + if (bHasFog != state.isFogEnabled()) + { + bHasFog = state.isFogEnabled(); + + if (bHasFog) { + gapi->glEnable(GL_FOG); + } else { + gapi->glDisable(GL_FOG); + } + } + + if (eCullMode != state.getCullMode()) + { + eCullMode = state.getCullMode(); + + switch (eCullMode) + { + case gamelib::mat::MATCullMode::CM_OneSided: + gapi->glCullFace(GL_BACK); + break; + case gamelib::mat::MATCullMode::CM_DontCare: + case gamelib::mat::MATCullMode::CM_TwoSided: + // please complete + gapi->glDisable(GL_CULL_FACE); + break; + } + } + } + + void RenderState::apply(QOpenGLFunctions_3_3_Core* gapi) const + { + if (bHasBlend) + { + gapi->glEnable(GL_BLEND); + + // Set blend mode based on your enum values + switch (eBlendMode) + { + case gamelib::mat::MATBlendMode::BM_TRANS: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANS_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case gamelib::mat::MATBlendMode::BM_TRANSADD_ON_OPAQUE: + gapi->glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_BEFORE_TRANS: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD_ON_OPAQUE: + gapi->glBlendFunc(GL_ONE, GL_ONE); + break; + case gamelib::mat::MATBlendMode::BM_ADD: + gapi->glBlendFunc(GL_ONE, GL_ONE); + gapi->glEnable(GL_BLEND); + break; + default: + // Do nothing + break; + } + } else { + gapi->glDisable(GL_BLEND); + } + + // Enable or disable alpha testing + if (bHasAlphaTest) { + gapi->glEnable(GL_ALPHA_TEST); + } else { + gapi->glDisable(GL_ALPHA_TEST); + } + + // Enable or disable fog + if (bHasFog) { + gapi->glEnable(GL_FOG); + } else { + gapi->glDisable(GL_FOG); + } + +#if 0 + // Enable or disable depth offset (Z bias) + if (renderState.hasZBias()) { + gapi->glEnable(GL_POLYGON_OFFSET_FILL); + gapi->glPolygonOffset(2.0f, renderState.getZOffset()); + } else { + gapi->glDisable(GL_POLYGON_OFFSET_FILL); + } +#endif + + // Set cull mode based on your enum values + switch (eCullMode) + { + case gamelib::mat::MATCullMode::CM_OneSided: + gapi->glCullFace(GL_BACK); + break; + case gamelib::mat::MATCullMode::CM_DontCare: + case gamelib::mat::MATCullMode::CM_TwoSided: + // please complete + gapi->glDisable(GL_CULL_FACE); + break; + } + } } \ No newline at end of file diff --git a/BMEdit/Editor/ThirdParty/RenderDoc/CMakeLists.txt b/BMEdit/Editor/ThirdParty/RenderDoc/CMakeLists.txt new file mode 100644 index 0000000..0eb5b67 --- /dev/null +++ b/BMEdit/Editor/ThirdParty/RenderDoc/CMakeLists.txt @@ -0,0 +1,6 @@ +project(RenderDoc) + +add_library(RenderDoc_AppInterface INTERFACE) +add_library(RenderDoc::AppInterface ALIAS RenderDoc_AppInterface) + +target_include_directories(RenderDoc_AppInterface INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/BMEdit/Editor/ThirdParty/RenderDoc/include/RenderDoc/renderdoc_app.h b/BMEdit/Editor/ThirdParty/RenderDoc/include/RenderDoc/renderdoc_app.h new file mode 100644 index 0000000..31632be --- /dev/null +++ b/BMEdit/Editor/ThirdParty/RenderDoc/include/RenderDoc/renderdoc_app.h @@ -0,0 +1,741 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019-2024 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#pragma once + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html +// + +#if !defined(RENDERDOC_NO_STDINT) + #include +#endif + +#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) + #define RENDERDOC_CC __cdecl +#elif defined(__linux__) || defined(__FreeBSD__) + #define RENDERDOC_CC +#elif defined(__APPLE__) + #define RENDERDOC_CC +#else + #error "Unknown platform" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Constants not used directly in below API + +// This is a GUID/magic value used for when applications pass a path where shader debug +// information can be found to match up with a stripped shader. +// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = +// RENDERDOC_ShaderDebugMagicValue_value +#define RENDERDOC_ShaderDebugMagicValue_struct \ + { \ + 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// as an alternative when you want a byte array (assuming x86 endianness): +#define RENDERDOC_ShaderDebugMagicValue_bytearray \ + { \ + 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ + } + +// truncated version when only a uint64_t is available (e.g. Vulkan tags): +#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc capture options +// + +typedef enum RENDERDOC_CaptureOption +{ + // Allow the application to enable vsync + // + // Default - enabled + // + // 1 - The application can enable or disable vsync at will + // 0 - vsync is force disabled + eRENDERDOC_Option_AllowVSync = 0, + + // Allow the application to enable fullscreen + // + // Default - enabled + // + // 1 - The application can enable or disable fullscreen at will + // 0 - fullscreen is force disabled + eRENDERDOC_Option_AllowFullscreen = 1, + + // Record API debugging events and messages + // + // Default - disabled + // + // 1 - Enable built-in API debugging features and records the results into + // the capture, which is matched up with events on replay + // 0 - no API debugging is forcibly enabled + eRENDERDOC_Option_APIValidation = 2, + eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum + + // Capture CPU callstacks for API events + // + // Default - disabled + // + // 1 - Enables capturing of callstacks + // 0 - no callstacks are captured + eRENDERDOC_Option_CaptureCallstacks = 3, + + // When capturing CPU callstacks, only capture them from actions. + // This option does nothing without the above option being enabled + // + // Default - disabled + // + // 1 - Only captures callstacks for actions. + // Ignored if CaptureCallstacks is disabled + // 0 - Callstacks, if enabled, are captured for every event. + eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, + eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4, + + // Specify a delay in seconds to wait for a debugger to attach, after + // creating or injecting into a process, before continuing to allow it to run. + // + // 0 indicates no delay, and the process will run immediately after injection + // + // Default - 0 seconds + // + eRENDERDOC_Option_DelayForDebugger = 5, + + // Verify buffer access. This includes checking the memory returned by a Map() call to + // detect any out-of-bounds modification, as well as initialising buffers with undefined contents + // to a marker value to catch use of uninitialised memory. + // + // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do + // not do the same kind of interception & checking and undefined contents are really undefined. + // + // Default - disabled + // + // 1 - Verify buffer access + // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in + // RenderDoc. + eRENDERDOC_Option_VerifyBufferAccess = 6, + + // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. + // This option now controls the filling of uninitialised buffers with 0xdddddddd which was + // previously always enabled + eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, + + // Hooks any system API calls that create child processes, and injects + // RenderDoc into them recursively with the same options. + // + // Default - disabled + // + // 1 - Hooks into spawned child processes + // 0 - Child processes are not hooked by RenderDoc + eRENDERDOC_Option_HookIntoChildren = 7, + + // By default RenderDoc only includes resources in the final capture necessary + // for that frame, this allows you to override that behaviour. + // + // Default - disabled + // + // 1 - all live resources at the time of capture are included in the capture + // and available for inspection + // 0 - only the resources referenced by the captured frame are included + eRENDERDOC_Option_RefAllResources = 8, + + // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or + // getting it will be ignored, to allow compatibility with older versions. + // In v1.1 the option acts as if it's always enabled. + // + // By default RenderDoc skips saving initial states for resources where the + // previous contents don't appear to be used, assuming that writes before + // reads indicate previous contents aren't used. + // + // Default - disabled + // + // 1 - initial contents at the start of each captured frame are saved, even if + // they are later overwritten or cleared before being used. + // 0 - unless a read is detected, initial contents will not be saved and will + // appear as black or empty data. + eRENDERDOC_Option_SaveAllInitials = 9, + + // In APIs that allow for the recording of command lists to be replayed later, + // RenderDoc may choose to not capture command lists before a frame capture is + // triggered, to reduce overheads. This means any command lists recorded once + // and replayed many times will not be available and may cause a failure to + // capture. + // + // NOTE: This is only true for APIs where multithreading is difficult or + // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option + // and always capture all command lists since the API is heavily oriented + // around it and the overheads have been reduced by API design. + // + // 1 - All command lists are captured from the start of the application + // 0 - Command lists are only captured if their recording begins during + // the period when a frame capture is in progress. + eRENDERDOC_Option_CaptureAllCmdLists = 10, + + // Mute API debugging output when the API validation mode option is enabled + // + // Default - enabled + // + // 1 - Mute any API debug messages from being displayed or passed through + // 0 - API debugging is displayed as normal + eRENDERDOC_Option_DebugOutputMute = 11, + + // Option to allow vendor extensions to be used even when they may be + // incompatible with RenderDoc and cause corrupted replays or crashes. + // + // Default - inactive + // + // No values are documented, this option should only be used when absolutely + // necessary as directed by a RenderDoc developer. + eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, + + // Define a soft memory limit which some APIs may aim to keep overhead under where + // possible. Anything above this limit will where possible be saved directly to disk during + // capture. + // This will cause increased disk space use (which may cause a capture to fail if disk space is + // exhausted) as well as slower capture times. + // + // Not all memory allocations may be deferred like this so it is not a guarantee of a memory + // limit. + // + // Units are in MBs, suggested values would range from 200MB to 1000MB. + // + // Default - 0 Megabytes + eRENDERDOC_Option_SoftMemoryLimit = 13, +} RENDERDOC_CaptureOption; + +// Sets an option that controls how RenderDoc behaves on capture. +// +// Returns 1 if the option and value are valid +// Returns 0 if either is invalid and the option is unchanged +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); +typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); + +// Gets the current value of an option as a uint32_t +// +// If the option is invalid, 0xffffffff is returned +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); + +// Gets the current value of an option as a float +// +// If the option is invalid, -FLT_MAX is returned +typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); + +typedef enum RENDERDOC_InputButton +{ + // '0' - '9' matches ASCII values + eRENDERDOC_Key_0 = 0x30, + eRENDERDOC_Key_1 = 0x31, + eRENDERDOC_Key_2 = 0x32, + eRENDERDOC_Key_3 = 0x33, + eRENDERDOC_Key_4 = 0x34, + eRENDERDOC_Key_5 = 0x35, + eRENDERDOC_Key_6 = 0x36, + eRENDERDOC_Key_7 = 0x37, + eRENDERDOC_Key_8 = 0x38, + eRENDERDOC_Key_9 = 0x39, + + // 'A' - 'Z' matches ASCII values + eRENDERDOC_Key_A = 0x41, + eRENDERDOC_Key_B = 0x42, + eRENDERDOC_Key_C = 0x43, + eRENDERDOC_Key_D = 0x44, + eRENDERDOC_Key_E = 0x45, + eRENDERDOC_Key_F = 0x46, + eRENDERDOC_Key_G = 0x47, + eRENDERDOC_Key_H = 0x48, + eRENDERDOC_Key_I = 0x49, + eRENDERDOC_Key_J = 0x4A, + eRENDERDOC_Key_K = 0x4B, + eRENDERDOC_Key_L = 0x4C, + eRENDERDOC_Key_M = 0x4D, + eRENDERDOC_Key_N = 0x4E, + eRENDERDOC_Key_O = 0x4F, + eRENDERDOC_Key_P = 0x50, + eRENDERDOC_Key_Q = 0x51, + eRENDERDOC_Key_R = 0x52, + eRENDERDOC_Key_S = 0x53, + eRENDERDOC_Key_T = 0x54, + eRENDERDOC_Key_U = 0x55, + eRENDERDOC_Key_V = 0x56, + eRENDERDOC_Key_W = 0x57, + eRENDERDOC_Key_X = 0x58, + eRENDERDOC_Key_Y = 0x59, + eRENDERDOC_Key_Z = 0x5A, + + // leave the rest of the ASCII range free + // in case we want to use it later + eRENDERDOC_Key_NonPrintable = 0x100, + + eRENDERDOC_Key_Divide, + eRENDERDOC_Key_Multiply, + eRENDERDOC_Key_Subtract, + eRENDERDOC_Key_Plus, + + eRENDERDOC_Key_F1, + eRENDERDOC_Key_F2, + eRENDERDOC_Key_F3, + eRENDERDOC_Key_F4, + eRENDERDOC_Key_F5, + eRENDERDOC_Key_F6, + eRENDERDOC_Key_F7, + eRENDERDOC_Key_F8, + eRENDERDOC_Key_F9, + eRENDERDOC_Key_F10, + eRENDERDOC_Key_F11, + eRENDERDOC_Key_F12, + + eRENDERDOC_Key_Home, + eRENDERDOC_Key_End, + eRENDERDOC_Key_Insert, + eRENDERDOC_Key_Delete, + eRENDERDOC_Key_PageUp, + eRENDERDOC_Key_PageDn, + + eRENDERDOC_Key_Backspace, + eRENDERDOC_Key_Tab, + eRENDERDOC_Key_PrtScrn, + eRENDERDOC_Key_Pause, + + eRENDERDOC_Key_Max, +} RENDERDOC_InputButton; + +// Sets which key or keys can be used to toggle focus between multiple windows +// +// If keys is NULL or num is 0, toggle keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); + +// Sets which key or keys can be used to capture the next frame +// +// If keys is NULL or num is 0, captures keys will be disabled +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); + +typedef enum RENDERDOC_OverlayBits +{ + // This single bit controls whether the overlay is enabled or disabled globally + eRENDERDOC_Overlay_Enabled = 0x1, + + // Show the average framerate over several seconds as well as min/max + eRENDERDOC_Overlay_FrameRate = 0x2, + + // Show the current frame number + eRENDERDOC_Overlay_FrameNumber = 0x4, + + // Show a list of recent captures, and how many captures have been made + eRENDERDOC_Overlay_CaptureList = 0x8, + + // Default values for the overlay mask + eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | + eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), + + // Enable all bits + eRENDERDOC_Overlay_All = ~0U, + + // Disable all bits + eRENDERDOC_Overlay_None = 0, +} RENDERDOC_OverlayBits; + +// returns the overlay bits that have been set +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); +// sets the overlay bits with an and & or mask +typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); + +// this function will attempt to remove RenderDoc's hooks in the application. +// +// Note: that this can only work correctly if done immediately after +// the module is loaded, before any API work happens. RenderDoc will remove its +// injected hooks and shut down. Behaviour is undefined if this is called +// after any API functions have been called, and there is still no guarantee of +// success. +typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); + +// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. +typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; + +// This function will unload RenderDoc's crash handler. +// +// If you use your own crash handler and don't want RenderDoc's handler to +// intercede, you can call this function to unload it and any unhandled +// exceptions will pass to the next handler. +typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); + +// Sets the capture file path template +// +// pathtemplate is a UTF-8 string that gives a template for how captures will be named +// and where they will be saved. +// +// Any extension is stripped off the path, and captures are saved in the directory +// specified, and named with the filename and the frame number appended. If the +// directory does not exist it will be created, including any parent directories. +// +// If pathtemplate is NULL, the template will remain unchanged +// +// Example: +// +// SetCaptureFilePathTemplate("my_captures/example"); +// +// Capture #1 -> my_captures/example_frame123.rdc +// Capture #2 -> my_captures/example_frame456.rdc +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); + +// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string +typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. +typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; +typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; + +// returns the number of captures that have been made +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); + +// This function returns the details of a capture, by index. New captures are added +// to the end of the list. +// +// filename will be filled with the absolute path to the capture file, as a UTF-8 string +// pathlength will be written with the length in bytes of the filename string +// timestamp will be written with the time of the capture, in seconds since the Unix epoch +// +// Any of the parameters can be NULL and they'll be skipped. +// +// The function will return 1 if the capture index is valid, or 0 if the index is invalid +// If the index is invalid, the values will be unchanged +// +// Note: when captures are deleted in the UI they will remain in this list, so the +// capture path may not exist anymore. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, + uint32_t *pathlength, uint64_t *timestamp); + +// Sets the comments associated with a capture file. These comments are displayed in the +// UI program when opening. +// +// filePath should be a path to the capture file to add comments to. If set to NULL or "" +// the most recent capture file created made will be used instead. +// comments should be a NULL-terminated UTF-8 string to add as comments. +// +// Any existing comments will be overwritten. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, + const char *comments); + +// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); + +// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. +// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for +// backwards compatibility with old code, it is castable either way since it's ABI compatible +// as the same function pointer type. +typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; + +// This function will launch the Replay UI associated with the RenderDoc library injected +// into the running application. +// +// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter +// to connect to this application +// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open +// if cmdline is NULL, the command line will be empty. +// +// returns the PID of the replay UI if successful, 0 if not successful. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, + const char *cmdline); + +// RenderDoc can return a higher version than requested if it's backwards compatible, +// this function returns the actual version returned. If a parameter is NULL, it will be +// ignored and the others will be filled out. +typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); + +// Requests that the replay UI show itself (if hidden or not the current top window). This can be +// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle +// showing the UI after making a capture. +// +// This will return 1 if the request was successfully passed on, though it's not guaranteed that +// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current +// target control connection to make such a request, or if there was another error +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)(); + +////////////////////////////////////////////////////////////////////////// +// Capturing functions +// + +// A device pointer is a pointer to the API's root handle. +// +// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc +typedef void *RENDERDOC_DevicePointer; + +// A window handle is the OS's native window handle +// +// This would be an HWND, GLXDrawable, etc +typedef void *RENDERDOC_WindowHandle; + +// A helper macro for Vulkan, where the device handle cannot be used directly. +// +// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. +// +// Specifically, the value needed is the dispatch table pointer, which sits as the first +// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and +// indirect once. +#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) + +// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will +// respond to keypresses. Neither parameter can be NULL +typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// capture the next frame on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); + +// capture the next N frames on whichever window and API is currently considered active +typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); + +// When choosing either a device pointer or a window handle to capture, you can pass NULL. +// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify +// any API rendering to a specific window, or a specific API instance rendering to any window, +// or in the simplest case of one window and one API, you can just pass NULL for both. +// +// In either case, if there are two or more possible matching (device,window) pairs it +// is undefined which one will be captured. +// +// Note: for headless rendering you can pass NULL for the window handle and either specify +// a device pointer or leave it NULL as above. + +// Immediately starts capturing API calls on the specified device pointer and window handle. +// +// If there is no matching thing to capture (e.g. no supported API has been initialised), +// this will do nothing. +// +// The results are undefined (including crashes) if two captures are started overlapping, +// even on separate devices and/oror windows. +typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Returns whether or not a frame capture is currently ongoing anywhere. +// +// This will return 1 if a capture is ongoing, and 0 if there is no capture running +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); + +// Ends capturing immediately. +// +// This will return 1 if the capture succeeded, and 0 if there was an error capturing. +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Ends capturing immediately and discard any data stored without saving to disk. +// +// This will return 1 if the capture was discarded, and 0 if there was an error or no capture +// was in progress +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, + RENDERDOC_WindowHandle wndHandle); + +// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom +// title to the capture produced which will be displayed in the UI. +// +// If multiple captures are ongoing, this title will be applied to the first capture to end after +// this call. The second capture to end will have no title, unless this function is called again. +// +// Calling this function has no effect if no capture is currently running, and if it is called +// multiple times only the last title will be used. +typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API versions +// + +// RenderDoc uses semantic versioning (http://semver.org/). +// +// MAJOR version is incremented when incompatible API changes happen. +// MINOR version is incremented when functionality is added in a backwards-compatible manner. +// PATCH version is incremented when backwards-compatible bug fixes happen. +// +// Note that this means the API returned can be higher than the one you might have requested. +// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned +// instead of 1.0.0. You can check this with the GetAPIVersion entry point +typedef enum RENDERDOC_Version +{ + eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 + eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 + eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 + eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 + eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 + eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 + eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 + eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 + eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 + eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 + eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 + eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 + eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 +} RENDERDOC_Version; + +// API version changelog: +// +// 1.0.0 - initial release +// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered +// by keypress or TriggerCapture, instead of Start/EndFrameCapture. +// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation +// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new +// function pointer is added to the end of the struct, the original layout is identical +// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote +// replay/remote server concept in replay UI) +// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these +// are captures and not debug logging files. This is the first API version in the v1.0 +// branch. +// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be +// displayed in the UI program on load. +// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions +// which allows users to opt-in to allowing unsupported vendor extensions to function. +// Should be used at the user's own risk. +// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to +// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to +// 0xdddddddd of uninitialised buffer contents. +// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop +// capturing without saving anything to disk. +// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening +// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option. +// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected +// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a +// capture made with StartFrameCapture() or EndFrameCapture() + +typedef struct RENDERDOC_API_1_6_0 +{ + pRENDERDOC_GetAPIVersion GetAPIVersion; + + pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; + pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; + + pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; + pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; + + pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; + pRENDERDOC_SetCaptureKeys SetCaptureKeys; + + pRENDERDOC_GetOverlayBits GetOverlayBits; + pRENDERDOC_MaskOverlayBits MaskOverlayBits; + + // Shutdown was renamed to RemoveHooks in 1.4.1. + // These unions allow old code to continue compiling without changes + union + { + pRENDERDOC_Shutdown Shutdown; + pRENDERDOC_RemoveHooks RemoveHooks; + }; + pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; + + // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. + // These unions allow old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; + // current name + pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; + }; + union + { + // deprecated name + pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; + // current name + pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; + }; + + pRENDERDOC_GetNumCaptures GetNumCaptures; + pRENDERDOC_GetCapture GetCapture; + + pRENDERDOC_TriggerCapture TriggerCapture; + + // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. + // This union allows old code to continue compiling without changes + union + { + // deprecated name + pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; + // current name + pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; + }; + pRENDERDOC_LaunchReplayUI LaunchReplayUI; + + pRENDERDOC_SetActiveWindow SetActiveWindow; + + pRENDERDOC_StartFrameCapture StartFrameCapture; + pRENDERDOC_IsFrameCapturing IsFrameCapturing; + pRENDERDOC_EndFrameCapture EndFrameCapture; + + // new function in 1.1.0 + pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; + + // new function in 1.2.0 + pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; + + // new function in 1.4.0 + pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; + + // new function in 1.5.0 + pRENDERDOC_ShowReplayUI ShowReplayUI; + + // new function in 1.6.0 + pRENDERDOC_SetCaptureTitle SetCaptureTitle; +} RENDERDOC_API_1_6_0; + +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0; + +////////////////////////////////////////////////////////////////////////////////////////////////// +// RenderDoc API entry point +// +// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. +// +// The name is the same as the typedef - "RENDERDOC_GetAPI" +// +// This function is not thread safe, and should not be called on multiple threads at once. +// Ideally, call this once as early as possible in your application's startup, before doing +// any API work, since some configuration functionality etc has to be done also before +// initialising any APIs. +// +// Parameters: +// version is a single value from the RENDERDOC_Version above. +// +// outAPIPointers will be filled out with a pointer to the corresponding struct of function +// pointers. +// +// Returns: +// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested +// 0 - if the requested version is not supported or the arguments are invalid. +// +typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file From 96c6f012053b085f77f53df198144186cdfe4a1d Mon Sep 17 00:00:00 2001 From: DronCode Date: Wed, 2 Oct 2024 20:35:26 +0300 Subject: [PATCH 79/80] CI: Switch actions/upload-artifact to v4 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6e70fc..3b7f6ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,7 +76,7 @@ jobs: mv README.md dist - name: Upload build artifacts - BMEdit - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: "BMEdit" path: dist From 0b468940e163b9a1b7310988af4f352f568237b6 Mon Sep 17 00:00:00 2001 From: DronCode Date: Thu, 3 Oct 2024 20:45:03 +0300 Subject: [PATCH 80/80] Reversed colibits from GMS. Added small filter to fix low fps in some places. --- Assets/g1/ECollisionMask.json | 15 +++++++++++++++ .../Source/Widgets/SceneRenderWidget.cpp | 17 ++++++++++++++--- .../Include/GameLib/GMS/GMSGeomEntity.h | 18 ++++++++++++++---- .../Source/GameLib/GMS/GMSGeomEntity.cpp | 7 ++----- 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 Assets/g1/ECollisionMask.json diff --git a/Assets/g1/ECollisionMask.json b/Assets/g1/ECollisionMask.json new file mode 100644 index 0000000..89bb00c --- /dev/null +++ b/Assets/g1/ECollisionMask.json @@ -0,0 +1,15 @@ +{ + "typename": "ECollisionMask", + "kind": "TypeKind.ENUM", + "enum": { + "COLIMASK_All" : 1, + "COLIMASK_Background": 2, + "COLIMASK_Shot": 4, + "COLIMASK_WaterGlass": 8, + "COLIMASK_NoWalk": 16, + "COLIMASK_Sight": 32, + "COLIMASK_Hero": 64, + "COLIMASK_Camera": 128, + "COLIMASK_NPC": 256 + } +} \ No newline at end of file diff --git a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp index 45fb384..dadb7a2 100644 --- a/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp +++ b/BMEdit/Editor/Source/Widgets/SceneRenderWidget.cpp @@ -49,6 +49,14 @@ namespace widgets { static render::Shader* g_pLastKnownShader = nullptr; + static bool canDrawGeom(const gamelib::scene::SceneObject* pObject) + { + using CM = gamelib::gms::ECollisionMask; + constexpr uint32_t kExpectedToSeeMask = CM::COLIMASK_Sight | CM::COLIMASK_Hero | CM::COLIMASK_NPC | CM::COLIMASK_Background; + + return pObject && (pObject->getGeomInfo().getColiBits() & kExpectedToSeeMask); + } + struct RenderState { bool bHasBlend = false; @@ -1341,6 +1349,9 @@ namespace widgets if (visitedObjects.contains(sObject.pObject.get())) continue; // Skip because it's in render list already + if (!canDrawGeom(sObject.pObject.get())) + continue; + if (auto bbox = getGameObjectBoundingBox(sObject.pObject, true); bbox.has_value() && m_camera.canSeeObject(bbox.value())) { // Need to render it @@ -1451,9 +1462,9 @@ namespace widgets return; } - // Don't draw invisible things - if (bInvisible) - return; + if (!bIgnoreVisibility) + if (bInvisible || !canDrawGeom(geom)) + return; if (g_bannedObjectIds.contains(std::string_view{geom->getName()}) || geom->getName().starts_with("CloneGroup_")) return; diff --git a/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h b/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h index 831f366..b012030 100644 --- a/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h +++ b/BMEdit/GameLib/Include/GameLib/GMS/GMSGeomEntity.h @@ -11,6 +11,19 @@ namespace ZBio::ZBinaryReader namespace gamelib::gms { + enum ECollisionMask : uint32_t + { + COLIMASK_All = 1, + COLIMASK_Background = 2, + COLIMASK_Shot = 4, + COLIMASK_WaterGlass = 8, + COLIMASK_NoWalk = 16, + COLIMASK_Sight = 32, + COLIMASK_Hero = 64, + COLIMASK_Camera = 128, + COLIMASK_NPC = 256 + }; + class GMSGeomEntity { ///---------- @@ -53,10 +66,7 @@ namespace gamelib::gms uint32_t m_unk10 { }; uint32_t m_typeId { }; uint32_t m_unk18 { }; - uint8_t m_coliBits {}; // +1C - uint8_t m_unk1D {}; - uint8_t m_unk1E {}; - uint8_t m_unk1F {}; + uint32_t m_coliBits {}; // +1C. NOTE: ECollisionMask contains all possible & expected values uint32_t m_unk20 { }; uint32_t m_unk24 { }; uint32_t m_unk28 { }; diff --git a/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp b/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp index cc12540..dbfe9bf 100644 --- a/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp +++ b/BMEdit/GameLib/Source/GameLib/GMS/GMSGeomEntity.cpp @@ -23,7 +23,7 @@ namespace gamelib::gms uint32_t GMSGeomEntity::getColiBits() const { - return static_cast(m_coliBits); + return m_coliBits; } uint32_t GMSGeomEntity::getParentGeomIndex() const @@ -79,10 +79,7 @@ namespace gamelib::gms entity.m_unk18 = gmsBinaryReader->read(); // Read coliBits - entity.m_coliBits = gmsBinaryReader->read(); - entity.m_unk1D = gmsBinaryReader->read(); - entity.m_unk1E = gmsBinaryReader->read(); - entity.m_unk1F = gmsBinaryReader->read(); + entity.m_coliBits = gmsBinaryReader->read(); // Read unk20, 24, 28, 2C entity.m_unk20 = gmsBinaryReader->read();